こんにちは!
前回こんな記事を書きました↓
本記事は『Pythonでレトロゲームを作ろう!』シリーズの”Day3”です!
Contents
【Day 3】Pyxelでレトロゲームを作ろう!
”Day2”ではマウス操作で猫を動かしました。
マウスで猫を操作してみた!#Pyxel 楽しい!! pic.twitter.com/qu4PxokpBr
— はやぶさ (@Cpp_Learning) December 8, 2018
ただ、Appクラスがcat関数を持つという、ちょっとスマートじゃない作りでした。
なので本記事では、catをクラス化して”柔軟なソースコード”にする方法を説明します。
くるるちゃんは今日もモチベーション高めです(ง •̀ω•́)ง✧
クラス化のメリット
クラス化のメリット…答えられますか?
『レゴブロックのような柔軟なソフトウェア』だよね!
(※上図のブロックは”クラス”や”インスタンス”のイメージです!)
はい!私の理想は『レゴブロックのような柔軟な開発ができるソフトウェア』です!
あと、もっと大事なのは…
『読み手のことを想ってソフトウェア設計図およびコードを書くのが正解』
という考え方です(*・ω・)ノ♪
ソフトウェア(設計)については、様々な考え方があります。上記は、私の”思想”なので「正しい/正しくない」という意見はナンセンスです!ただし「私の思想は○○だ!」という熱い意見は持っていて良いと思います(*・ω・)ノ♪
ソフトウェア設計図を作成するモチベーション
うーん。。
かっちりした現場だと「ソフトウェア設計図の作成」⇒「実装」というフローでソフトウェア開発を進めます。
チームで開発する場合、以下の理由でソフトウェア設計図を作成するモチベーションがあります。
【ソフトウェア設計図を作成するモチベーション】
- メンバーがどのクラスを担当するか?
- どのくらいの規模のソフトウェアになるか見積もる
- 抽象化/部品化/メンテナンス性/責務の事前検討
など
ただ、”くるる”ちゃんの言う通り、ソフトウェア設計図って作るの大変です…
特に趣味で個人がソフトウェアを作る場合、ソフトウェア設計図を作るモチベーションは、あまり高くないような気がしています…
本記事(本シリーズ)は「ソフトウェアの実装」をメインに説明するため「ソフトウェア設計」の説明を割愛しますが…本当は作った方が良いです。
クラスを作成するモチベーション
ん?
- ソフトウェア設計図を作らない
- 個人でのソフトウェア開発
だったとしても…
『読み手のことを想ってソフトウェア設計図およびコードを書くのが正解』というコンセプトは変わらないよ!
この読み手には”未来の自分”も含まれています!
1か月前に作ったコードを見直したとき「何してるか分からない…」では困ります!
”未来の自分”のためにも丁寧にソースコードを作りましょう!
正解!
クラスは”責務”を考えて作成する”レゴブロック”なのでソースコードの「使い勝手」や「管理」がしやすくなります!
”くるる”ちゃん良いこと言うなぁ
- 相手を想って何かを作成するということは”未来の自分”のためにもなります!
- クラス化することで、ソースコードと頭を整理でき、かつソースコードの可読性が向上します!
クラス化のメリット -具体例-
クラス化のメリットを具体例で見てみましょう!
Day2のソースコードは以下の通りでしたね↓
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
import pyxel WINDOW_H = 120 WINDOW_W = 160 CAT_H = 16 CAT_W = 16 class App: def __init__(self): self.IMG_ID0 = 0 self.IMG_ID1 = 1 # self.IMG_ID2 = 2 self.IMG_ID0_X = 60 self.IMG_ID0_Y = 65 pyxel.init(WINDOW_W, WINDOW_H, caption="Cat Game") pyxel.image(self.IMG_ID0).load(0, 0, "assets/pyxel_logo_38x16.png") pyxel.image(self.IMG_ID1).load(0, 0, "assets/cat_16x16.png") # pyxel.mouse(False) pyxel.run(self.update, self.draw) def update(self): if pyxel.btnp(pyxel.KEY_Q): pyxel.quit() def draw(self): pyxel.cls(0) pyxel.text(55, 40, "Are you Kururu?", pyxel.frame_count % 16) pyxel.blt(self.IMG_ID0_X, self.IMG_ID0_Y, self.IMG_ID0, 0, 0, 38, 16) self.cat() def cat(self): x = pyxel.mouse_x y = pyxel.mouse_y if pyxel.btn(pyxel.MOUSE_LEFT_BUTTON): pyxel.blt(x, y, self.IMG_ID1, 0, 0, -CAT_W, CAT_H, 5) else: pyxel.blt(x, y, self.IMG_ID1, 0, 0, CAT_W, CAT_H, 5) App() |
このコードだとAppクラスがcatの情報(操作)を持っていることになります。
今は、cat関連の属性(プロパティ)を持っていないけど…
catの情報…例えば、座標(位置)の情報を属性として持ちたいときは、どんな風にコードを変更すれば良いかな?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
class App: def __init__(self): self.IMG_ID0 = 0 self.IMG_ID1 = 1 # self.IMG_ID2 = 2 self.IMG_ID0_X = 60 self.IMG_ID0_Y = 65 self.cat_x = 0 self.cat_y = 0 pyxel.init(WINDOW_W, WINDOW_H, caption="Cat Game") pyxel.image(self.IMG_ID0).load(0, 0, "assets/pyxel_logo_38x16.png") pyxel.image(self.IMG_ID1).load(0, 0, "assets/cat_16x16.png") # pyxel.mouse(False) pyxel.run(self.update, self.draw) |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
class App: def __init__(self): self.IMG_ID0 = 0 self.IMG_ID1 = 1 self.IMG_ID2 = 2 self.IMG_ID0_X = 60 self.IMG_ID0_Y = 65 self.cat_x = 0 self.cat_y = 0 self.cat_x2 = 0 self.cat_y2 = 0 pyxel.init(WINDOW_W, WINDOW_H, caption="Cat Game") pyxel.image(self.IMG_ID0).load(0, 0, "assets/pyxel_logo_38x16.png") pyxel.image(self.IMG_ID1).load(0, 0, "assets/cat_16x16.png") pyxel.image(self.IMG_ID2).load(0, 0, "assets/cat2_16x16.png") # pyxel.mouse(False) pyxel.run(self.update, self.draw) |
泣きそうな”くるる”ちゃんが愛おしい!
クラスの責務を明確化
Appもクラスなので責務があります。
どんな責務を持たせるかは開発者次第ですが…私なら、こんな感じ↓
【Appの責務】
ゲーム画面の管理
【具体的な処理】
- ゲーム画面の初期設定
- ゲーム画面の更新
- ゲーム画面の描画処理
- (インターフェース処理)
さっき”くるる”ちゃんが書いたコードだと、Appの責務に『猫の管理』も追加した感じだね!
まぁ猫が2匹だけなら良いけど、猫の数が増えたり、猫以外のキャラまでAppが管理すると…間違いなく『ファット(太った)クラス』になります。
さっきまで泣きそうだったのに、もうケロッとしてる…可愛い(*・ω・)ノ♪
『ファット(太った)クラス』は管理が難しいし、Appの責務を考えれば『猫情報』まで持たせるのは少し不自然です。
「クラスの責務」と「追記コード」のマッチングを考えてみて下さい。不自然だと感じたら『ファット(太った)クラス』になっている可能性があります。ダイエットさせましょう!
猫クラス設計
おぉ~
クラス作成のポイントは以下の通りです。
『抽象化/部品化/メンテナンス性/責務』を考えてクラス化する
今回は、こんな感じで猫クラスを設計してみました↓
1 2 3 4 5 6 7 8 9 10 11 12 13 |
class Vec2: def __init__(self, x, y): self.x = x self.y = y class cat: def __init__(self, img_id): self.pos = Vec2(0, 0) self.img_cat = img_id def update(self, x, y): self.pos.x = x self.pos.y = y |
現時点では、猫が動くだけなので属性としては、「座標の情報」と「猫画像のID情報」だけあれば十分です。
操作(関数)は「座標情報の更新」のみです。
正解!
猫のクラス化によるメリット
猫をクラス化したことで「本当にソースコードが扱いやすくなったのか?」を確認してみます。
クラスは設計図なので、実体化(インスタンス生成)して初めてゲーム画面に登場します。
※フクロウの絵になってるけど、猫に置き換えて考えて下さい…
実体化(インスタンス生成)のコードは、こんな感じです↓
1 2 3 4 5 6 |
pyxel.image(self.IMG_ID1).load(0, 0, "assets/cat_16x16.png") pyxel.image(self.IMG_ID2).load(0, 0, "assets/cat2_16x16.png") self.cat_Ichiro = cat(self.IMG_ID1) self.cat_Jiro = cat(self.IMG_ID1) self.cat_Saburo = cat(self.IMG_ID2) |
猫の名前は”イチロー”・”ジロー”・”サブロー”にしました。
”イチロー”・”ジロー”は見た目が同じ…つまり同じ画像を使って実体化させます!
”サブロー”は兄とは違う画像から実体化させます!
また、”イチロー”・”ジロー”・”サブロー”の座標は以下のように表現できます。
1 2 3 4 5 6 7 8 |
self.cat_Ichiro.pos.x self.cat_Ichiro.pos.y self.cat_Jiro.pos.x self.cat_Jiro.pos.y self.cat_Saburo.pos.x self.cat_Saburo.pos.y |
どの猫の座標なのか明確なので可読性(メンテナンス性)が高いです!
クラス化のメリットが分かってくれて嬉しいです!
【クラス化するメリット】
- 各インスタンスの情報はクラス(設計図)を見るだけで把握できる
- クラスは共通部品(レゴブロック)なので、コピペで簡単に移植できる
- クラスを設計するとき、責務を考えるため、コードと頭を整理できる
おぉ!!!
(そこは素直に”はやぶさ先生”すげーでも良かったと思うなぁ…)
最終的に完成したソースコード
以上を踏まえて、最終的に完成したソースコードは以下の通りです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
import pyxel WINDOW_H = 120 WINDOW_W = 160 CAT_H = 16 CAT_W = 16 class Vec2: def __init__(self, x, y): self.x = x self.y = y class cat: def __init__(self, img_id): self.pos = Vec2(0, 0) self.img_cat = img_id def update(self, x, y): self.pos.x = x self.pos.y = y class App: def __init__(self): self.IMG_ID0_X = 60 self.IMG_ID0_Y = 65 self.IMG_ID0 = 0 self.IMG_ID1 = 1 # self.IMG_ID2 = 2 pyxel.init(WINDOW_W, WINDOW_H, caption="Hello Pyxel") pyxel.image(self.IMG_ID0).load(0, 0, "assets/pyxel_logo_38x16.png") pyxel.image(self.IMG_ID1).load(0, 0, "assets/cat_16x16.png") self.mcat = cat(self.IMG_ID1) # pyxel.mouse(True) pyxel.run(self.update, self.draw) def update(self): if pyxel.btnp(pyxel.KEY_Q): pyxel.quit() # ====== ctrl cat ====== self.mcat.update(pyxel.mouse_x, pyxel.mouse_y) def draw(self): pyxel.cls(0) pyxel.text(55, 40, "Are you Kururu?", pyxel.frame_count % 16) pyxel.blt(self.IMG_ID0_X, self.IMG_ID0_Y, self.IMG_ID0, 0, 0, 38, 16) pyxel.blt(self.mcat.pos.x, self.mcat.pos.y, self.mcat.img_cat, 0, 0, -CAT_W, CAT_H, 5) App() |
ざっくりとしたフローは以下の通り
- 画像(IMG_ID1)の猫を実体化(インスタンス生成):34行目
- 猫の座標をマウスの座標に更新:44行目
- 猫を描画:51行目
①は最初の一回だけ、②と③はアプリを終了するまで繰り返します。
クラス化のメリットで”イチロー”・”ジロー”・”サブロー”の3匹の猫を例に説明したけど、このコードでは1匹の猫しか生成していません…
今回は、Day2のcatをクラス化するのが目的だったので↓
Day2と↑のデモで見た目に違いはないけど、それでもクラス化するメリットは伝わったかな?
うん!うん!うん!うん!と何度も頷く”くるる”ちゃんが今日も可愛い(*・ω・)ノ♪
「1か月後に見直しても問題ないと思えるソースコード」や「他人がスッと理解できるソースコード」は、品質の高いソースコードです。『クラスを使えば品質が高くなる!』というわけではありませんが、クラスを上手に使うことで品質は向上できますよ(*・ω・)ノ♪
おわりに
『Pythonでレトロゲームを作ろう!Day 3-クラス化-』について説明しました。
ゲーム開発(pyxelの使い方)を題材にして、楽しくプログラミングの勉強ができると良いなーとか考えながら、シリーズ記事を書いています。
そして、今回は説明が難しい”クラス”の話でした。
という人やフクロウが本記事を読んで、少しでも”頭がスッキリした”となっていると、大変嬉しいです!
クラスの説明って難しいね!笑
あまり小難しくなり過ぎずに最後まで楽しく読めるように配慮したつもりだけど…
楽しんでくれたかな?次回もお楽し…
マウスの移動方向に応じて猫の向きが変化するように修正したよ↓
じゃあ、Day4では”ベクトル”について勉強しようねー
では改めて…次回もお楽しみに~