ゲーム制作勉強中!あこがれだったプログラマーに今からなろう!

昔、あこがれていたプログラマー。今からでも勉強してみようと思い立ち、チャレンジ開始! 勉強メモや、悪戦苦闘な日々の記録です。

ウィザードリィライクのダンジョンゲームを作る!(3):複数のObjectPoolをまとめて管理しやすくする。



前回まででPlayerの周りの初期描画と、Playerが自由に移動できる様になりました。
でも、まだ壁はすり抜けるし、初期描画の範囲を超えると何もない空間が広がっているだけという状況なので、次はPlayerの移動に合わせて、描画範囲を増やしていく仕組みを作っていきたいと思います。
でも増やしていくだけだと、結局はダンジョン全部を描画することになってしまうので、Playerの移動方向は描画し、Playerの後方の描画済みのエリアは消去していく、
www.youtube.com
こんな感じが、移動による描画の最終形です。

そのためには、今まで作ったコードも結構改造する必要があるので、今日はまず、ダンジョンマップの設計図classのDungeonMapの改造と、DungeonBuilder改めDungeonManagerObjectPoolを追加するところまでやろうと思います。

まずは、DungeonMapの改造
今まではダンジョンマップの0と1だけのint型2次元配列を持つだけでしたが、ダンジョンの描画と消去を行うためには、消去したいエリアのObjectを特定する必要があるので、Object管理用の2次元配列を新たに追加します。

int[, ] map; //元々用意されていたダンジョンマップの設計図用2次元配列

List<GameObject>[,] objects; //今回追加したObject管理用の2次元配列

mapの同じ座標に、床と天井の二種類のObjectを保管する必要があるので、GameObject型の2次元配列ではなく、GameObject型のListを格納する2次元配列にしています。

次に、このobjectsの初期化や、Objectの格納、Object取得を行うための関数を追加します。

    public void Objects_Reset()//2次元配列初期化用
    {
        for (int i = 0; i < objects.GetLength(0); i++)
        {
            for (int j = 0; j < objects.GetLength(1); j++)
            {
                objects[i, j] = new List<GameObject>();
            }
        }
    }

    public List<GameObject> GetObject(int x, int y)//指定座標のObject有無チェック
    {
        if (x < 0 || x >= map.GetLength(0) || y < 0 || y >= map.GetLength(1))
        {
            return null;
        }

        if (objects[x, y].Count == 0)
        {
            return null;
        }
        else
        {
            return objects[x, y];//Objectがあれば、そのlistを返す。
        }
    }

    public void SetObject(int x, int y, GameObject obj)//Objectを指定座標のListに追加する。
    {
        if (x < 0 || x >= map.GetLength(0) || y < 0 || y >= map.GetLength(1))
        {
            return;
        }

        objects[x, y].Add(obj); 
    }

    public void ListClear(int x, int y)//指定座標のみのList初期化
    {
        if (x < 0 || x >= map.GetLength(0) || y < 0 || y >= map.GetLength(1))
        {
            return;
        }

        objects[x, y] = new List<GameObject>();
    }


そして忘れない様に、コンストラクタにObject_Reset関数を呼び出すのを追加してやり、objectsに空のListを配置させます。

あとは、ダンジョンの設計図の定義も変えておきます。
0が通路、1が壁にしていましたが、壁を1ではなく99に修正して、98として扉を追加しています。

    public DungeonMap()
    {
        map = new int[15, 15]{
          // 0    1   2   3   4   5   6   7   8   9  10  11  12  13  14 
            {99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99},//→Y座標 //0
            {99,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, 99},//↓x座標  //1
            {99,  0, 99, 99, 99, 99, 99,  0, 99, 99, 99, 99, 99,  0, 99},//2  
            {99,  0, 99,  0,  0,  0, 99,  0, 99,  0,  0,  0, 99,  0, 99},//3
            {99,  0, 99,  0,  0,  0, 99,  0, 99,  0,  0,  0, 99,  0, 99},//4
            {99,  0, 99,  0,  0,  0, 99,  0, 99,  0,  0,  0, 98,  0, 99},//5
            {99,  0, 99, 98, 99, 99, 99,  0, 99, 99, 99, 99, 99,  0, 99},//6
            {99,  0, 99,  0,  0,  0,  0,  0, 99,  0,  0,  0,  0,  0, 99},//7
            {99,  0, 99, 99, 99, 99, 99, 99, 99,  0, 99, 99, 99, 99, 99},//8
            {99,  0, 99,  0,  0,  0, 98,  0, 99,  0, 99,  0,  0,  0, 99},//9
            {99,  0, 99,  0,  0,  0, 99,  0, 99,  0, 99,  0,  0,  0, 99},//10
            {99,  0, 99,  0,  0,  0, 99,  0, 99,  0, 98,  0,  0,  0, 99},//19
            {99,  0, 99,  0,  0,  0, 99,  0, 99,  0, 99,  0,  0,  0, 99},//12
            {99,  0, 98,  0,  0,  0, 99,  0,  0,  0, 99,  0,  0,  0, 99},//13
            {99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99} //14
        };

        objects = new List<GameObject>[15, 15];
        Objects_Reset();//objectsに空のListを配置する。
    }

