【Unity3D】自動生成する不思議なダンジョンの作り方⑤「現在位置マップの作成」

2022年8月26日

どうも、だらはです。
前回に引き続き、自動生成する不思議なダンジョンの作り方を紹介していきます。
今回はとうとう最後、現在位置マップの作成です!

スポンサーリンク

概要

記事が長くなると思うので先ずは完成図目次を示します。
目次の順番で自動生成する不思議なダンジョンを作成していきます。

◆完成図

◆目次

  1. 2Dマップを生成する。
  2. 2Dマップをもとに3Dマップを作成する。
  3. キャラクタやアイテムを初期配置する。
  4. 時間経過で敵を生成する。
  5. 現在位置マップを作成する。←今ココ!

⑤現在位置マップを作成する。

さて、今回は少し長いです。
前回の[ElementGenerator.cs]に以下の機能を実装します。

  • GameObjectのマップチップを生成する機能:GenerateObjMap2D()
  • GameObjectのマップチップの位置を更新する機能:UpdateMap2D()
  • プレイヤーが部屋に入ったら部屋をマッピングする機能:SearchMap()

これらの関数は、各種GameObjectStart()またはUpdate()から実行します。
アイテムは[ItemCtrl.cs]から、敵やプレイヤーは[CharacterCtrlCommon.cs]から実行します。

◆その他手順

  • プレイヤーの設定
    • TagPlayerと設定する。
    • [CharacterCtrlCommon.cs]をアタッチし、プレイヤー用のマップチップを設定する。(サイズは5)
    • MainCameraを子に配置する。
  • 敵の設定(GameObjectをResourcesに格納する)
    • TagEnemyと設定する。
    • [CharacterCtrlCommon.cs]をアタッチし、敵用のマップチップを設定する。(サイズは5)
  • アイテムの設定(GameObjectをResourcesに格納する)
    • [ItemCtrl.cs]をアタッチし、アイテム用のマップチップを設定する。(サイズは5)

◆参考イメージ

◆変更後の[ElementGenerator.cs]

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
using UnityEngine.UI;

public class ElementGenerator : MonoBehaviour
{
    //リソース格納用 (階層またぎのセーブ/ロードで使用)
    Material material;                                              //壁のマテリアル
    GameObject objGoal;                                             //ゴール
    GameObject[] objEnemyList = new GameObject[1];                  //敵リスト
    public GameObject[] objItemList = new GameObject[1];            //アイテムリスト
    GameObject[] objMapTipList = new GameObject[1];                 //マップチップリスト

    //調整用パラメタ
    [SerializeField] int LimitEnemyInitMin;                         //初期生成の敵の下限数
    [SerializeField] int LimitEnemyInitMax;                         //初期生成の敵の上限数
    [SerializeField] int LimitItemInitMin;                          //初期生成のアイテムの下限数
    [SerializeField] int LimitItemInitMax;                          //初期生成のアイテムの上限数
    
    //2Dマップ生成スクリプト用
    DungeonGenerator dungeonGenerator;
    int[,] map;

    //パス読み込み用
    GameObject objMap2D;                                            //Map2D
    GameObject objPlayer;                                           //プレイヤー

    //生成したマップチップ
    GameObject[,] objMapExist;                                      //フィールド用

    //部屋探索中フラグ
    public bool flgSearchMap;

    //敵の自動生成用
    [SerializeField] float appearNextTime = 10;                     //次に敵が出現するまでの時間
    [SerializeField] int maxNumOfEnemys = 99;                       //出現する敵の数
    int numEnemys = 0;                                              //今何人の敵を出現させたか(総数)
    float countTime = 0;                                            //時間計測用

    void Awake()
    {
        //リソース読み込み
        ReadResources();

        //壁を生成
        GenerateWall();

        //二次元マップ生成
        GenerateMap2D(map);
    }
    
