「Flappy Daruma」を作ってみよう!③障害物の生成

今回の内容を整理しよう!

前回の内容を復習しよう

前回は、さまざまなゲームで利用できる背景の無限スクロールについて紹介しました。

Unityで行う無限スクロールには何種類か方法がありますが、前回紹介したものは直感的でシンプルなので簡単に応用ができると思います。

今回進める作業を確認しよう!

今回は、障害物を配置していきます。
ゲームによって障害物はさまざまですが、今回は地面と土管が障害物になります。

地面については、前回背景の一部として実装したオブジェクトに、障害物としての機能を追加していきます。

土管については、地面と同じように横スクロールさせるだけではなく、一定間隔で土管を繰り返し無制限に作り出す方法や、土管の高さをランダムに変える方法、表示されなくなった土管の後片付けについても紹介します。

前提知識を学ぼう!

衝突を扱うCollider2Dコンポーネント

障害物になるオブジェクトは、別のオブジェクトと衝突する必要があります。衝突を伴う処理を扱うのがCollider2Dというコンポーネントです。

ダルマを地面に落下させて、地面のオブジェクトにCollider2Dコンポーネントが追加されているときとそうでないときを比べてみましょう。
(このときダルマのオブジェクトには、落下などの物理挙動を扱うRigidbody2Dコンポーネントと、地面に追加しものと同様のCollider2Dコンポーネントが追加されています。)

前回のように地面の画像をSpriteRendererコンポーネントで表示しただけでは、下図のようにすり抜けてしまいます。

そこでCollider2Dコンポーネントを地面のオブジェクトに追加すると、下図のように、オブジェクト同士が衝突し、落下するダルマが地面に衝突し着地しました。

Collider2Dには、形によってさまざまな種類があり、地面には四角形のBoxCollider2D、ダルマには円形のCircleCollider2Dが割り当てられています。
Collider2Dの形状は自動的に画像の大きさに合わせるだけではなく、手動で編集することもできます。

オブジェクトを複製できるプレハブの仕組み

土管は、ゲームオーバーになるまで無限に出現し、右から左に流れてくる必要があります。
今回は土管を無限に作る方法として、Unityのプレハブ(Prefab)という機能を利用します。

プレハブは、すでに作ったオブジェクトをファイル(アセット)として保存したものです。
作り方は簡単で、ヒエラルキーウィンドウのオブジェクトを、プロジェクトウィンドウの任意のフォルダにドラッグ&ドロップするだけです。

逆にプレハブをシーンビューやヒエラルキーウィンドウにドラッグ&ドロップすると、シーンにプレハブと同じオブジェクトを複製して配置することができます。

単純にシーン上のオブジェクトをコピーすることでも複製はできますが、プレハブの複製には複製後もメリットがあります。

その一つが、複数のオブジェクトの修正が簡単になることです。
プレハブ自体を変更すると、そのプレハブから複製された複数のオブジェクトにも変更がすぐに適用されます。
例えば、下図のようにプレハブにしたダルマをいくつもシーンに配置したあと、向きを修正したい場合、プレハブをインスペクターウィンドウなどで編集するだけで簡単に全ての複製の修正ができます。

反対に、複製されたオブジェクトの情報を元のプレハブに反映させるということもできます。

また、これから紹介する、スクリプトから土管のオブジェクトを作成する際も、プレハブが必要になってきます。

プレハブの詳細については、「0から2Dアクションバトルゲームを作ろう!③弾を発射させてみよう」をご参考ください。

地面に当たり判定を設定しよう

それでは早速、さきほどのCollider2Dの紹介で述べたように、地面に衝突の機能をつけてみましょう。

地面にCollider2Dコンポーネントを追加する

前回、シーンに配置した地面のオブジェクトは横スクロールしながら画像を表示する機能は持っています。
しかし、まださきほどのようにダルマを落としても地面とぶつからずすりぬけてしまいます。

今回は、地面のオブジェクトに、ダルマとぶつかる障害物としての機能を持たせるためCollider2Dコンポーネントを追加しましょう。

