はじめに
この記事は、はじめてゲームプログラミングで作った落ち物パズルのソフトウエア構造説明です。
製品が発売されてから既に2年使く経っています。なので初期から続けているプレイヤーはプログラムコードを読めば、何をやってるかおおよそ理解できるレベルまで習熟していることでしょう。ですが厳しいリソース制限のもとで作りこまれたゲームはスパゲッティコード化している事が常であり、プログラムの構造解説1がないと読む気にすらなれません。困ったことに今回作ったゲームもその類です。
こういった解説系の読み物が増えると良いなーとコミュニティーに期待しつつ、だったら自分から始めろということで作者解説を記事にしておきます。
オリジナルゲームの紹介
今回勝手移植の素材にしたゲームの紹介です。日本語タイトルはコズモギャング・ザ・パズル、海外向けにキャラクターをパックマンに置き換えPac-Attackというタイトルでリリースされています。
個人的にはテトリス、ぷよぷよに並ぶ3台落ち物ゲームの一つじゃないかしらと思ってますが、残念ながら続編は作られませんでした…
コンセプト
物体の移動開始から静止までのトータル時間がフリースライドよりも短い手段(以降、高速カーソルと呼びます)を用いて、ゲーム体験の向上を図るのが目的です。具体的にはゲーム内で用いるフリースライドを全て高速カーソルに置きかえて、操作対象がキビキビ動く気持ち良いものを作ろうという事です。
今回パズルゲームコンテスト2が開催されたのが一番の理由ですが、今まで使う機会の無かった高速カーソルを何処かで使うチャンスを伺っていた、というのもあります。
STGやACTゲームのように自機が完全に静止しなくても気にならないゲームならフリースライドで充分です。しかしグリッドの中に正確にブロックを配置するような場合は少々のズレが非常に悪目立ちしてしまいます。
これを解決するには正確にグリッドの中心にある事を保証する、言い換えると移動する対象物が目的座標で静止するまで待つ必要があります。
フリースライドと高速カーソルを1m及び10m移動させてから静止するまでのフレーム数を計測した実験動画があるのでご覧ください。上段がフリースライド、下段が高速カーソル(動かせるモノ)です。
距離にして1mの移動開始から静止までをフリースライドは14フレーム、高速カーソルは2フレームで完了します。その差は7倍です。早いですね。使わない手はありません。
高速カーソルとは
全体構造の説明の前に、まずは仕組みを支える基礎技術、高速カーソル3の説明です。簡単のため下図ではXスライド相当部分を高速カーソルに置き換えています。
高速カーソルが8ノードン、スライドは3ノードン使用している事からも分かる通り高速カーソルを使うとノードンコストは高くなりますが、ゲーム体験をより良くするためのコストと割り切ってください。ちなみにX,Zの2軸対応させた高速カーソルは11ノードン必要になります。
高速カーソルに使われる定数60は少し説明が必要です。恐らくは経験的に得られた一番高いポテンシャルを発揮するマジックナンバーです。60より少ないと移動速度が徐々に遅くなります。大きいと今度は速すぎて目的座標をオーバーランしてしまい、安定化までの時間が長くなります。
高速カーソルに他のモノを接続させると重量が増えます。すると重量の影響を受けて徐々に遅くなります。センサー類の重量は微々たるものなので無視してもよいでしょう。また他のモノをカーソルの中央に接続した場合は意図したとおりに動きますが、バランスを崩すような接続を行うと全体が回転してしまいます。
下の動画は3ブロック分の大きさをもつ動かせるモノの中央に、直方体を接続して動かした結果です。
回路を組んでしまえば、あとはスライド(フリースライド)と同じように扱えます。静止判定に用いる位置センサーが既に回路内にあるので、目的座標(図ではカウンター)の値と同じになれば静止してる事を検知できます。
全体俯瞰図
編集画面内のどこあたりにどの機能ブロックを配置してあるかの俯瞰図4です。黒い枠が高速カーソル、灰色はその他の特徴的な機能ブロックの位置を示します。
高速カーソルはタイトル画面表示からゲーム開始までの間にそれぞれの初期位置へ自動的に移動します。編集画面では何となく適当な場所に配置しているように見えますが、コピペし易さミスの混入しずらい制作手法を採用したためです。
整列器のX座標はゲーム中に初期化(=変更)されないため編集画面の配置座標と同じになります。階段状にすらして配置してあるのはこのためです。
はじプロをお持ちの方はゲームID G-002-WD2-XLD を参照してください。タイトル名は pico PAC-ATTACK です。
ステートマシン
はじプロでは言語の特性上、個々の機能ブロックやその塊は「AとBとCを満たしたら〇〇をする」という実装に落ち着きます。これが自然とステートマシン5になっていきます。
以下のステートマシンが個別に動作しながらも全体としてゲームを成立させています。
整列器
起動されると画面内のブロックの整列を行います。整列が完了したあと、ブロック破壊を起動します。1周で整列が完了するので、次のシグナルが入力されるまで初期位置で待機しています。整列器はカウンターの値が0の場合InActive、非0の場合Activeとします。
落下ブロック群
整列器がInActiveの場合、難易度に応じた速度で移動及び落下を行います。整列器がActiveな場合は移動しません。
ブロック1,2,3が接地した場合、ブロック設置を起動します。
初期化シグナルを受けた時、画面上部へ復帰します。
ブロック設置
起動された場合ブロック1,2,3の順に移動して該当するブロック(緑ブロックは直方体、ゴーストは円柱)を固着します。パックマンが選出されている場合はそのブロックの固着をスキップします。
パックマンが選出されている場合、ブロックの固着完了後にパックマンを起動します。選出されていなければ整列器を起動します。最後に軸(=落下ブロック群)のZ座標を初期化するシグナルを送って起動状態を解除します。
起動状態でない場合は、落下ブロック群の軸と同じ位置に居続けます。
パックマン
起動された場合はブロック1の座標をコピーし、自身の基準座標とします。これ以降落下ブロック群が初期化(画面上部へ移動)しても独自に動作するようになります。
自身の移動アルゴリズムに従い1ブロックずつ移動を行います。移動先ブロックではゴースト(円柱)を破壊します。Z座標が変わらないままX-面とX+面の壁に接触すると、移動アルゴリズムを終了します。
最後に整列器を起動します。
起動状態でない場合は、落下ブロック群のブロック1と同じ位置に居続けます。
ブロック破壊
起動された場合、各行のブロック数を数え6つ揃っている場合は対応する行のブロックを破壊します。
ブロック破壊が行われた場合は、整列器を起動します。ブロックが揃っていなかった場合はなにもせず終了します。
ゴースト全滅
破壊されたゴーストの数を累積し、規定値を超えていた場合に画面内のゴーストを消去(/null/devワープ)します。
消去後に整列器を起動します。また規定値を決定するカウンターを+1します。規定値はカウンターの値の3乗とし、ゲーム進行に伴い必要規定数が増加する事とします。カウンターの初期値が2なので(8, 27, 64, 125,...)と規定値が増加します。
高速カーソルの適用箇所
今回作ったゲームでは15個の高速カーソルを用いて主要な機能ブロックを実装しています。内訳は以下の通り。
名前 | 数 | 速度 | 用途 |
---|---|---|---|
整列器-入口 | 2 | 60 | 固着したブロックの整列、ワープ入口を使って整列対象を吸い込む |
整列器-出口 | 6 | 58 | 固着したブロックの整列、ワープ出口を使って整列対象を再固着する |
軸 | 1 | 8~40, 58 | 落下ブロックの中心軸、左右移動と落下の移動に使用する |
ブロック1,2,3 | 3 | 60 | 落下中のテクスチャ表示とブロックの回転に使用する |
L字の空き | 1 | 60 | 落下ブロック回転の可否を判定するセンサー |
ブロック設置 | 1 | 60 | 落下ブロックが接地した後、ブロックとゴーストをモノ発射(100)で固着する |
パックマン | 1 | 58 | ブロックの上を水が流れるように移動しゴーストを破壊する |
速度58とあるのは本来は60にしたかった…のですが、あまりに早すぎるとセンサーの検知範囲が拡大してしまいます。1ブロック以上先のブロックまで誤検知してしまうのを回避するために少し速度を落としています。
整列器
以下は実験中の動画なので上下が逆転していますが、やっている事の説明には充分耐えるでしょう。左側がフリースライドによる実装、右が高速カーソルによる実装の速度比較です。
整列器-入口
ワープ対象物を見つけるまでサーチして、見つけたらワープさせるという機能を持ちます。
高速カーソルの中心とX-,X+に、触ってるセンサーとワープ入口を合計3つ接続しています(イメージしやすいよう先の図では横長の長方形で表記しています)。ノードン削減を目的として3つの整列器-入口を1つの高速カーソルに集約しています。
整列器-出口
空白ブロックを見つけるまで移動してみつけたらそこに留まり、ワープ対象物が飛んできたら一歩進む機能です。
落下ブロック部
軸
落下ブロック全体の左右移動及び落下速度を担当します。この高速カーソルだけが可変の速度で動いてます。速度を変えるには定数60の値を変える事で実現できます。
難易度上昇に伴う自然落下速度は人間がギリギリ対応できる40、十字キー下で高速落下させる時のみ58にしています。
軸を使わず落下ブロックだけで移動と回転を実現しようとすると、ブロックの落下速度と回転速度が同じになってしまいます。これではゲーム開始直後の落下速度(ゆっくり)と同じ速度で(ゆっくり)回転してしまうので、とても微妙な感じになります。なので軸は移動だけを担当しています。
ゆっくり落下する軸を最高速で追従するブロック1,2,3の構図をイメージしてください。
ブロック1,2,3
テクスチャの表示と位置の変更、つまり回転を担当します。また壁や固着したブロックとの接触判定を行います。
L字の空き
ブロックはL字型をしており1か所空いた空間があります。ここが固着したブロックに引っ掛かるような状態で落下と回転を同時に実行すると、「次のグリッドで固着ブロックに落下ブロック群がめり込む」という事態が生じます。
これを回避するために接触判定を行い、何かに接触していたら回転操作を無効化します。
ブロック設置
ステートマシンの説明に同じです。
パックマン
パックマンです(雑)。
ここまでの構造を理解したら後はスムーズにコードを読めると思います。
おわりに
高速カーソルや静止判定に留まらず、はじプロで使える様々な基礎技術やテクニック集をまとめた同人誌を作っています。よろしければこちらもご覧ください。(隙あらば宣伝)