パート②:静的な物体(床・外壁・出口)を配置する@2Dローグライク公式tutorial解説【Unity2018】

このパートで行うこと

いよいよこのパートからローグライクゲームの作成を開始しよう。

さっそくこのパートでは、プレイヤーや敵が動き回るためのフィールドの作成に入る。
フィールドにはさまざま物体があるが、その中でもまずは床・外壁・出口をフィールドに配置していく。

f:id:snoopopo:20180704070907p:plain

これらは必ず決まった位置に配置されるもので、もちろん動いたりもしない。シンプルな物体だ。

壁には外壁と内壁の二種類あるが、ここではフィールドの外周に必ず表示する壁のことを外壁と呼ぶ。

このシンプルな物体を配置することでこのゲームにおける物体を配置するまでの流れをおさえよう。


前回の記事はこちら。
パート①:イントロダクション@2Dローグライク公式tutorial解説【Unity2018】


※この記事は、unity公式チュートリアル「2D Roguelike tutorial」を解説した連載記事です。
この連載記事共通の事項(実施している環境等)は以下の記事を参照ください。
表紙:2Dローグライク公式tutorial解説【Unity2018】


床のプレハブを作成する

まずはじめにフィールドに配置される床を作成する。


プレハブを作るために適当なSceneを作成しよう。

(Projectビューの)Sceneなど任意のフォルダで右クリック -> Create -> Scene

次に床のゲームオブジェクトを作成する。
新規にゲームオブジェクトを作成し、「Floor1」という名前をつけておこう。

(Hierarchyで)右クリック -> Create Empty

f:id:snoopopo:20180704043132p:plain

画像を表示する

いま作成した「Floor1」ゲームオブジェクトで画像を表示するには、
SpriteRendererコンポーネントをアタッチ*1し、 SpriteRenderer#Sprite に床の画像を設定しよう。

画像はすでに「Sprites」フォルダの下に「Scavengers_SpriteSheet」というスプライトシートで用意されている。
スプライトシートというのは、一つの画像を分割して複数のスプライトとして扱うものだ。スプライトアトラスとかそういった名前で呼ばれることもある。

Scavengers_SpriteSheet」スプライトシートが複数のスプライトに分割されていることを確認しよう。
床画像は 「Scavengers_SpriteSheet_32」として用意されている。 SpriteRenderer#Sprite に設定しよう。

f:id:snoopopo:20180704044142g:plain

描画順を指定する

床はフィールドに配置するどの物体よりも奥に描画したい(床の上にプレイヤーやアイテムがのるイメージ)ため、 ここでは描画順を一番奥に設定していく。
描画順はSpriteRenderer#SortingLayer を使って設定することができる。

SortingLayerにはデフォルトで設定されている「Default」の他に「Floor」「Items」「Units」の3つの値が設定されており*2、このゲームではこの3つのみ使う。

SortingLayerはプルダウンの上に表示されている値ほど奥に、下に表示されている値ほど手前に描画される。

このゲームでは「Default」は使用しないので一番奥に描画される「Floor」を設定しよう。

f:id:snoopopo:20180704044622g:plain

プレハブにする

作成した「Floor1」ゲームオブジェクトはプレハブにする。

プレハブとはゲームオブジェクトのひな形・テンプレートのようなもので、今設定したコンポーネントやその設定値をそのまま保存し、複製して使うことができるというものだ。

ゲームオブジェクトをプレハブにするには、Projectビューにゲームオブジェクトをドラックすればよい。
床に限らずこの先フィールドに配置する物体はすべてプレハブにしてから使うことになるので、この手順はおさえておこう。

f:id:snoopopo:20180704044955g:plain

プレハブもいくつか作るので「Prefabs」フォルダにまとめておくと整理しやすいゾ。

複数の床を作成する

実は床用の画像は全部で8種類用意されており、「Scavengers_SpriteSheet_32 〜 39」まであるので、 先ほど作成した「Floor1」を元に画像だけを変えたプレハブも作成しよう。


