【Unity3D】自動生成する不思議なダンジョンの作り方③「キャラやアイテムの初期配置」

2022年8月26日

どうも、だらはです。
前回に引き続き、自動生成する不思議なダンジョンの作り方を紹介していきます。
今回は、キャラやアイテムの初期配置です!

スポンサーリンク

概要

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

◆完成図

◆目次

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

③キャラクタやアイテムを初期配置する。

今回はキャラクタとアイテムを生成するスクリプトを作成します。
前回作成した[ElementGenerator.cs]に追記する形で実装していきます。
因みに、今回生成するのは敵キャラとアイテムを想定しています。
(初期生成の最大最小値をLimitEnemyInitMin, LimitEnemyInitMax, LimitItemInitMin, LimitItemInitMaxで調整してください)

◆完成イメージ

◆変更後の[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;                                      //フィールド用

    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 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;
            }
        }
    }
}

補足①「Awake()とStart()の使い分け」

今回の変更にて、Awake()とは別にStart()を追加しました。
これは、2D/3Dマップを確実に生成し終わってから、GameObjectを配置していきたいためです。
このように処理の順番を制御したい場合は、Awake()とStart()を使い分けやると良いです。
処理の順番について、詳細が気になる方はUnityの公式マニュアルにフローチャートが準備されていますので参照頂けたらと思います。

補足②「3Dモデルのサンプル」

本スクリプトを実行するうえで、3Dモデルのサンプルを準備するのに困ることと思います。
そんな時は、商用利用ができて無料のMixamoを利用すると良いです。
具体的なダウンロード手順を以下にまとめてありますので、よろしければ参照ください。

◆Mixamoについて

最後に

いかがでしたでしょうか。
今回はキャラクタやアイテムの初期配置に対応しました。
次回は時間経過で敵を生成していきます。

◆次回「時間経過で敵の自動生成」

以上、だらはでした。

スポンサーリンク

応用

Posted by daraha_gm