Collider2Dコンポーネントの追加も、他のコンポーネントと同じく、追加先のオブジェクトを選択してインスペクターウィンドウに表示される[Add Component]ボタンを押します。
そうすると、下図のようなリストがでてくるので、上のテキストボックスで検索したりカテゴリから選択して目的のコンポーネントを選択します。

Collider2Dコンポーネントには、Box、Circleなど形に応じて様々な種類があります。これらはPhysics 2Dカテゴリに入っているので、そちらから選ぶと良いでしょう。

今回は地面の形状にあわせてBoxCollider2Dコンポーネントを地面のオブジェクトに追加しました。
通常Collider2Dコンポーネントを追加した直後、画像の大きさに合わせた形状になっています。
(Collider2Dの形状はオブジェクトを選択すると、緑色の線で表され、BoxCollider2Dコンポーネントの場合は緑色の四角形で表示されます。)

しかし、地面のオブジェクトにBoxCollider2Dコンポーネントを配置しても、横のサイズがあっていません。

これは、前回お話した地面の描画方法に理由があります。
地面のオブジェクトは、SpriteRendererコンポーネントのDraw ModeをTiledにして、横幅の狭い画像を横に並べて表示していました。

Tiledで表示されている画像の場合、BoxCollider2Dコンポーネントを追加すると、画像を並べる前の一枚の画像のサイズに合わせて初期化されるのです。

BoxCollider2Dコンポーネントの形状を横に並べた状態に合わせるには、BoxCollider2DコンポーネントのAuto Tilingにチェックをいれましょう。
これで、SpriteRendererコンポーネントのDraw ModeがTiledのときの表示サイズに応じて自動的にサイズを合わせるようになります。

また、自分で微調整したい場合は、BoxCollider2Dコンポーネントの[Edit Collider]ボタンを押すことで、シーンビューの緑の四角形の範囲を調整することもできます。

今回はAuto Tilingにチェックを選んで自動的にサイズをあわせました。
これで、適切なサイズのBoxCollider2D が地面のオブジェクトに追加され、ダルマと衝突することができるようになりました。

当たり判定はレイヤーごとに管理できる

衝突を表現するには、オブジェクト同士の当たり判定が必要になります。
今回作るゲームのようにオブジェクト数が少ない場合は特に問題ありませんが、オブジェクトの数が多くなると当たり判定だけで処理が重くなる可能性があります。

また、味方同士は互いにすりぬけるけど、敵とは衝突するようにしたいなど、特定の組み合わせでのみ衝突を行いたい場合もあります。

そのような場合に便利なのが、Layer Collision Matrixです。
[Edit]→[Project Settings]→[Physics 2D]メニューを選択するとインスペクターウィンドウに表示されます。
レイヤーを行と列で列挙し、その組み合わせごとに衝突を行う場合のみチェックをいれます。

例えば、今回のゲームでは、ダルマはPCレイヤー、地面はGroundレイヤー、土管はObstacleレイヤーに所属しています。
上図の設定では、ダルマは地面や土管と衝突できるようにチェックが入れられています。

レイヤーは、オブジェクトを選択した後のインスペクターウィンドウの右上のほうで選択したり追加することができます。

天井を追加して改造してみよう

Flappy Darumaでは天井が存在しません。
そのため、ダルマがジャンプし続けると画面表示より上に移動して隠れてしまいます。
これは今回の仕様であり、土管が表示よりも十分に高い位置まで伸びているため、どんなにダルマが高い位置にジャンプしたとしても飛び越えられないようになっているので問題はありません。

しかし、天井を作りたいという読者の方もいるかもしれませんので、簡単な改造方法を紹介します。

方法は簡単で、コピーした地面のオブジェクトを真上に配置するだけです。

コピーしたいオブジェクト、今回は地面1と地面2を選択した後、Ctrl(Macならば⌘command)+Dで複製すると、同じ位置にオブジェクトの複製が配置されるので位置を調整するのが楽になります。
地面を複製して、真上に移動する、たったこれだけで天井を作ることが出来ました。

土管を作ろう

続いて、もうひとつの障害物、土管を作っていきます。
土管は地面と異なり、高さをランダムにしたり、無限に作り出す必要があるので、そちらを中心に紹介しましょう。

土管のプレハブを作る

