ジャンル別ゲームの作り方 (落ち物パズル)
今回は落ち物パズルゲームの作り方を解説します。
■落ちものパズル
落ち物パズルとは、「テトリス」「ぷよぷよ」「ズーキーパー」「ツムツム」のようなゲームを表すジャンルです。落ちものパズルは、おおよそ以下のルールとなっています。
1. 消去可能なブロックが上から落下してくる
2. 一定ルール (同じ色をつなげるなど) でブロックの消去ができる
3. 1〜2を繰り返して出現位置がブロックでふさがったり、一定時間内に消せないとゲームオーバー
通常、ブロックは下に遮るものがないと重力的な落下を行います。それによりフィールド上に配置されたブロックを瞬時に消すための思考力・判断力を求められるアクション性の高いパズルゲームとなっています。
(※ゲームによっては、非リアルタイムであったり、消去できないブロックが存在することもあります)
▼消去判定の作り方
落ち物パズルで重要なのが、ブロックをどのようにすると消すことができるのか、というルールの実装です。
このルールを実装するには、ゲームのフィールドに配置されているブロックの情報を2次元の配列で格納すると都合が良いです。
(後述しますが、この方法はツムツムを除きます)
例えば、ズーキーパーであれば、8x8のフィールドなので、8x8の二次元配列を用意します。
int blockList[8][8] = {
{1,2,4,6,5,2,3,7},
{1,3,3,1,5,1,3,4},
{4,2,5,1,1,6,7,1},
{2,4,2,3,1,2,2,1},
{7,6,5,4,2,1,1,5},
{3,5,2,1,7,7,2,5},
{7,5,3,1,7,2,5,2},
{3,1,3,7,1,3,4,6},
};
上記のデータは、ブロックが以下のように並んでいるイメージです。
なぜ、2次元配列でブロックの管理をするのかというと、ゲーム画面での表示は、ブロックのサイズの間隔で配置されるからです。
例えば、ブロックの画面上のサイズが 32x32 ピクセルとすると、左上から、(0, 0), (32, 0), (64, 0), ... という座標になります。
この座標系では隣り合っているブロックを取得するには、少し面倒です。
そこで、2次元配列の座標系でブロックデータを扱うと、(0, 0), (1, 0), (2, 0), ... というように、データへアクセスすることができてわかりやすくなって、プログラム上で処理の都合が良くなります。
ある程度プログラムをやるとわかることですが、「見た目の表示(=ユーザーインターフェース)」と「実際のデータ」との区別をしっかり行うと、役割分担が明確となり、プログラムの見通しが良くなります。
* 見た目の表示:画面上でのブロック表示
* 実際のデータ:2次元配列で管理しているブロックのデータ
では、この2次元配列を使って消去判定を行います。
(x, y) = (4, 3) にあるブロックを (3, 3) に移動させると消せそうなので、この移動を行うとします。
すると以下のような配置になります。
このデータをもとに消去判定を行います。
判定のアルゴリズムは、基準となる位置から「上下左右」に探索をするものとなります。
まず左から調べるとします。その場合、ブロックは違う色なのでこちらの方向には消すことができるブロックはない、という結果となります。
次に、上を調べます。すると同じ色なので消すことができる可能性があります。
なので、さらに上を調べます。
すると3つ同じ色が揃ったので消すことができます。
消すことができるブロックの情報は、同じサイズの2次元配列を別に作っておき、そこに格納します。
int blockEraseList[8][8] = {
{0,0,0,0,0,0,0,0},
{0,0,0,1,0,0,0,0},
{0,0,0,1,0,0,0,0},
{0,0,0,1,0,0,0,0},
{0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0},
};
消去ブロックが複数ある場合は、このように情報を保持しておくと、消去処理が簡単にできます。
このような感じで、上下左右を判定した後、消去対処が存在していれば、消去処理を行います。
blockEraseList の要素が “1” であるものを消します。
(3, 1), (3, 2), (3, 3) が消去対象なので、それぞれを “0” に置き換えました。
消去ができたので、次は落下処理です。
ブロックを落下させるために必要なのは、以下の情報です。
* どのブロックを落下させるのか
* ブロックをどこからどこへ落下させるのか
先ほどの図のような状態であれば、(3, 0) にある “6” を (3, 3) まで落下させる、ということを求める計算が必要になります。
方法としては、一番下の段にあるブロックから “0” のブロックを探します。そしてその段を調べ終わったら、1段ずつ上を調べていくようにします。
そして、 “0” を見つけたら、上方向を順番に調べます。先ほどの消去アルゴリズムと同様に、1マスずつ調べて、”0” でないブロックを見つけます。
上方向にブロックを見つけたので、以下の情報をどこかに格納しておきます。
* 対象: “6” のブロック
* 開始地点: (3, 0)
* 落下地点: (3, 3)
落下情報をすべて作成し終わったら、この情報を使って落下アニメーションを行います。なお、落下アニメーションなしで、とりあえず動かしたいのであれば、1フレームで落下を完了させても問題ありません。
さきほどの、ブロックの並びでは問題ないのですが、状況によっては、落下ブロックの上にブロックが存在することもあります。(というか、かなりの確率でそうなることが多いですね)
少し図を変更しました。”6” の上に “4” が存在しています。"4"も一緒に落下させる計算をするためには、落下計算用の2次元配列を別途用意します。そして、落下ブロックが存在した場合は、そちらに落下済みのブロックを移動させます。
これにより、その上にあるブロックの落下位置を正しく計算できるようになります。
ここまでの処理ステップを図にまとめてみました。
▼処理ステップ
1. ブロック操作を行い、ブロックを移動する
2. 消去判定を行う。
a. 消去可能なブロックがあれば、3へ進む
b. 消去ブロックが存在しない場合は、1へ戻る
3. 消去処理を実行してブロックをフィールドから消す
4. 落下可能なブロックがあれば落下処理を行い、落下完了したら 2に戻る
以上が、落ち物パズルの基本的な作り方となります。3マッチパズルであれば、おおよそ、この流れで実装できます。テトリスは消去判定よりもブロックの回転処理が少し面倒ですが、基本は同じです。
ぷよぷよは、直線だけでなくジグザグにもつながるので、上下左右のつながりを連続で判定することになります。
■ツムツムの作り方
ツムツムは他の落ちものパズルと比べてやや特殊な作りです。
ブロックが格子状に並んでおらず、箱にボールを入れたような状態になるため、やや複雑な衝突処理を実装する必要があります。
ただ、「物理エンジン」があれば、ブロック落下時の衝突処理が簡単にできるため、ツムツムの実装が容易となります。特にUnityでは簡単に物理エンジンを扱うことができるので、Unity初心者向けの題材としても良いです。
もちろん、衝突処理を自作するのもとても勉強になるので、プログラムに自信のある方にはそこから作ってみてもよいでしょう。
ひとまず、ここでは落下処理を物理エンジンで実装するとして、消去ロジックの実装方法を紹介します。
まず、真ん中にある水色のブロックを選んだとします。
そうしたら、そのブロックを中心に円(距離)で一定の範囲内に同じ色のブロックがあるかどうかを判定して、距離内であれば接続可能、そうでなければ接続不可、という判定をします。
上と下にあるブロックは接続可能ですが、左上にあるブロックには届かないので接続できない、という判定になります。
ここでは、下にあるブロックに接続したとします。
そうしたら、接続したブロックから、さらに別のブロックに接続できるかをチェックします。この時、接続済のブロックは除外して判定を行います。そうして、スクリーンから手を離した時に、接続済のブロックを消去する、という判定で実装が可能です。
なお、消去可能な範囲 (距離) は適切な値に調整する必要があります。広くしすぎるとあまりにも消してやすくなってしまい退屈なゲームになってしまうかもしれません。逆に狭くしすぎると、なかなか消せなくてストレスのたまるゲームになってしまいます。
また、ブロック落下中にスワイプ操作でブロック同士を接続した場合、ブロックが落下して接続済のブロックとの距離が離れ過ぎてしまう問題があります。この場合は、その時点で強制的に消去処理に進むなど、不自然な接続にならないよう、特別な処理を入れても良いかもしれません。
■落ちものパズルのシステムをどう展開していくか
落ちものパズルをより長く楽しませるには、どういう構成が良いかを考えてみます。
1つの案としては、すでにフィールドにブロックが配置されていて、それらを全て消すとステージクリアにするというものです。
それに対して、私がよく作るパターンとしては、ブロックをたくさん消すほど多くのダメージを敵に与えて、一定ダメージを与えると敵を倒せる、というルールを採用しています。
要はパズドラ方式ですね。
以下は昔 iPhone 向けに作った落ち物パズル「かずおち」です。
「たくさん消すと敵に大ダメージ」というシステムを採用すると、たくさん消すことがスコアという抽象的なものではく、敵へのダメージというわかりやすいものに反映されるため、手軽に爽快感が得られます。
また、敵はブロックを降らしたり、一定ターン数で攻撃するといった単純なAIで実装できて楽です。
敵を強くしたければ、HPを増やしたり、厄介なブロックを登場させたりするだけで良いので、これまた難易度調整が容易にできてしまうのですよね。
ということで、特に面白くするアイデアが思いつかない場合は、このルールを採用すると、それなりに面白くなるのでオススメです。
■関連する記事
今回紹介した、2次元配列でブロックを管理する方法は、一筆書きゲームと同じ考え方となります。もし、今回の内容が理解できない場合は、一筆書きゲームに挑戦してからでも良いかもしれません。
ゲーム作りに慣れていない場合は、この記事で書いた5つのゲームにチャレンジしても良いかもしれません。どれもシンプルながらとても勉強になるゲーム(ジャンル)だと思います。
2次元配列はそのまま使うと、ちょっとしたミスで領域外参照でエラーとなる危険性があります。それを防ぐには、2次元配列をそのまま使わずに独自のクラスをかぶせて使うと安心です。
上記記事では、独自のクラスを実装する例を紹介しています。
この記事が気に入ったらサポートをしてみませんか?