step1 お菓子集めゲーム

お化けに捕まらずに、どれだけお菓子を集められるか

← トップページへもどる

1. 完成イメージ

男の子を矢印キーで動かし、画面に出てくるお菓子を拾い集めます。お化けが男の子を追いかけてきて、 触れられたらゲームオーバー。集めたお菓子の数がそのままスコアです。 お菓子を集めるほどお化けが増えていくので、後半になるほど逃げ場がなくなります。

SCORE 12
完成イメージ(実際の画面は 160×120 ドット。図は概念図です)

▶ 完成した作品をブラウザでみる

項目内容
画面サイズ160 × 120 ドット(step0 と同じ)
操作矢印キーで上下左右に移動。Enter でゲーム開始・再挑戦
スコア集めたお菓子の数。画面左上に表示する
終了条件お化けに触れられたらゲームオーバー(制限時間はなし)
難易度お菓子 5 個ごとにお化けが 1 体増える(最大 4 体)
画面タイトル/ゲーム/ゲームオーバーの 3 画面
作品の規模感 最終的なコードはおよそ 150〜200 行です。step0 の 2 倍以上ありますが、 10 のフェーズに分け、1 フェーズあたり 15〜25 行ずつ書き足していきます。 各フェーズの終わりで必ず一度実行し、動くことを確かめてから次に進みます。
参考にした記事 ゲームの題材は こちらの記事を参考にしています。 コードや画像は転載せず、ドット絵はリソースエディタで自分で描き、コードもフェーズごとに 一から組み立てます。仕様も、この学習に合わせて一部変えています。

2. フェーズ分割

完成までを 10 のフェーズに分けます。上から順に、少しずつ書き足していきます。 解説ページは、フェーズを進めるたびに追加していきます。

フェーズやること主に使う API・機能
1リソースエディタで 3 つのドット絵を描く
男の子・お化け・お菓子を描き、画面に出して確かめる
pyxel edit、イメージバンク、.pyxres
2画面を出して、男の子を矢印キーで動かす
step0 の復習にあたる土台づくり
pyxel.init / run / cls / blt / btn
3お菓子を 1 個置き、重なったら消して次を出す
この step の山場のひとつ
当たり判定、random、関数の戻り値
4集めた数をスコアとして画面に表示するpyxel.text、カウント用の変数
5お化けを 1 体出して、男の子を追いかけさせる相対位置による場合分け
6お化けに捕まったらゲームオーバー画面へ状態変数、シーンごとの処理の振り分け
7タイトル画面を足して 3 画面にする
もう一度あそべるようにする
画面遷移の完成、ゲームの初期化
8お菓子を集めるほどお化けを増やす(最大 4 体)リストで複数を管理、繰り返しで当たり判定
9効果音と BGM をつけるpyxel.play / playm、サウンド編集
10パッケージ化して Web で公開するpyxel package / pyxel app2html
進め方 1 フェーズごとに「考え方 → 必要な API → コード → 解説」を読み、コードを自分で入力して 実行します。想定どおり動くことを確認できてから、次のフェーズに進みます。 疑問が出たときは、その内容を備忘録に残していきます。
はじめて読むときは、この下の3. 学ぶ技術項目4. 先に押さえておきたい 2 つの土台に目を通してから、フェーズ 1 へ進んでください。

3. この step で学ぶ技術項目

step0 が「Pyxel の機能を広く浅く体験する」だったのに対し、step1 は 「ゲームとして成立させるための仕組み」を学びます。 描画やキー入力は step0 で身につけたものをそのまま使い、新しいのは次の 4 つが柱です。

分類項目登場フェーズ
当たり判定四角どうしの重なりを調べる(横と縦の 2 方向で見る)3・6
判定する範囲を絵の見た目より小さくして、遊びやすくする6
ゲーム進行状態変数による画面遷移(タイトル/ゲーム/ゲームオーバー)6・7
ゲームの初期化と、もう一度あそべるようにする処理7
敵の動き相対位置で場合分けして追いかける5
難易度を段階的に上げる(数を増やす)8
データの持ち方リストで同じものを複数まとめて管理する8
繰り返しで、全員ぶんの移動と当たり判定を回す8
画面表示スコアの表示(HUD)4
複数のドット絵を 1 つのイメージバンクに並べて使い分ける1・2
サウンド効果音の再生(pyxel.play)と BGM の使い分け9
公開パッケージ化と Web 公開(step0 の復習)10