さきほどプレハブの紹介で少し述べましたが、スクリプトからオブジェクトの複製を作り出す場合はプレハブを用いると簡単です。
そこで、はじめに土管のプレハブを作っていきます。

土管の構成要素は、上の土管、下の土管、ScoreUpLineの3つです。
ScoreUpLineは見えないオブジェクトですが、スコアを増やすきっかけになる縦長のBoxCollider2Dコンポーネントのついたオブジェクトです。

この3つをシーン上で作成し、さきほど説明したようにプロジェクトウィンドウにドラッグ&ドロップしてそれぞれのプレハブを作ります。

Sprite Editorによる画像の設定

土管の高さの変更は、スクリプトからSpriteRendererコンポーネントのSizeの縦幅(Height)にアクセスして行います。
このフィールドはDraw ModeがSlicedかTiledのときにインスペクターウィンドウに表示され、Slicedの場合は画像を拡大縮小することができます。

しかし、画像全体を縦に伸ばすと土管の口の部分や、黒い横線まで引き伸ばされてしまいます。

この問題を解決するため、引き伸ばしたい画像の範囲を四角形であらかじめ設定しておきます。

上の動画の土管の画像アセットでは、その設定をしていないため、画像全体が拡大・縮小されてしまいました。
その設定は、画像アセットを選択して、インスペクターウィンドウの[Sprite Editor]ボタンを押すと表示されるSprite Editorウィンドウで行います。

土管の場合は、口の部分と、底の黒い横線の部分を引き伸ばして欲しくないので、緑の四角形を調整し、土管の上下を除いた部分を選択しました。
四角形の調整が終わったらSprite Editorウィンドウの右上の[Apply]ボタンを押して適用しましょう。
これで、拡大をしても土管の口の部分や黒い横線が不自然に伸びず、きれいに土管が伸びるようになりました。

上の動画では、インスペクターウィンドウでSizeを変更していますが、これを後述するスクリプトで設定することでランダムな土管をゲームの実行中に作り出します。

また、この土管は縦のサイズを変更すると上に伸びていきますが、これはさきほど土管の画像アセットをSprite Editorで設定した際に、Pivotを左下に設定したためです。

逆に上の土管の場合はPivotに左上を指定し、下方向に伸びるように設定しています。

一定間隔で土管を複製する

地面や背景は、シーンに直接配置しました。
土管もシーンにたくさん配置させてスクロールさせる方法もありますが、今回はランダムな高さの土管を無限に出現させるため、スクリプトから土管を作ることにします。

Flappy Darumaのプロジェクトでは、土管を一定間隔で作りスクロールさせる機能として、ObstacleManagerコンポーネントをC#で作りました。
そのコンポーネントを同じ名前(別の名前でも構いません)のObstacleManagerオブジェクトに追加しました。

コンポーネント用のC#ファイルの作成、オブジェクトへのコンポーネントの追加については前回作成したScrollAndBackコンポーネントを参考にしてください。
(TODO:ここに前回の記事へのリンクを記載したいです)

ObstacleManagerクラス内には、土管のプレハブをインスペクターウィンドウで設定するためのメンバ変数が定義されています。

    /// 上側の障害物のプレハブです。
    public GameObject prefabObstacleUpper_;

    /// 下側の障害物のプレハブです。
    public GameObject prefabObstacleLower_;

    /// 上下の障害物の間にあるスコアアップを判定する縦線のプレハブです。
    public GameObject prefabScoreUpLine_;

このメンバ変数はインスペクターウィンドウのフィールドに表示されるので、プロジェクトウィンドウのプレハブをドラッグ&ドロップしてコンポーネントに設定します。

この設定により、ObstacleManager.csスクリプトでは、メンバ変数から土管のプレハブを参照できるようになりました。
これらのプレハブを引数にしてInstantiate関数を呼び出し、プレハブから複製を作りシーンに配置します。

        GameObject _obstacle = Instantiate(_prefab) as GameObject;

 

土管の高さをランダムにする

土管のプレハブの複製は、全て同じ高さをしています。
これをランダムな高さに変更していきましょう。