Floor1」はすでにプレハブとして保存されているので、ヒエラルキーに残った「Floo1」というゲームオブジェクトを「Floor2」へリネームし流用する。

先程と同じように画像の指定を SpriteRenderer#Sprite で行おう。
今度は「Scavengers_SpriteSheet_33」というスプライトを指定する。
設定が終わったら「Floor2」も忘れずにプレハブにしておくこと。

同じ要領で残りの画像分、床プレハブを作成してしまおう。

プレハブ名 Sprite
Floor1 Scavengers_SpriteSheet_32
Floor2 Scavengers_SpriteSheet_33
Floor3 Scavengers_SpriteSheet_34
Floor4 Scavengers_SpriteSheet_35
Floor5 Scavengers_SpriteSheet_36
Floor6 Scavengers_SpriteSheet_37
Floor7 Scavengers_SpriteSheet_38
Floor8 Scavengers_SpriteSheet_39

作成が完了するとこのように8種類の床プレハブが出来ているはずだ。

f:id:snoopopo:20180704045623p:plain

外壁のプレハブを作成する

今度は外壁のプレハブを作成する。
外壁は3種類の画像が用意されているので、床の時と同様に3つのプレハブを作成する。

床のプレハブを元に外壁用の画像を指定してプレハブ「OuterWall1」「OuterWall2」「OuterWall3」を作成しよう。

プレハブ名 Sprite
OuterWall1 Scavengers_SpriteSheet_25
OuterWall2 Scavengers_SpriteSheet_26
OuterWall3 Scavengers_SpriteSheet_28

f:id:snoopopo:20180704051020p:plain

床と外壁を配置する

ここからはScriptの実装にも入っていく。

BoardManager.cs

新規にスクリプトを作成して、「BoardManager」と名前をつけよう。

(Projectビューの任意のフォルダで)右クリック -> Create -> C# Scriot

このクラスは、その名の通りフィールドを管理するクラスとなる。
このパートでは、ゲーム起動時に床・外壁などの物体を配置する処理を追加する。

BoardManager.cs
using UnityEngine;

public class BoardManager : MonoBehaviour
{

    public int columns = 8;
    public int rows = 8;

    public GameObject[] floorTiles; //床プレハブ
    public GameObject[] outerWallTiles; //外壁プレハブ

    private Transform boardHolder;

    private void BoardSetup()
    {
        this.boardHolder = new GameObject("Board").transform;

        //外壁 -1 からスタートする
        for (int x = -1; x < columns + 1; x++)
        {
            for (int y = -1; y < rows + 1; y++)
            {
                //床の配置
                GameObject toInstantiate = this.floorTiles[Random.Range(0, this.floorTiles.Length)];

                if (x == -1 || x == columns || y == -1 || y == rows) //外壁
                {
                    toInstantiate = this.outerWallTiles[Random.Range(0, this.outerWallTiles.Length)];
                }

                GameObject instance = Instantiate(toInstantiate, new Vector3(x, y, 0), Quaternion.identity, this.boardHolder) as GameObject;
            }
        }
    }

    public void SetupScene()
    {
        BoardSetup();
    }
}

上記のコードは、先ほど作成した床と外壁のプレハブを必要な数だけ複製しフィールドに配置している。


このゲームではフィールド全体のサイズは固定であり、10×10マスである。

このパートの最初に記載した通り、壁には内壁と外壁の2種類存在するが、 ここでいう外壁は必ずフィールド全体を囲むように配置される。

よってフィールド全体のサイズは10×10だが外壁分減らした8×8の範囲、プレイヤーが動き回れることになる。

f:id:snoopopo:20180704060828p:plain

コードのはじめでフィールドのサイズは8×8と定義されているが、これはプレイヤーが動き回れる範囲のサイズであることに注意しよう。

public int columns = 8;
public int rows = 8;

BoardSetup() はフィールドに床と外壁の配置を行っている。