    void Start()
    {
        //プレイヤーを部屋に配置(予め生成しておく)
        objPlayer = GameObject.Find("Player");
        GenerateObj(map, objPlayer);

        //ゴールを部屋に配置
        GenerateObj(map, objGoal);

        //敵を部屋に配置(初期出現数はランダム)
        int enemyNum = UnityEngine.Random.Range(LimitEnemyInitMin, LimitEnemyInitMax + 1);
        for (int i = 0; i < enemyNum; i++)
        {
            int enemyIdx = UnityEngine.Random.Range(0, objEnemyList.Length);
            GenerateObj(map, objEnemyList[enemyIdx]);
        }

        //アイテムを部屋に配置(初期出現数はランダム)
        int itemNum = UnityEngine.Random.Range(LimitItemInitMin, LimitItemInitMax + 1);
        for (int i = 0; i < itemNum; i++)
        {
            int itemIdx = UnityEngine.Random.Range(0, objItemList.Length);
            GenerateObj(map, objItemList[itemIdx]);
        }
    }

    void Update()
    {
        //時間経過で敵を生成
        AppearEnemy();
    }

    //リソース読み込み
    void ReadResources()
    {
        //壁のマテリアル
        material = (Material)Resources.Load("Material/StoneSurface_edit");

        //ゴール
        objGoal = (GameObject)Resources.Load("Prefab/Stage/Goal_Light");

        //敵リスト
        objEnemyList[0] = (GameObject)Resources.Load("Prefab/Enemy/mutant_edit");

        //アイテムリスト
        objItemList[0] = (GameObject)Resources.Load("Prefab/Item/Magic/Wand1A_edit");

        //2Dのマップチップ読み込み
        objMapTipList[0] = (GameObject)Resources.Load("Prefab/Map2D/MapTip");
    }

    //壁を生成
    void GenerateWall()
    {
        //ダンジョンマップ
        dungeonGenerator = GetComponent<DungeonGenerator>();

        //壁の高さ
        float blockHeight = 4f;

        //2Dマップ生成
        map = dungeonGenerator.Generate();

        //生成する壁の親となるGameObject
        GameObject objWall = GameObject.Find("Wall");

        //自動生成したマップにCubeを配置
        for (int i = 0; i < map.GetLength(0); i++)
        {
            for (int j = 0; j < map.GetLength(1); j++)
            {
                //壁をCubeで作成
                if (map[i, j] == 0)
                {
                    for (int k = 0; k < blockHeight; k++)
                    {
                        GameObject cube = GameObject.CreatePrimitive(PrimitiveType.Cube);

                        //Wall直下に階層を移動
                        cube.transform.parent = objWall.transform;
                        cube.GetComponent<Renderer>().material = material;
                        cube.transform.localScale = new Vector3(1, 1, 1);
                        cube.transform.position = new Vector3(i, k + 0.5f, j);
                    }
                }
            }
        }
        GetComponent<NavMeshSurface>().BuildNavMesh();
    }

    //二次元マップ生成
    void GenerateMap2D(int[,] map)
    {
        objMap2D = GameObject.Find("Map2D").gameObject;
        objMapExist = new GameObject[map.GetLength(0), map.GetLength(1)];

        //0:壁、1:部屋、2:通路
        for (int i = 0; i < 50; i++)
        {
            for (int j = 0; j < 50; j++)
            {
                objMapExist[i, j] = Instantiate(objMapTipList[0]);

                //Map2D直下に階層を移動
                objMapExist[i, j].transform.parent = objMap2D.transform;
                objMapExist[i, j].transform.localScale = new Vector3(1, 1, 1);

                //マップの位置調整 (MapTipの幅が5fのため)
                Vector2 vector2 = new Vector2(2.5f + 5f * i, 2.5f + 5f * j);
                objMapExist[i, j].GetComponent<RectTransform>().anchoredPosition = vector2;

                //初期状態では非表示
                Color color = objMapExist[i, j].GetComponent<Image>().color;
                color.a = 0;
                objMapExist[i, j].GetComponent<Image>().color = color;

                /*
                if ((map[i, j] == 1) || (map[i, j] == 2))
                {
                    //2Dマップ生成のデバッグ用
                    if ((map[i, j] == 1))
                    {
                        objMapExist[i, j].GetComponent<Image>().color = new Color(1, 0, 0, 0.5f);
                    }
                    else if ((map[i, j] == 2))
                    {
                        objMapExist[i, j].GetComponent<Image>().color = new Color(0, 1, 0, 0.5f);
                    }
                }
                */
            }
        }
    }
    