さきほどプレハブを準備したときに、すでにSpriteRendererコンポーネントのDraw ModeのSlicedについては説明しました。
土管を縦に伸ばす際には、その中のSizeを次のコードのように設定します。

        // 指定された高さに障害物の高さを変更します。
        SpriteRenderer _sr = _obstacle.GetComponent<SpriteRenderer>();
        _sr.size = new Vector2(_sr.size.x, _height);

Flappy Darumaでは上と下の土管のすき間の縦幅は、インスペクターウィンドウでObstacleManagerコンポーネントに設定した幅に固定されています。
そのため、土管の一方の長さをランダムに決めると、もう片方の土管の長さが自然に決まります。

土管の位置を設定する

スクリプトで土管のオブジェクト群を作成したら、次はシーンのどこに配置するかを設定します。
土管は、いつも画面表示枠よりも右側の固定位置に作られるのですが、その位置をインスペクターウィンドウでObstacleManagerコンポーネントのフィールドに設定しています。

座標を直接設定することもできますが、シーン内に空のオブジェクトを配置し、それを設定したほうが直感的でわかりやすいので、オブジェクトのTransformを設定しています。

        // 指定したワールド座標に位置を変更します。
        _obstacle.transform.position = _position;
        // まとめてスクロールさせるため obstacles_ の下位に配置します。
        _obstacle.transform.SetParent(obstacles_.transform, true);

上のコードの _position はインスペクターウィンドウで指定したオブジェクトの位置が設定されています。
また、複数の土管をまとめてスクロールさせるため、ヒエラルキーウィンドウの中でObstacleManagerオブジェクトの下位になるようにTransformコンポーネントの親子関係を設定しています。
スクロールについては、前回の背景と同様にTransformのTranslate関数により毎フレーム少しずつ位置をずらして実現しています。

        // obstacles_ の持つ全ての下位のゲームオブジェクト(全て障害物)の位置を左に移動します。
        foreach(Transform _obstacle in obstacles_)
        {
            _obstacle.Translate(-_speed, 0f, 0f);
        }

これでランダムな高さの土管のセットが作られ、シーン上の指定した場所に出現し、同じ速度でスクロールするようになりました。

通り過ぎた土管の後片付け

ここまでで、土管について必要な実装ができました。
しかし、これだけだと、左にスクロールして画面から見えなくなった土管たちがどんどんと増えてき、処理が重くなることが懸念されます。

そこで、土管を作成したあとは、最後に後片付けをするようにしましょう。
方法は簡単で、画面表示枠より少し左側にDestroyZoneと呼ばれる当たり判定のついたオブジェクトを配置しておきます。

上図の左側にある縦長の四角形がDestroyZoneオブジェクトです。

DestroyZoneオブジェクトにもBoxCollider2Dコンポーネントが追加されていますが、今までと異なりTriggerを設定しておきます。

Collider2DコンポーネントでIs Triggerをチェックすると、オブジェクト同士が接しても衝突しなくなりますが、接触したことをOnTriggerEnter2Dイベント関数で知らせてくれるようになります。

OnTriggerEnter2Dイベント関数では、接触したオブジェクトを引数から取得できるので、そのオブジェクトをDestroy関数を使って削除します。

    private void OnTriggerEnter2D(Collider2D collision)
    {
        // 接触したゲームオブジェクト(今回は障害物に限定される)を破棄します。
        GameObject.Destroy(collision.gameObject);
    }

上のイベント関数のスクリプトでは、接触した全てのオブジェクトを削除しますが、背景や地面は削除されません。
その理由は、さきほど話したLayer Collision Matrixの設定にあります。

DestroyZoneオブジェクトはDestroyZoneレイヤーに所属していて、土管の所属しているObstacleレイヤーのみに当たり判定を行う設定です。
そのため、地面や背景などのほかのオブジェクトと重なったとしてもOnTriggerEnter2Dイベント関数は呼び出されず削除もされません。

まとめ

今回は障害物として、地面と土管を作成しました。

Collider2Dにより衝突・接触を実現できること、対象となるレイヤーの組み合わせを設定できることを紹介しました。
また、天井をつける簡単な改造方法も説明しました。

土管の作成では、プレハブから複製を作る方法、画像の一部を伸び縮みさせる方法、接触したときのイベント処理の実装方法などを紹介しました。