あわせて新しく出てくる Python の概念

step0 でクラスと二次元リストを扱いました。step1 ではさらに次の 3 つが登場します。 出てきたフェーズで、必要になった理由とセットで解説します。

概念なぜ必要になるか登場フェーズ
関数が値を返す
return と真偽値)
「当たっているか」を調べる処理は、何度も別の場所から呼びたくなります。 結果を True / False で持ち帰る書き方を覚えると、 if の条件がそのまま日本語のように読めるようになります。 3
定数で「状態」を表す 画面が 3 つに増えると scene == 1 のような数字では意味が読み取れません。 SCENE_TITLE = 0 のように名前を付けて扱います。 6
リストの要素を安全に取り除く お化けやお菓子を複数持つと、「繰り返しの最中にリストから消す」場面が出てきます。 step0 の復習作品で使った「残すものだけ新しいリストに詰め直す」書き方を、あらためて整理します。 8

4. 先に押さえておきたい 2 つの土台

土台 1:当たり判定は「横」と「縦」に分けて考える

「男の子とお菓子が重なったか」を一度に考えようとすると難しく感じますが、 横だけ・縦だけに分けると、どちらもただの数直線の問題になります。 そして横も縦も重なっているときだけ、2 つの四角は本当に重なっています。

2 つの四角が重なっているか 男の子 お菓子 ← 重なっている部分 男の子の横 お菓子の横 横の範囲が重なっている 男の子の縦 お菓子の縦 縦の範囲も 重なっている 横と縦、どちらも重なっているときだけ「当たった」
横の範囲が重なり、かつ縦の範囲も重なっているときだけ「当たった」と判定する

四角の位置は左上の座標で持っているので、たとえば横の範囲は 「左端 = x」から「右端 = x + 幅」までです。 2 つの範囲が重なっている条件は、次の 2 つが同時に成り立つことだと言いかえられます。

縦もまったく同じ形になります。この 4 つの条件をすべて満たしたときが「当たり」です。 実際のコードはフェーズ 3 で書きます。

この考え方の名前 四角どうしの当たり判定は AABB(軸に沿った四角形どうしの判定)と呼ばれ、 2D ゲームでもっとも基本的な方法です。step2 の倉庫番でも、step3 のシューティングでも使います。

土台 2:画面が増えたら「今どの画面か」を変数で覚える

step0 の作品は画面が 1 つだけでした。step1 ではタイトル・ゲーム・ゲームオーバーの 3 つを 行き来します。このとき、画面ごとに別のプログラムを作るのではなく、 「今どの画面か」を覚えておく変数を 1 つ用意するのが定石です。

1 つの変数 scene が「今どの画面か」を覚えている タイトル画面 scene = 0 ゲーム画面 scene = 1 ゲームオーバー scene = 2 Enter を押す お化けに触れる Enter を押す(もう一度あそぶ) update() と draw() は、どちらも scene の値で処理を振り分ける
変数 scene の値を書き換えることが、そのまま画面の切り替えになる

毎フレーム呼ばれる update()draw() は、どちらも最初に 「今どの画面か」で処理を振り分けます。タイトル画面なら、動かすものは何もなく、 タイトル文字を描くだけ。ゲーム画面なら、男の子とお化けを動かして当たり判定をする、という具合です。

なぜ変数 1 つで足りるのか 画面はいつでもどれか 1 つだけが表示されています。同時に 2 つ表示されることはありません。 「同時に 1 つしか成り立たないもの」は、変数 1 つで表せます。 ゲームオーバーになったら scene を書き換えるだけで、次のフレームから update()draw() も自動的にゲームオーバー画面のふるまいに変わります。
画面を切り替えるときは、忘れずに片づける ゲームオーバーからタイトルに戻り、もう一度あそび始めるとき、 スコアやお化けの数が前回のまま残っていては困ります。 「ゲームを始める」処理をひとつの関数にまとめておくと、初期化のし忘れを防げます。 これはフェーズ 7 で扱います。