これまでに床のプレハブは8種類、外壁プレハブは3種類作成していた。
画像違いのプレハブの中からランダムに1つのプレハブ決め、そのプレハブを複製=InstantiateしてできたゲームオブジェクトをBoardゲームオブジェクトの子ゲームオブジェクトにすることで、配置を行っている。

フィールドの外周に配置される外壁は8×8のプレイヤー移動可能エリアの外側に1マス分囲むように配置される。
プレイヤー移動可能エリアの一番左下のマスを(0,0)として考えると、外壁はxかy座標が-1か8のマスに配置されることになる。

f:id:snoopopo:20180704062542p:plain

床はフィールド全体に配置されるものだが、外壁を配置するところには必要はない。
なぜなら、外壁の画像は床も含んだ不透過な画像となっているからだ。
よって床は外壁を配置した以外のマスに配置するようになっている。


最後に SetupScene() を用意している。
これは、BoardSetup() がprivateアクセスなのに対してpublicなメソッドとなっており、 外部からはこの SetupScene() のみ呼べば良いようにしておく。

ゲームを起動して確認する

配置がうまくいっているか実際にゲームを起動してテストしたいが、現在はスクリプトを書いただけで BoardManager はどこからも呼び出されていない。
ここでは BoardManagerをテストするための準備を行う。

GameManager

新規に「GameManager」というスクリプトを作成する。

GameManager.cs
using UnityEngine;

public class GameManager : MonoBehaviour
{
    public static GameManager instance;
    private BoardManager boardScript;

    void Awake()
    {
        if (instance == null)
        {
            instance = this;
        }
        else if (instance != this)
        {
            Destroy(gameObject);
        }
        DontDestroyOnLoad(gameObject);

        boardScript = GetComponent<BoardManager>();
        InitGame();
    }

    void InitGame()
    {
        boardScript.SetupScene();
    }
}

GameManagerはその名の通りこのゲーム全体を管理する役目を担う。
このクラスは複数存在しない方が都合がよいため、このクラスのインスタンスを保持するstaticフィールドを用意して唯一のインスタンスにしておく。

public static GameManager instance;

外部からこのクラスを使う場合は、

GameManager.instance.InitGame();

というように静的にアクセスすることができる。

また、ゲーム起動中「GameManager」ゲームオブジェクトは常に存在していてほしいため、DontDestroyOnLoadにして破棄されないようにしている。

Awake()の最後でInitGame()を呼び、その中で先ほど作成したBoardManager#SetupScene()を呼ぶ。


今度はunityエディタ側に戻り、「GameManager」というゲームオブジェクトを新規で作成しよう。
そしてここまでで作成した、「GameManager」「BoardManager」スクリプトをアタッチする。

BoardManager」は、複数種類ある床と外壁プレハブからランダムに1つのプレハブを選び配置する動きだった。
BoardManager」には選ばされる対象の床と壁のプレハブがまだ伝わっていないのでここで設定しよう。

f:id:snoopopo:20180704064538g:plain

インスペクターの右上にある錠前アイコンをクリックすると表示が切り替わらなくなり、選択しやすい。


GameManager」ゲームオブジェクトもこれまでと同様にプレハブにして保存しておき、ヒエラルキーに残ったゲームオブジェクトは削除しておこう。

f:id:snoopopo:20180704064838p:plain

Loader

最後に「Loader」というスクリプトを新規に作成する。 このクラスは「GameManager」プレハブが存在しないならば作成する、という役割を担う。

Loader .cs
using UnityEngine;

public class Loader : MonoBehaviour {

    public GameObject gameManager;

    void Awake() {
        if (GameManager.instance == null)
        {
            Instantiate(gameManager);
        }
    }
}

Loader」スクリプトを使うために、シーンに自動で作成されている「Main Camera」ゲームオブジェクトに「Loader」スクリプトをアタッチしておこう。

Loader#GameManagerに「GameManager」プレハブを設定する。

f:id:snoopopo:20180704065309p:plain

カメラを調整する

