4.とりあえず動かそう!Pyxelでゲーム作り+ちょこっとPython:ゲームの説明
第4回はミニゲームのプログラムの説明をします。文法ではなく、何をしているかを書いています。
クリックするとジャンプして爆弾をよけるミニゲーム。bomb-jump
ChatGPTにコードを貼り付けて、解説してもらいました。
VS Code などで見たい方は GitHub のリンクを貼っておきます。
GitHubのリンク
https://github.com/programma-py/first-pyxel
ワンクリックで遊ぶPyxelミニゲームのしくみ
このゲームでは、プレイヤーが自動で右へ進み、爆弾が左へ流れてきます。クリックまたはスペースキーでジャンプし、爆弾をよけます。ぶつかると爆発してゲームオーバーになります。クリックひとつで遊べるゲームです。
最初のクリックでゲーム開始
ゲーム中のクリックでジャンプ
爆弾に当たるとゲームオーバー
ゲームオーバー後のクリックでリスタート
このプログラムは、大きく分けると次の部品でできています。
画像データの定義
↓
音の設定
↓
Player クラス
↓
Bomb クラス
↓
App クラス
↓
App() でゲーム開始1. 画像データを辞書でまとめる
最初に、キャラクターの画像データを定義しています。
PUTIT_GREEN = { # キャラクターの画像データ
"sprites": {
"IDLE": [(0, 0, 16, 15, 16, 2), # 立ち止まっている状態
(0, 16, 16, 15, 16, 2)],
"JUMP": [(0, 0, 0, 15, 16, 2), # ジャンプしている状態
(0, 16, 0, 15, 16, 2)]
},
"state_to_sprite": { # 状態と画像データの対応
"idle": "IDLE",
"run": "JUMP",
"jump": "JUMP",
"down": "IDLE"
}
}Pyxelでは、画像を表示するときに pyxel.blt() を使います。
画像そのものを直接指定するのではなく、イメージバンクのどこにある画像を使うかを数字で指定します。
(イメージバンク番号, X座標, Y座標, 幅, 高さ, 透明色)(0, 0, 16, 15, 16, 2)これは、
イメージバンク 0 番、X座標 0、Y座標 16、幅 15、高さ 16、透明色 2
という意味です。
数字をそのままプログラムの中に書き続けると、あとで見たときに何の画像かわからなくなります。
そこで、画像データを `PUTIT_GREEN` という辞書にまとめています。
透明色について
透明色(とうめいしょく)とは、「この色は表示しない」と指定するための色です。透明色に指定した部分は画面に描かれず、後ろにある背景や画像がそのまま見えます。
状態と画像を対応させる
プレイヤーには、いくつかの状態があります。
idle:まだ動いていない
run:走っている
jump:ジャンプしている
down :倒れている、ゲームオーバー
という意味です。そして、状態に合わせて使う画像を変えています。
"state_to_sprite": {
"idle": "IDLE",
"run": "JUMP",
"jump": "JUMP",
"down": "IDLE"
}たとえば、プレイヤーの状態が "idle" のときは、画像グループ "IDLE" を使います。
プレイヤーの状態が "jump" のときは、画像グループ "JUMP" を使います。
"run" のときも "JUMP" の画像を使いまわしています。
このように、「今の状態がこれなら、この画像を使う」という対応表を作っておくと、プログラムが読みやすくなります。
2.効果音の設定
def setup_sounds():
pyxel.sound(0).set("c3", "t", "7", "n", 4) # 歩く音
pyxel.sound(1).set("c3g3c4", "p", "7", "n", 12) # ジャンプ音
pyxel.sound(2).set("c2c1c2c1", "n", "7", "n", 12) # 爆発音ここでは、ゲーム中に鳴らす音を3つ作っています。あとで、次のようにして音を鳴らします。
pyxel.play(0, 0)この場合、最初の 0 はチャンネル番号、次の 0 はサウンド番号です。
3. Playerクラス
class Player:クラスは、ざっくり言うと、キャラクターを作るための設計図です。
プレイヤーは、次のような情報を持っています。
x 横の位置
y 縦の位置
vy 縦方向の速度
state 現在の状態
width 幅
height 高さ
base_y 地面に立っているときのY座標resetで最初の状態に戻す
def reset(self):
self.x = 0
self.y = self.base_y
self.vy = 0
self.state = "idle"`reset()` は、プレイヤーを最初の状態に戻す命令です。
最初は、X座標は0、Y座標は地面の上、縦の速さは0、状態は `"idle"` になります。
ゲームを最初から始めるときや、リスタートするときに使います。
updateでプレイヤーを動かす
`update()` は、ゲーム中に毎フレーム呼ばれる処理です。
def update(self):ここで、プレイヤーの状態に合わせて動きを変えています。
idle状態
if self.state == "idle":
if pyxel.btnp(pyxel.MOUSE_BUTTON_LEFT):
self.state = "run"
return`idle` は、まだゲームが始まっていない状態です。
クリックされたら、状態を `"run"` に変えます。
self.state = "run"これでゲームが始まります。
run状態
if self.state == "run":
self.x += 0.5`run` のときは、少しずつ右に進みます。
self.x += 0.5これは、X座標を少しずつ増やしているということです。
X座標が増えると、キャラクターは右に動きます。
if self.x > pyxel.width - self.width:
self.x = 0画面の右端まで行ったら、左端に戻します。
クリックしたらジャンプ
if pyxel.btnp(pyxel.MOUSE_BUTTON_LEFT):
self.vy = -6
self.state = "jump"ゲーム中にクリックすると、ジャンプします。
ここで大事なのは、
self.vy = -6です。
Pyxelでは、上に行くほどY座標が小さくなります。
なので、上にジャンプさせたいときは、Y方向の速さをマイナスにします。
jump状態
if self.state == "jump":
self.x += 0.5
self.y += self.vy
self.vy += 0.4ジャンプ中も、少しずつ右に進みます。
self.y += self.vyで、縦方向に動かします。
self.vy += 0.4で、だんだん下向きの力を加えます。これは、重力のようなものです。
最初は `vy = -6` なので上に進みます。でも、毎回 `0.4` ずつ増えるので、だんだん上昇が弱くなり、やがて落ちてきます。
地面に着いたらrunに戻る
if self.y >= self.base_y:
self.y = self.base_y
self.vy = 0
self.state = "run"ジャンプして落ちてきたら、地面より下に行かないようにします。
self.y = self.base_yで、地面の上にぴったり戻します。
そして、状態を `"run"` に戻します。
drawでプレイヤーを描く
def draw(self):`draw()` は、画面に絵を表示する処理です。
sprite_name = PUTIT_GREEN["state_to_sprite"][self.state]
frames = PUTIT_GREEN["sprites"][sprite_name]ここでは、今の状態を見る、その状態に合う画像名を調べる、実際の画像データを取り出す、ということをしています。
たとえば、状態が `"idle"` なら、
PUTIT_GREEN["state_to_sprite"]["idle"]で `"IDLE"` が取り出されます。
PUTIT_GREEN["sprites"]["IDLE"]IDLE用の画像データを取り出します。
アニメーションのしくみ
frame = (pyxel.frame_count // 12) % len(frames)これは、画像を順番に切り替えてアニメーションさせるための計算です。
`pyxel.frame_count` は、ゲームが始まってから何フレーム経ったかを表します。
// 12 としているので、12フレームごとに画像が切り替わります。
% len(frames) は、画像の枚数を超えたら最初に戻すための計算です。
たとえば画像が2枚なら、`0, 1, 0, 1, 0, 1` のように繰り返されます。
// は、あまりのある割り算の商の部分 13 // 3 = 4
% は、あまりのある割り算のあまりの部分 13 % 3 = 1
4. Bombクラス
爆弾を表すクラスが `Bomb` です。
class Bomb:爆弾にも `self.state` があります。
爆弾の状態は、次の2つです。
wait 爆発前、explode 爆発中
爆弾を複数出すしくみ
このゲームでは、爆弾が3つ出ます。
self.bomb1 = Bomb()
self.bomb2 = Bomb(self.bomb1)
self.bomb3 = Bomb(self.bomb2)1つ目の爆弾は、普通に作ります。
self.bomb1 = Bomb()2つ目の爆弾は、1つ目の爆弾を目印にして作ります。
self.bomb2 = Bomb(self.bomb1)3つ目の爆弾は、2つ目の爆弾を目印にして作ります。
self.bomb3 = Bomb(self.bomb2)この目印になる爆弾を、プログラムでは `leader` と呼んでいます。
def __init__(self, leader=None):
self.leader = leader`leader` がない爆弾は、最初の爆弾です。
`leader` がある爆弾は、前の爆弾から少し離れた場所に置きます。
爆弾同士の間隔をランダムにする
self.spawn_min_x = 70
self.spawn_max_x = 80これは、爆弾どうしの間隔です。
self.x = self.leader.x + pyxel.rndi(self.spawn_min_x, self.spawn_max_x)この部分で、前の爆弾より70から80ピクセル右に置いています。
`pyxel.rndi(70, 80)` は、70から80までの整数をランダムに選びます。
そのため、爆弾の間隔が毎回少し変わります。ぴったり同じ間隔で出るより、ゲームらしくなります。
爆弾を左に動かす
self.x -= 1爆弾は左に動きます。
プレイヤーが右に進んでいるようにも見えますが、実際には爆弾も左に流れています。
横スクロールゲームでは、このように、プレイヤーを動かす、敵や背景を動かす、の両方を使って走っている感じを作ることができます。
爆弾が画面外に出たら戻す
if self.x < 0:
if self.leader is None:
self.x = 210
else:
self.x = self.leader.x + pyxel.rndi(self.spawn_min_x, self.spawn_max_x)
return True爆弾が画面の左に出ていったら、右側に戻します。
1つ目の爆弾は、
self.x = 210で、かなり右のほうに戻します。
2つ目、3つ目の爆弾は、前の爆弾から70〜80ピクセル離れた位置に置きます。
return Trueを返します。これは、「この爆弾は無事によけられました」という合図として使っています。
スコアを増やす
Appクラスの中で、爆弾の更新をしています。
if enemy.update():
self.score += 1`enemy.update()` が `True` を返したら、スコアを1増やします。
つまり、爆弾が画面外に出たら、よけたことになって点数が入ります。
if self.score > self.high_score:
self.high_score = self.score今のスコアが最高得点を超えたら、ハイスコアを更新します。
5. 当たり判定
ゲームでとても大事なのが、当たり判定です。
def check_collision(self, player, enemy):
if (player.x < enemy.x + enemy.width and
enemy.x < player.x + player.width and
layer.y < enemy.y + enemy.height and
enemy.y < player.y + player.height):
return True
else:
return Falseこれは、プレイヤーと爆弾の四角形が重なっているかを調べています。
キャラクターの画像を、四角い箱だと考えます。
プレイヤーにも、爆弾にも、左、右、上、下があります。
この2つの四角形が重なったら、当たったことにします。
少し難しく見えますが、考え方はシンプルです。
「プレイヤーの四角と、爆弾の四角が重なっているか?」を調べているだけです。
if self.check_collision(self.player, enemy):
enemy.explode()
self.player.state = "down"プレイヤーと爆弾が当たったら、爆弾を爆発状態にします。
enemy.explode()そして、プレイヤーの状態を `"down"` にします。
self.player.state = "down"プレイヤーが `"down"` になると、ゲームオーバーになります。
6. Appクラスはゲーム全体の管理役
class App:`App` クラスは、ゲーム全体を管理しています。
主な役割は次のとおりです。
Pyxelの初期化
画像データの読み込み
プレイヤーを作る
爆弾を作る
スコアを管理する
毎フレームの更新
画面の描画
Pyxelの初期化
pyxel.init(160, 120)
pyxel.load("my_resource.pyxres")ここで、Pyxelの画面を作っています。
pyxel.init(160, 120)は、横160ピクセル、縦120ピクセルの画面を作るという意味です。
pyxel.load("my_resource.pyxres")は、画像などが入ったリソースファイルを読み込んでいます。
ゲームループ
pyxel.run(self.update, self.draw)Pyxelでは、この1行でゲームが動き続けます。
self.updateは、ゲームの中身を更新する処理です。
self.drawは、画面に描く処理です。
Pyxelは、updateする、drawする、updateする、drawする、という流れを何度も繰り返します。
これをゲームループといいます。
ゲーム全体の状態の流れ
このゲームの流れは、次のようになっています。
タイトル状態
↓ クリック
走る
↓ クリック
ジャンプ
↓ 地面に着く
走る
↓ 爆弾に当たる
ゲームオーバー
↓ クリック
リスタートプログラムでは、この流れを `state` という変数で管理しています。
`state` を使うと、「今は何をしているところか?」がわかりやすくなります。
文字を描く処理
def text_draw(self, text, x, y, color1, color2):
pyxel.rect(x - 2, y - 2, len(text) * 4 + 4, 8, color2)
pyxel.text(x, y, text, color1)この関数は、文字の後ろに四角を描いてから、文字を表示しています。
pyxel.rect(...)で背景の四角を描きます。
pyxel.text(...)で文字を描きます。
文字だけだと背景にまぎれて読みにくいことがあります。
そこで、文字の後ろに四角を置いて、読みやすくしています。
drawで画面全体を描く
def draw(self):
pyxel.cls(1)`pyxel.cls(1)` は、画面を色番号1で塗りつぶします。
そのあとで、
self.player.draw()でプレイヤーを描きます。
ゲーム中やゲームオーバー中なら、爆弾も描きます。
if self.player.state in ("run", "jump", "down"):
for enemy in self.enemies:
enemy.draw()`for enemy in self.enemies:` は、爆弾リストの中にある爆弾を1つずつ取り出す処理です。
このゲームでは、爆弾が3つあります。
self.enemies = [self.bomb1, self.bomb2, self.bomb3]このリストに入れておくことで、3つの爆弾をまとめて処理できます。
ゲーム開始前とゲーム中で表示を変える
ゲーム開始前やゲームオーバー中は、次の文字を表示します。
CLICK OR TAP TO STARTゲーム中は、次の文字を表示します。
CLICK OR TAP TO JUMPプレイヤーの状態によって、表示するメッセージを変えています。
if self.player.state == "idle" or self.player.state == "down":これは、「プレイヤーが待機中、またはゲームオーバー中なら」という意味です。
elif self.player.state in ("run", "jump"):これは、「プレイヤーが走っている、またはジャンプしているなら」という意味です。
7. このプログラムで学べること
このプログラムには、ゲーム作りの基本がたくさん入っています。
特に大事なのは、次の5つです。
状態で動きを分ける
self.state = "idle"
self.state = "run"
self.state = "jump"
self.state = "down"ゲームでは、キャラクターが今どんな状態かを管理することが大切です。
クラスでキャラクターをまとめる
Player` クラスと `Bomb` クラスを作ることで、プレイヤーと爆弾の情報を整理しています。
辞書で画像データを管理する
画像の座標を辞書にまとめることで、プログラムが読みやすくなります。
updateとdrawを分ける
`update()` は動きの計算。
`draw()` は画面に描く処理。
この2つを分けるのは、ゲームプログラミングの基本です。
当たり判定でゲームらしくする
プレイヤーと爆弾が重なったらゲームオーバー。
これによって、ゲームにルールが生まれます。
まとめ
このプログラムは、見た目は小さなワンクリックゲームですが、中にはゲーム作りの基本がたくさん入っています。
キャラクターの状態管理
スプライト画像の切り替え
ジャンプの動き
敵キャラクターの出現
スコア計算
当たり判定
ゲームオーバーとリスタート
クリックひとつで遊べるシンプルなゲームでも、プログラムの中ではいろいろな仕組みが動いています。まずはこの形を理解しておくと、次に、
背景をスクロールさせる
爆弾の数を増やす
スピードをだんだん速くする
アイテムを出す
といった改造もしやすくなります。
関連ページ・書籍
とりあえず動かそう!Pyxelでゲーム作り+ちょこっとPython シリーズ
第1回 はじめの一歩
第2回 何かを表示してみよう
第3回 ミニゲーム
第4回 ゲームの説明 ← このページ
公式レファレンス
【公式】準備ゼロで使えるPyxel開発環境『Pyxel Code Maker』
Pyxel Code Maker マニュアル
Pyxel Editor マニュアル
Pyxel ユーザーガイド
Pyxel API Reference
関連書籍