    //オブジェクト生成 (プレイヤー以外)
    void GenerateObj(int[,] map, GameObject obj)
    {
        while (true)
        {
            int mapX = UnityEngine.Random.Range(0, map.GetLength(0) - 1);
            int mapY = UnityEngine.Random.Range(0, map.GetLength(1) - 1);

            if (map[mapX, mapY] == 1)
            {
                //プレイヤーは生成済みのため移動だけ
                if (obj.CompareTag("Player") == true)
                {
                    obj.transform.position = new Vector3(mapX, 0, mapY);
                }
                //その他は生成と移動
                else
                {
                    GameObject objInstant = Instantiate(obj, new Vector3(mapX, 0, mapY), Quaternion.Euler(0f, 0f, 0f));
                }
                break;
            }
        }
    }
    
    //時間経過で敵を生成
    void AppearEnemy()
    {
        //出現上限
        if (numEnemys >= maxNumOfEnemys)
        {
            return;
        }

        //時間経過で敵を生成
        countTime += Time.deltaTime;
        if (countTime > appearNextTime)
        {
            //出現させる敵をランダムに選ぶ
            int enemyIdx = UnityEngine.Random.Range(0, objEnemyList.Length);
            GenerateObj(map, objEnemyList[enemyIdx]);

            //初期化処理
            countTime = 0f;
            numEnemys++;
        }
    }

    //プレイヤー、ゴール、敵、アイテムのマップチップ生成
    public GameObject GenerateObjMap2D(GameObject obj, GameObject objMapTipResources)
    {
        GameObject objMapTip = Instantiate(objMapTipResources);

        //Map2D直下に階層を移動
        objMapTip.transform.parent = objMap2D.transform;
        objMapTip.transform.localScale = new Vector3(1, 1, 1);

        //マップの位置調整 (MapTipの幅が5fのため)
        Vector2 vector2 = new Vector2(2.5f + 5f * obj.transform.position.x, 2.5f + 5f * obj.transform.position.z);
        objMapTip.GetComponent<RectTransform>().anchoredPosition = vector2;

        return objMapTip;
    }

    //プレイヤー、敵、アイテムのマップチップ更新
    public void UpdateMap2D(GameObject obj, GameObject objMapTip)
    {
        //マップの位置調整 (MapTipの幅が5fのため)
        Vector2 vector2 = new Vector2(2.5f + 5f * obj.transform.position.x, 2.5f + 5f * obj.transform.position.z);
        objMapTip.GetComponent<RectTransform>().anchoredPosition = vector2;
        
        //プレイヤーの場合は処理完了
        if (obj.CompareTag("Player") == true)
        {
            return;
        }
        
        //敵とアイテムは、プレイヤーと同じ部屋に居る場合に表示
        if (map[(int)(obj.transform.position.x), (int)(obj.transform.position.z)] == 10)
        {
            Color color = objMapTip.GetComponent<Image>().color;
            color.a = 0.5f;
            objMapTip.GetComponent<Image>().color = color;
        }
        //敵は、プレイヤーと同じ部屋に居ない場合に非表示
        else
        {
            if (obj.CompareTag("Enemy") == true)
            {
                Color color = objMapTip.GetComponent<Image>().color;
                color.a = 0;
                objMapTip.GetComponent<Image>().color = color;
            }
        }
    }