ここまでで「BoardManager」をテストするための準備が整ったので、ゲームを起動してみよう。

f:id:snoopopo:20180704065503p:plain

フィールドが表示されているようだがやけに右上に表示されてしまっているので、カメラの位置を調整しよう。 カメラの位置は、「Main Camera」ゲームオブジェクトの Transform#position で設定できる。x,y = 3.5 にするとちょうどよいだろう。

また、背景色が青くなっていてこのゲームのイメージにはあわないのでCamera#Backgroundで黒色に変更しておく。

f:id:snoopopo:20180704065618p:plain

f:id:snoopopo:20180704065655p:plain

このように画面中央にフィールドが配置されればOKだ。

出口のプレハブを作成する

次はステージを切り替える出口のプレハブを作成しよう。
出口についても床と同じように静止画像を表示するので、先ほど作成した床プレハブを元に「Exit」ゲームオブジェクトを作成する。

スプライトは、「Scavengers_SpriteSheet_20」と言う出口用の画像を設定しておこう。
また床よりも手前に出口の画像を表示するため、SortingLayerは床プレハブに設定した「Floor」よりも手前に描画される「Items」をしよう。

ここまでの設定を載せておこう。床プレハブと同じ要領なので特に難しいところはない。
確認ができたらいつものようにプレハブにしてヒエラルキーからは削除しておくこと。

f:id:snoopopo:20180704070038p:plain

出口を配置する

今作成した出口プレハブを配置するコードを書いていこう。

BoardManager .cs
using UnityEngine;

public class BoardManager : MonoBehaviour
{

    public int columns = 8;
    public int rows = 8;

    public GameObject[] floorTiles; //床プレハブ
    public GameObject[] outerWallTiles; //外壁プレハブ
    public GameObject exit; //出口プレハブ

    private Transform boardHolder;

    private void BoardSetup()
    {
        this.boardHolder = new GameObject("Board").transform;

        //外壁 -1 からスタートする
        for (int x = -1; x < columns + 1; x++)
        {
            for (int y = -1; y < rows + 1; y++)
            {
                //床の配置
                GameObject toInstantiate = this.floorTiles[Random.Range(0, this.floorTiles.Length)];

                if (x == -1 || x == columns || y == -1 || y == rows) //外壁
                {
                    toInstantiate = this.outerWallTiles[Random.Range(0, this.outerWallTiles.Length)];
                }

                GameObject instance = Instantiate(toInstantiate, new Vector3(x, y, 0), Quaternion.identity, this.boardHolder) as GameObject;
            }
        }
    }

    public void SetupScene()
    {
        BoardSetup();
    
    //出口配置
    Instantiate (exit, new Vector3 (columns - 1, rows - 1, 0f), Quaternion.identity);
    }
}

追加した部分はBoardManager#SetupScene()で出口プレハブを複製してできたゲームオブジェクトを配置する処理のみだ。

出口はプレイヤーが動きまわれるエリアの一番右上のマスに固定で配置される。 このマスは0始まりであるので、一番右上のマス(7,7)に出口が配置される。

f:id:snoopopo:20180704070636p:plain


GameManager」プレハブのBoardManager#exitに作成した出口プレハブを設定する。

f:id:snoopopo:20180704070855p:plain

ゲーム起動して出口が追加されたことを確認しよう。

f:id:snoopopo:20180704070907p:plain

次回予告

今回は、もともと位置が決まっていて、動かない物体の床・外壁・出口の配置までを行った。

次のパートでは、フィールド内のランダムな位置に配置される、アイテムや内壁の配置を行う。
スクリプトも含めて配置の流れは今回でつかめたと思うので、違いはランダムな位置に配置することだけだ。

⇒ 続きのパートはこちら。

www.snoopopo.com

*1:ゲームオブジェクトにコンポーネントをつけること。

*2:SortingLayerはチュートリアル用に事前に作成されているが、プルダウン一番下のAdd Sorting Layer で自分で追加および、描画順の変更をすることが可能だ。