【Pyxel】中級者までの道のりと管理クラスの話

# はじめに

Pythonのゲームライブラリである Pyxel は、覚えることが少なく教育用に向いていますが、教育用言語の代表格である Scratch から移行する時には気をつけたい点があります。
それは、Scratchはたくさんのスプライトを使う作品を作りやすいのに対し、Python+Pyxelで同じような複雑さを持つ作品を作るのはむずかしく、楽しくないと感じてしまうケースです。

本記事の前半部では、Pyxelで少し大きなゲームを作れるようになるまでの道のりを示します（中級者への道のり）。当然ですが一例に過ぎません。
後半部では、一つのクラスからたくさんのインスタンスを生成し制御するような Pyxel 作品を作る時に、プログラムを整理するのに役立つ `管理クラス` の作り方をサンプルと共に示します。

# 【前半部】 中級者への道のり

## Scratchの次のステップとして Pyxel に取り組むにあたって

Scratch はクローン（≒インスタンス）生成のコードをスプライト（≒クラス）自身に記述することができるし、他のスプライトからクローンを生成・削除することもできます。
Scratchのこの柔軟な仕組みに慣れていると、Pyxel 学習時に『思い描いた作品が作れない』と感じるかもしれません。こちらとしては『 Scratchで簡単にできることは Pyxel だと難しい』 と伝えるのは避けたいです。

たしかにScratchのような様々な方法でPythonプログラムを破綻しないよう作るには多くの知識が必要です。しかし後半で紹介する管理クラスを使えば、非効率な場面はあるかもしれないけど、着実に完成させられると思います。
もし基本習得の段階で面白さを見出せていない人がいたら、中級者への道のりを知り、成長したら作れる作品に想いを馳せながら、気長に続けてほしいです。継続することで得られるものがきっとあるはずです

## 中級者への道のり

1. なにはともあれ Python の基本学習
    - Python入門書を立ち読みして、クラスの説明が自分に合ってると感じた本を選びましょう
    - 本記事の狙いから逸れますが、この段階でPygameを使ったゲーム制作入門書に取り組んでも良いと思います。代表的なゲームロジックを知ることはPyxelでも役立ちます