    //プレイヤーによる部屋探索。
    public void SearchMap(int x, int y)
    {
        //部屋を探索中の場合は処理終了
        if (map[x, y] == 10)
        {
            return;
        }

        //探索中フラグをON
        flgSearchMap = true;

        //プレイヤーの位置をキャッシュ(不要かも?)
        int maeX = x;
        int maeY = y;

        //部屋の角の位置を特定
        if (map[maeX, maeY] == 1)
        {
            while (true)
            {
                if (map[maeX - 1, maeY] == 1)
                {
                    maeX--;
                    continue;
                }
                break;
            }

            while (true)
            {
                if (map[maeX, maeY - 1] == 1)
                {
                    maeY--;
                    continue;
                }
                break;
            }

            //部屋の角の位置をキャッシュ
            int tmpX = maeX;
            int tmpY = maeY;

            //角からローラー作戦で部屋を表示する。
            for (int i = 0; i < 50; i++)
            {
                for (int j = 0; j < 50; j++)
                {
                    //map[x,y]のパラメタ:0:壁、1:部屋、2:通路、10:プレイヤーが居る部屋
                    if ((map[tmpX + i, tmpY + j] == 1) || (map[tmpX + i, tmpY + j] == 10))
                    {
                        objMapExist[tmpX + i, tmpY + j].GetComponent<Image>().color = new Color(0, 0, 0, 0.5f);
                        map[tmpX + i, tmpY + j] = 10;
                    }
                    else
                    {
                        break;
                    }
                }
                if (!((map[tmpX + i, tmpY] == 1) || (map[tmpX + i, tmpY] == 10)))
                {
                    break;
                }
            }

        }

        //プレイヤーが通路に移動したら、探索中のマップの状態を部屋に戻す。
        if (map[x, y] == 2)
        {
            for (int i = 0; i < 50; i++)
            {
                for (int j = 0; j < 50; j++)
                {
                    if ((map[i, j] == 10))
                    {
                        map[i, j] = 1;
                    }
                }
            }
        }

        //プレイヤーの位置を探索 (通路用)
        objMapExist[maeX, maeY].GetComponent<Image>().color = new Color(0, 0, 0, 0.5f);

        //探索中フラグをOFF
        flgSearchMap = false;
    }
}

◆[CharacterCtrlCommon.cs]

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class CharacterCtrlCommon : MonoBehaviour
{
    //マップ探索用
    ElementGenerator elementGenerator;                      //オブジェクト生成用
    [SerializeField] GameObject objMapTip;                  //マップチップリソース
    GameObject objMapTipFinal;                              //生成したマップチップ

    void Awake()
    {
        //マップ探索用
        elementGenerator = GameObject.Find("StageMake").GetComponent<ElementGenerator>();
    }
    
    void Start()
    {
        //マップチップ生成
        objMapTipFinal = elementGenerator.GenerateObjMap2D(gameObject, objMapTip);
        
        //プレイヤー以外は初期は非表示
        if (CompareTag("Player") == false)
        {
            Color color = objMapTipFinal.GetComponent<Image>().color;
            color.a = 0;
            objMapTipFinal.GetComponent<Image>().color = color;
        }
    }
    
    void Update()
    {
        //マップチップ更新
        elementGenerator.UpdateMap2D(gameObject, objMapTipFinal);

        //プレイヤーはマップ探索する
        if (CompareTag("Player") == true)
        {
            elementGenerator.SearchMap((int)(gameObject.transform.position.x + 0.5f), (int)(gameObject.transform.position.z + 0.5f));
        }
    }
}

◆[ItemCtrl.cs]

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class ItemCtrl : MonoBehaviour
{
    //マップ探索用
    ElementGenerator elementGenerator;                      //オブジェクト生成用
    [SerializeField] GameObject objMapTip;                  //マップチップリソース
    GameObject objMapTipFinal;                              //生成したマップチップ

    void Awake()
    {
        //マップ探索用
        elementGenerator = GameObject.Find("StageMake").GetComponent<ElementGenerator>();
    }

    void Start()
    {
        //マップチップ生成
        objMapTipFinal = elementGenerator.GenerateObjMap2D(gameObject, objMapTip);
        
        //マップチップの透明度設定
        Color color = objMapTipFinal.GetComponent<Image>().color;
        color.a = 0;
        objMapTipFinal.GetComponent<Image>().color = color;
    }

    void Update()
    {
        //マップチップ更新
        elementGenerator.UpdateMap2D(gameObject, objMapTipFinal);
    }
}

補足①「プレイヤーによる部屋探索」

プレイヤーが部屋に入ると、先ずは角位置を特定しに行きます。
そこからローラー作戦で部屋のマップチップを表示していきます。
部屋にアイテムや敵キャラクタが居たら、そのマップチップを表示します。
現状は部屋の形が四角形のみに対応しているため、部屋の形を複雑化する場合はスクリプトの修正が必要となります。

最後に

いかがでしたでしょうか。
基本的にはこれで、自動生成する不思議なダンジョンの骨格は完成です!
あとは、3Dモデルを充実するなり、床にトラップを配置するなりブラッシュアップしていく感じです。
不明点等ありましたらお気軽にコメント頂けると嬉しいです。
参考になったという方はシェアして頂けると更に喜びます。

以上、だらはでした。

 

スポンサーリンク

応用

Posted by daraha_gm