LoginSignup
55
54

More than 5 years have passed since last update.

Unityで不思議のダンジョン生成(ランダム性高め)

Last updated at Posted at 2018-08-26

概要

既存のアルゴリズムでは解決できず、高度なことをせずに出来るのでは無いかと思い作成。
思ったよりもいい感じだったので公開。広まれ……広まれ……

目標と結果

  • 部屋と部屋が重なった状態で生成 ->解決
  • 数字を諸々指定するだけで全部生成 ->解決
  • 部屋分割法ではある程度ダンジョンの形が決まってしまうので、それを回避 ->解決

説明

全て壁なダンジョンを作る

  • 二次元配列を作って、それにすべて壁というデータを入れる。
  • それをもとに生成

部屋を作る

  • 二次元配列のデータをいじって、いい感じに部屋を作る

道を作る

  • ある一点に向かってすべての部屋から道を伸ばす

コードと結果

全て壁なダンジョンを作る

  • Wall(GameObject)はプレハブ化しておいてアタッチしておく。(作り方やアタッチの方法は省略)
  • ダンジョンの大きさを変えたいときはMapWidthとMapHeightを変更
SystemManager.cs
public class SystemManager : MonoBehaviour {

    const int MapWidth = 50;
    const int MapHeight = 50;

    public int[,] Map;

    const int wall = 9;
    const int road = 0;

    public GameObject WallObject;

    void Start () {
        ResetMapData();
        CreateDangeon();
    }

    /// <summary>
    /// Mapの二次元配列の初期化
    /// </summary>
    private void ResetMapData() {
        Map = new int[MapHeight, MapWidth];
        for (int i = 0; i < MapHeight; i++) {
            for (int j = 0; j < MapWidth; j++) {
                Map[i, j] = wall;
            }
        }
    }

    /// <summary>
    /// マップデータをもとにダンジョンを生成
    /// </summary>
    private void CreateDangeon() {
        for (int i = 0; i < MapHeight; i++) {
            for (int j = 0; j < MapWidth; j++) {
                if (Map[i, j] == wall) {
                    Instantiate(WallObject, new Vector3(j - MapWidth/2, i - MapHeight/2, 0), Quaternion.identity);
                }
            }
        }
    }

最初.png

部屋を作る

  • 部屋の大きさを変えたいときはroom~を変更
  • 部屋の数を変更したいときはRoom~を変更
  • 道を考えた方法にしているため、boolで返しているが道の生成アルゴリズム
  • 既に書いてある部分は省略
SystemManager.cs
public class SystemManager : MonoBehaviour {

    const int roomMinHeight = 5;
    const int roomMaxHeight = 10;

    const int roomMinWidth = 5;
    const int roomMaxWidth = 10;

    const int RoomCountMin = 10;
    const int RoomCountMax = 15;

    void Start () {

        ResetMapData();

        CreateSpaceData();

        CreateDangeon();

    }

    /// <summary>
    /// 空白部分のデータを変更
    /// </summary>
    private void CreateSpaceData() {
        int roomCount = Random.Range(RoomCountMin, RoomCountMax);
        for (int i = 0; i < roomCount; i++) {
            int roomHeight = Random.Range(roomMinHeight, roomMaxHeight);
            int roomWidth = Random.Range(roomMinWidth, roomMaxWidth);
            int roomPointX = Random.Range(2, MapWidth - roomMaxWidth - 2);
            int roomPointY = Random.Range(2, MapWidth - roomMaxWidth - 2);

            int roadStartPointX = Random.Range(roomPointX, roomPointX + roomWidth);
            int roadStartPointY = Random.Range(roomPointY, roomPointY + roomHeight);

            bool isRoad = CreateRoomData(roomHeight, roomWidth, roomPointX, roomPointY);            
        }
    }

    /// <summary>
    /// 部屋データを生成。すでに部屋がある場合はtrueを返し、道を作らないようにする
    /// </summary>
    /// <param name="roomHeight">部屋の高さ</param>
    /// <param name="roomWidth">部屋の横幅</param>
    /// <param name="roomPointX">部屋の始点(x)</param>
    /// <param name="roomPointY">部屋の始点(y)</param>
    /// <returns></returns>
    private bool CreateRoomData(int roomHeight, int roomWidth, int roomPointX, int roomPointY) {
        bool beCreateRoad = false;
        for (int i = 0; i < roomHeight; i++) {
            for (int j = 0; j < roomWidth; j++) {
                if (Map[roomPointY + i, roomPointX + j] == road) {
                    beCreateRoad = true;
                } else {
                    Map[roomPointY + i, roomPointX + j] = road;
                }
            }
        }
        return beCreateRoad;
    }

}

2.png
赤枠部分が良い感じ。ランダムなので完成形がかなりばらけるが、そこもまた良い
ランダムなので、部屋が一つだけ(重なって生成される)のもありうる

道を作る(コード完成版)

  • ある点に向けて道を掘り進めるだけ。
  • 横に行ってから縦に行くか、その逆かというので分岐。
  • 複数の点を作って、その中から(ランダム/最短)な点を探そうと思ったがマップが小さければ一点だけでもそれっぽいので現時点ではこれで
  • 上記の複数の点も結ぶ方法は同様にCreateRoadDataを使えばできるはず。
SystemManager.cs
public class SystemManager : MonoBehaviour {

    static int MapWidth = 50;
    static int MapHeight = 50;

    int[,] Map;

    const int wall = 9;
    const int road = 0;

    public GameObject WallObject;

    const int roomMinHeight = 5;
    const int roomMaxHeight = 10;

    const int roomMinWidth = 5;
    const int roomMaxWidth = 10;

    const int RoomCountMin = 10;
    const int RoomCountMax = 15;