2. Pyxel の基本学習
    - Web上に丁寧な教材があるので心配ないです。おすすめは[勉強ボックス](https://kinutani.hateblo.jp/entry/2022/10/24/161404) 様です
    - それから [Pyxel作者様の入門書](https://www.amazon.co.jp/dp/4297146576)が出版されるようです。Python基礎学習もカバーしているようです。期待大ですね
    - 教材に沿って小さな作品をいくつか制作しましょう
    - 慣れてきたら、サンプルプログラムを読み解いて、変更を加えるのもいいかもしれません
3. クラスの理解度を深める
    - 入門書やWebサイトを読んで真似るだけでは、クラスを習得した実感が得られにくいと思います
    - 『 画面端で反射するBallクラスを作り、そのBallを画面いっぱいに増殖させる』プログラムを、自分の力で作ってみてほしいです。そのまま簡単なクリックゲームに改造するとさらなるレベルアップを期待できます
4. 部品クラスを作る経験を重ねる（後半部の内容）
    - 作りたい作品の部品に注目します
　例）シューティングゲームの敵、弾 など
    - そしてその部品が必要とする要素（変数・関数）をある程度見定めたらクラスを記述します
　例）画面に表示して動かすから座標x,yと速度vx,vy が必要 など
    - つい先回りし過ぎて、将来必要そうな機能をあれこれ追加したくなりますが、それでは制作の終わりが見えなくなります。必要な時がきたら追加しましょう
    - 本記事では、サイコロとボタンを作る様子をお伝えします
5. 管理クラスを作る経験を重ねる（後半部の内容）
    - １つのクラスから複数のインスタンスを作って制御するコード（生成,削除,更新,描画）を、まずは Appクラスのupdate と draw に直接書きます、始めのうちはそれでOKです
    - しかし部品のクラスの種類が増えるに従い、一つの関数内のコード量が増えて、全体の流れが分かりにくくなります。これを解決するために管理クラスを作成します
    - 本記事ではサイコロとボタンそれぞれの管理クラスを作ります
6. ファイルを分ける
    - 管理クラスでAppクラスがシンプルになっても、部品が増えればコード自体は膨れ上がるのでファイルを分けましょう
    - [【参考】ソースファイル分割（勉強ボックス様）](https://kinutani.hateblo.jp/entry/2023/01/28/180503)

7. いったん完成させる
    - 『これで完成！』と区切りをつけましょう

8. 興味を引くサンプルプログラムを見つけて、読み解いてみる
    - 頑張れば読解できそうなレベルのサンプルプログラムを見つける
    - 以前の自分と比較すると、より深いところまで吸収できるようになってるはずです
    - 気になることやなるほどと思った事を、コメントとして追記するといいです
9. 同じ要素を複数持つクラスたちを親クラスで束ねる
    - 共通要素を抜き出して親クラスを作り、継承することで重複コードを減らせます。共通要素を変更する時に1箇所だけ変更すれば済む点も、変更忘れが無くなるので良いですね
    - ただし、構成がきっちり固まっていない状況で継承を使うと、試行錯誤しながら進めた時に行き詰まる危険性があります
    - 継承の使い方を学習し、大丈夫そうな時は使う。最終形がイメージ出来ない時はバラバラのまま制作を進める、でいいと思います


# 【後半部】管理クラスの作成事例

スゴロクゲームの制作を想定した、サイコロを転がすプログラムの作成例を示します。
段階を踏んでサイコロとボタンのクラス、そしてそれらの管理クラスを実装します

## サイコロ１つを表現するクラス（Die）

作成手順を示します。少しずつ機能を増やす形をとりますので、動きを確認しながらコードを書いていきましょう

1. `pyxel copy_examples` で生成されるサンプルデータ内の sample.pyxres を複製。複製ファイルはプログラムを書いているファイルと同じ場所に置く。ファイル名は sample1.pyxres とした
2. `pyxel edit sample1.pyxres` でリソースファイルを開き、image0の一番下にサイコロの絵を追加
![dicepict.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/703513/4b27844f-529c-c120-afef-04cbf2f19ff7.png)

3. 『左クリックするたびに出目が変わる』機能を実装する。必要最低限のインスタンス変数•関数を決める
4. 『左クリックで回り始めて一定時間で止まる』機能を実装する [（サンプル dice1.py）](https://github.com/mg-metal/pyxel_dice/blob/main/dice1.py)
![pyxel-dice1.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/703513/1b2e0ed3-3492-1d1f-5aee-595489e12b44.gif)

5. サイコロのインスタンスを２つ用意し、合計値を表示。クリック前はサイコロを非表示とする[（サンプル dice2.py）](https://github.com/mg-metal/pyxel_dice/blob/main/dice2.py)
![pyxel-dice2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/703513/e057520f-fef3-0edb-a60f-89f8d74c3078.gif)

6. １つの変数の値を変更するだけで、サイコロの個数をコントロールできるようにする。表示位置をランダムに設定する[（サンプル dice3.py）](https://github.com/mg-metal/pyxel_dice/blob/main/dice3.py)
![pyxel-dice3.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/703513/86005fd1-0d36-231f-7a33-c14bdc39cbb2.gif)

Die単体での作成はここで一旦ストップ。次の管理クラスDiceSetの機能を決める時に、Dieに求められる機能が明確になっていくので、必要になり次第Dieの変更も実施します

## サイコロ管理クラス（DiceSet）

### 管理クラスに求める機能を書き出してみる
- サイコロの個数を気軽に変更できるようにしたい
- サイコロの振り方を選択できるようにしたい
    - いかにもサイコロを放り投げたようなバウンド動作
    - サイコロは整列させたまま徐々に値が決まっていく動作
    （振り方を選択させる前に、Dieクラスに動作プログラムの追加が必要）

いきなりDiceSetクラスを作るのではなく、Appクラスに実装したい動作を書きながら、イメージを固めていくと良いかもしれません。

### 具体的な機能として

- インスタンス生成時に引数で指示したいこと
    - 利用個数
    - サイコロの振り方
        - Dieに振り方選択のメソッド追加[（サンプル dice4.py）](https://github.com/mg-metal/pyxel_dice/blob/main/dice4.py)
        - DiceSetからDieへの呼び出し方法を決めて振り方を選択できるようにする[（サンプル dice5.py）](https://github.com/mg-metal/pyxel_dice/blob/main/dice5.py)
- 管理下にあるサイコロ全てについて、動作中か待機中かを見分けて更新(update)と描画(draw)を実施
- 合計値の表示

## 画面に設置するボタン１つを表現するクラス（GuiButton）

画面上に表示されたボタンを押すことでサイコロが振られる、そんな動きを実装する。

1. 表示は四角と文字だけの簡易的なものとする
2. ボタン押下で色が変わる
3. ボタン押下でサイコロを振る
4. インスタンスを２つ用意し、各々の振り方を指示できるようにする[（サンプル dice6.py）](https://github.com/mg-metal/pyxel_dice/blob/main/dice6.py)
## 複数のボタンをまとめるクラス（ButtonContainer）

### 管理クラスに求める機能を書き出す

- ボタン表示文字列のリストを渡すだけで、ボタンが縦に並んで生成される
- 表示位置を指定できる
- ボタンを押すと、ボタンに書かれた動作でサイコロが振られる

### 実装
ボタンの表示名が長くなるのはイマイチですが、今回はコードの簡略化を優先しました[（サンプル dice7.py）](https://github.com/mg-metal/pyxel_dice/blob/main/dice7.py)

![pyxel-dice7.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/703513/4501dc2f-bc98-bf65-bf26-b41589f6f52a.gif)


# 最後に
サンプルプログラムの完成度は低いかもしれませんが、途中で書きました通り **必要になったら作る** の精神でいきましょう。一人で組むコードであれば不都合が見つかったら直すというスタンスでいいと思います。
それから、コードが大きくなって行き詰ってしまった場合は、思い切って始めからやり直すことも検討してください。捨てコードを作る過程で色々なことが見えているはずなので、より良いプログラムが書けると思います。

---