壁と示す数字の変更は今やらなくても良い修正ですが、後で壁方向には進めなくする判定に使うのと、今回説明するObjectPoolで出てくるタグが、ダンジョン設計のこの数字と関連してくるので、今の段階で変えておいた方が楽になるからです。


ダンジョンマップの設計図classのDungeonMapの改造は終わったので、次はDungeonBuilder改め、DungeonManagerObjectPoolを追加します。
ObjectPool単体では一種類のGameObjectしかPoolできないので、今回の様に壁や床や扉といった複数のGameObjectをPoolするためには、その種類毎にObjectPoolを作る必要があります。
ただ、それではマップを描画する際、いちいちその座標の数字を読んで、それに沿ったObjectPoolからGameObjectをとってくる必要があり、ちょっと面倒なので、ObjectPoolをまとめて管理しやすくする方法を紹介します。

まず、個々のObjectPoolインスペクターで設定できる様にします。
tagに設定する数字がGameObjectのタグに当たり、先ほどのダンジョン設計の数字と同じ値になります。
つまり、99は壁で98は扉のObjectPoolになる様にします。

    [System.Serializable]
    public class Pool
    {
      public int tag; //この数字がマップの数字とリンクする。
      public GameObject prefab;
      public int size;//ObjectPoolの初期容量になる。
    }

    public List<Pool> pools;//今作成したPool classのListをインスペクターで作成できる様になる。

次に、ObjectPoolをまとめて管理するための辞書型変数を作成し、
先ほどのpools Listの数だけObjectPoolを作成して、辞書型変数に追加してやります。

    public Dictionary<int,ObjectPool<GameObject>> poolDictionary;

    private void Awake() 
    { 
      poolDictionary = new Dictionary<int, ObjectPool<GameObject>>();

      foreach(Pool pool in pools) 
      {
        ObjectPool<GameObject> objectPool = new ObjectPool<GameObject>(
          createFunc: () => Instantiate(pool.prefab),
          actionOnGet: (obj) => obj.SetActive(true),
          actionOnRelease: (obj) => obj.SetActive(false),
          actionOnDestroy: (obj) => Destroy(obj),
          collectionCheck: false,
          defaultCapacity: pool.size
          );

        poolDictionary.Add(pool.tag, objectPool);
      }

これで複数のObjectPoolを、1つの辞書型変数poolDictionaryにまとめることができます。
あとはこの辞書型変数poolDictionaryにタグ番号で使いたいObjectPoolを指定してGetReleaseを使って、目的のObjectを扱う事ができるので、ついでにGetReleaseの関数も作成してしまうことにします。

  public GameObject GetFromPool(int tag,int X, int Y, int Z,int rotation)
  {
    if(!poolDictionary.ContainsKey(tag))//存在しないタグなら何も返さない。
    {
      return null;
    }

    GameObject obj = poolDictionary[tag].Get();
    obj.transform.SetParent(transform);
    obj.transform.position = new Vector3(X, Y, Z);
    obj.transform.rotation = Quaternion.Euler(0, rotation, 0);
    map.SetObject(X, Z, obj);//DungeonMapのobjects配列に出現するObjectを追加する。

    return obj;
  }

  public void ReturnToPool(int tag, GameObject obj)
  {
    if(!poolDictionary.ContainsKey(tag))//存在しないタグなら受け取らない。
    {
      return;
    }

    poolDictionary[tag].Release(obj);
  }


辞書型変数poolDictionaryの使い方で注意しなければならない事として、
Getしてくるときはまだ良いのですが、Releaseする時にもタグが必要になってくるので、どのObjectをPoolに返却するのかまで管理する必要があるので、そのあたりの仕組みも含めて、次回、Playerの移動に合わせてダンジョンの描画範囲を変えていく工程について説明できればと思います。