    //道の集合点を増やしたいならこれを増やす
    const int meetPointCount = 1;

    void Start () {

        ResetMapData();

        CreateSpaceData();

        CreateDangeon();

    }

    // Update is called once per frame
    void Update () {

    }

    /// <summary>
    /// Mapの二次元配列の初期化
    /// </summary>
    private void ResetMapData() {
        Map = new int[MapHeight, MapWidth];
        for (int i = 0; i < MapHeight; i++) {
            for (int j = 0; j < MapWidth; j++) {
                Map[i, j] = wall;
            }
        }
    }

    /// <summary>
    /// 空白部分のデータを変更
    /// </summary>
    private void CreateSpaceData() {
        int roomCount = Random.Range(RoomCountMin, RoomCountMax);

        int[] meetPointsX = new int[meetPointCount];
        int[] meetPointsY = new int[meetPointCount];
        for (int i = 0; i < meetPointsX.Length; i++) {
            meetPointsX[i] = Random.Range(MapWidth / 4, MapWidth * 3 / 4);
            meetPointsY[i] = Random.Range(MapHeight / 4, MapHeight * 3 / 4);
            Map[meetPointsY[i], meetPointsX[i]] = road;
        }

        for (int i = 0; i < roomCount; i++) {
            int roomHeight = Random.Range(roomMinHeight, roomMaxHeight);
            int roomWidth = Random.Range(roomMinWidth, roomMaxWidth);
            int roomPointX = Random.Range(2, MapWidth - roomMaxWidth - 2);
            int roomPointY = Random.Range(2, MapWidth - roomMaxWidth - 2);

            int roadStartPointX = Random.Range(roomPointX, roomPointX + roomWidth);
            int roadStartPointY = Random.Range(roomPointY, roomPointY + roomHeight);

            bool isRoad = CreateRoomData(roomHeight, roomWidth, roomPointX, roomPointY);

            if (isRoad == false) {
                CreateRoadData(roadStartPointX, roadStartPointY, meetPointsX[Random.Range(0,0)], meetPointsY[Random.Range(0, 0)]);
            }
        }


    }

    /// <summary>
    /// 部屋データを生成。すでに部屋がある場合はtrueを返し、道を作らないようにする
    /// </summary>
    /// <param name="roomHeight">部屋の高さ</param>
    /// <param name="roomWidth">部屋の横幅</param>
    /// <param name="roomPointX">部屋の始点(x)</param>
    /// <param name="roomPointY">部屋の始点(y)</param>
    /// <returns></returns>
    private bool CreateRoomData(int roomHeight, int roomWidth, int roomPointX, int roomPointY) {
        bool isRoad = false;
        for (int i = 0; i < roomHeight; i++) {
            for (int j = 0; j < roomWidth; j++) {
                if (Map[roomPointY + i, roomPointX + j] == road) {
                    isRoad = true;
                } else {
                    Map[roomPointY + i, roomPointX + j] = road;
                }
            }
        }
        return isRoad;
    }

    /// <summary>
    /// 道データを生成
    /// </summary>
    /// <param name="roadStartPointX"></param>
    /// <param name="roadStartPointY"></param>
    /// <param name="meetPointX"></param>
    /// <param name="meetPointY"></param>
    private void CreateRoadData(int roadStartPointX, int roadStartPointY, int meetPointX, int meetPointY) {

        bool isRight;
        if (roadStartPointX > meetPointX) {
            isRight = true;
        } else {
            isRight = false;
        }
        bool isUnder;
        if (roadStartPointY > meetPointY) {
            isUnder = false;
        } else {
            isUnder = true;
        }

        if(Random.Range(0,2) == 0) {

            while (roadStartPointX != meetPointX) {

                Map[roadStartPointY, roadStartPointX] = road;
                if (isRight == true) {
                    roadStartPointX--;
                } else {
                    roadStartPointX++;
                }

            }

            while(roadStartPointY != meetPointY) {

                Map[roadStartPointY, roadStartPointX] = road;
                if(isUnder == true) {
                    roadStartPointY++;
                } else {
                    roadStartPointY--;
                }
            }

        } else {

            while (roadStartPointY != meetPointY) {

                Map[roadStartPointY, roadStartPointX] = road;
                if (isUnder == true) {
                    roadStartPointY++;
                } else {
                    roadStartPointY--;
                }
            }

            while (roadStartPointX != meetPointX) {

                Map[roadStartPointY, roadStartPointX] = road;
                if (isRight == true) {
                    roadStartPointX--;
                } else {
                    roadStartPointX++;
                }

            }

        }
    }

    /// <summary>
    /// マップデータをもとにダンジョンを生成
    /// </summary>
    private void CreateDangeon() {
        for (int i = 0; i < MapHeight; i++) {
            for (int j = 0; j < MapWidth; j++) {
                if (Map[i, j] == wall) {
                    Instantiate(WallObject, new Vector3(j - MapWidth/2, i - MapHeight/2, 0), Quaternion.identity);
                }
            }
        }
    }
}

3.png
良い感じ。

問題点

部屋数増やすとイマイチ問題

4.png

  • 集合点を複数作り部屋ごとに最短集合点を目指す
  • 集合点ごとに道をつなげる 

というほうが見栄えは良さそう。あとでまた書きます。

部屋数少なくなる問題

  • マップサイズ小さめだと重なるので部屋数が少なくなる
  • 2、3部屋だけという制限が欲しいが現状だと重なって一部屋になる可能性がある

分割法使ってください。現状大きめで自作するのが大変なマップのためのアルゴリズムを目指しているので、小さくまとまったマップを作るためのアルゴリズムなら別のものがいいと思います

55
54
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
55
54