ハイクラス:マップの自動生成

基本的にはこちらの記事をほぼほぼ使っている

準備:独自クラスを作成

僕も0からこれらを作ったわけではなく、参考記事の内容を見ながら作成しています。

マップを構成する2次元配列を自作

using UnityEngine;

public class Layer2D
{
    // マップの情報を保持する配列
    int width;
    int height;
    // 範囲外の座標にアクセスしたときの値
    int outOfRangeValue = -1;
    int[,] value = null; // マップの情報を保持する配列:-1なら壁、0なら通路

    // プロパティ
    public int Width { get { return width; } }
    public int Height { get { return height; } }

    // コンストラクタ
    public Layer2D(int width, int height)
    {
        this.width = width;
        this.height = height;
        value = new int[width, height];
    }

    // 領域外の座標かどうかを判定する
    public bool IsOutOfRange(int x, int y)
    {
        if(x < 0 || x >= width || y < 0 || y >= height)
        {
            return true;
        }
        return false;
    }

    // 指定した座標の値を取得する
    public int Get(int x, int y)
    {
        if(IsOutOfRange(x, y))
        {
            return outOfRangeValue;
        }
        return value[x, y];
    }
    // 指定した座標に値を設定する
    public void Set(int x, int y, int v)
    {
        if(IsOutOfRange(x, y))
        {
            return;
        }
        value[x, y] = v;
    }

    // すべての要素に対して処理を行う:埋める
    public void Fill(int val)
    {
        for(int x = 0; x < width; x++)
        {
            for(int y = 0; y < height; y++)
            {
                Set(x, y, val);
            }
        }
    }

    // 矩形領域を指定して値を埋める:部屋を作るときに使う
    public void FillRect(int x, int y, int w, int h, int val)
    {
        for(int i = x; i < x + w; i++)
        {
            for(int j = y; j < y + h; j++)
            {
                Set(i, j, val);
            }
        }
    }

    // 矩形領域を指定して値を取得する:左,上,右,下の4点指定
    public void FillRectLTRB(int left, int top, int right, int bottom, int val)
    {
        FillRect(left, top, right - left, bottom - top, val);
    }
}

※_values[0][1]で(0,1)のデータが壁なのか道なのかなどわかる

※TopはBottomよりしたになる!?

外周の情報/部屋の情報を管理するクラス

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

// ダンジョンの区画管理クラス:外周や部屋の矩形を管理する
public class DgDivision
{
    // 矩形管理クラス
    public class DgRect
    {
        // 矩形(四角形)を決める4つの辺
        public int Left { get; private set; }
        public int Top { get; private set; }
        public int Right { get; private set; }
        public int Bottom { get; private set; }

        // 値をまとめて設定する
        public void Set(int l, int t, int r, int b)
        {
            Left = l;
            Top = t;
            Right = r;
            Bottom = b;
        }

        // 矩形の幅
        public int Width
        {
            get { return Right - Left; }
        }
        // 矩形の高さ
        public int Height
        {
            get { return Bottom - Top; }
        }
    }

    // 外周の矩形情報
    public DgRect Outer;

    // 部屋の矩形情報
    public DgRect Room;

    // 道の矩形情報
    public DgRect Road;

    // コンストラクタ
    public DgDivision()
    {
        Outer = new DgRect();
        Room = new DgRect();
        Road = new DgRect();
    }
}

ダンジョン生成を行うクラス

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

public class DgGenerator : MonoBehaviour
{
    Layer2D layer2D = null;

    List<DgDivision> divList = null;

    // ここから処理を追加していきます
}

ダンジョン自動生成の手順

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using static UnityEditor.Experimental.GraphView.GraphView;

public class DgGenerator : MonoBehaviour
{
    Layer2D layer2D = null;

    List<DgDivision> divList = null;

    // ここから処理を追加していきます

    const int WIDTH = 20; // マップの幅
    const int HEIGHT = 20; // マップの高さ

    const int CHIP_WALL = -1; // 壁

    void Generate()
    {
        // ■1. 初期化
        // 2次元配列初期化
        layer2D = new Layer2D(WIDTH, HEIGHT);

        // 区画リスト作成
        divList = new List<DgDivision>();

        // ■2. すべてを壁にする
        layer2D.Fill(CHIP_WALL);

        // ■3. 最初の区画を作る
        CreateDivision(0, 0, WIDTH - 1, HEIGHT - 1);

        // ■4. 区画を分割する
        // 垂直 or 水平分割フラグの決定
        bool bVertical = (Random.Range(0, 2) == 0);
        SplitDivison(bVertical);

        // ■5. 区画に部屋を作る
        CreateRoom();

        // ■6. 部屋同士をつなぐ
        ConnectRooms();
    }

    void CreateDivision(int left, int top, int right, int bottom)
    {
    }

    void SplitDivison(bool bVertical)
    {
    }
    void CreateRoom()
    {
    }
    void ConnectRooms()
    {
    }
}

区画を作る関数(この区画を分割していく)

    // 区画を作る
    void CreateDivision(int left, int top, int right, int bottom)
    {
        DgDivision div = new DgDivision();
        div.Outer.Set(left, top, right, bottom);
        divList.Add(div);
    }

区画の分割

分割方法等すべてこのサイトが優秀なのでここを参考にする

考え方

    // 区画を分割する(trueなら縦方向、falseなら横方向)
    void SplitDivison(bool bVertical)
    {
        // 末尾の要素を取り出す:なぜ? => 末尾の要素を取り出すことで、最後に作った区画を分割したいから
        DgDivision lastDivigion = divList[divList.Count - 1];
        divList.Remove(lastDivigion);

        // 子となる区画を作る
        DgDivision childDivision = new DgDivision();
        // 縦方向に分割
        if (bVertical)
        {
            // もう分割できない場合は終了
            if (!CheckDivisionSize(lastDivigion.Outer.Width))
            {
                // 末尾に戻す
                divList.Add(lastDivigion);
                return;
            }

            // 分割位置を決める
            // 区画の幅から最小サイズを引いて、その範囲でランダムに決める
            int pointA = lastDivigion.Outer.Left + (MIN_ROOM_SIZE + OUTER_MERGIN);
            int pointB = lastDivigion.Outer.Right - (MIN_ROOM_SIZE + OUTER_MERGIN);


            // ABの距離を求める
            int distance = pointB - pointA;
            // 最大の部屋サイズを超えないようにする
            distance = Mathf.Min(distance, MAX_ROOM_SIZE);
            // 分割位置を決める
            int splitPoint = Random.Range(pointA, pointA + distance + 1);

            // 子の区画の設定
            childDivision.Outer.Set(lastDivigion.Outer.Left, lastDivigion.Outer.Top, splitPoint, lastDivigion.Outer.Bottom);

            // 親の区画の修正
            lastDivigion.Outer.Set(splitPoint, lastDivigion.Outer.Top, lastDivigion.Outer.Right, lastDivigion.Outer.Bottom);
        }
        else
        {
            // 横方向に分割
            if (!CheckDivisionSize(lastDivigion.Outer.Height))
            {
                divList.Add(lastDivigion);
                return;
            }

            int pointA = lastDivigion.Outer.Top + (MIN_ROOM_SIZE + OUTER_MERGIN);
            int pointB = lastDivigion.Outer.Bottom - (MIN_ROOM_SIZE + OUTER_MERGIN);
            int distance = pointB - pointA;
            distance = Mathf.Min(distance, MAX_ROOM_SIZE);
            int splitPoint = Random.Range(pointA, pointA + distance + 1);

            childDivision.Outer.Set(lastDivigion.Outer.Left, lastDivigion.Outer.Top, lastDivigion.Outer.Right, splitPoint);
            lastDivigion.Outer.Set(lastDivigion.Outer.Left, splitPoint, lastDivigion.Outer.Right, lastDivigion.Outer.Bottom);
        }

        // 次に分割する区画をランダムに決める:最後に追加した方が分割対象となる
        if (Random.Range(0, 2) == 0)
        {
            divList.Add(lastDivigion);
            divList.Add(childDivision);
        }
        else
        {
            divList.Add(childDivision);
            divList.Add(lastDivigion);
        }

        // 再帰的に分割する
        SplitDivison(!bVertical);
    }

    const int MIN_ROOM_SIZE = 4;
    const int MAX_ROOM_SIZE = 6;
    const int MIN_ROOM_SIZE = 4;
    const int OUTER_MERGIN = 2;
    const int POS_MERGIN = 2;


    bool CheckDivisionSize(int size)
    {
        // 2分割するための最低サイズ
        // 2倍して、道を作るための最低サイズを確保する
        return size >= 2 * (MIN_ROOM_SIZE + OUTER_MERGIN) + 1;
    }

区画内に部屋を作る関数

考え方

・区画から基準のサイズを決める
・部屋の大きさをランダムに決める
・部屋が最大サイズを超えないようにする
・空きスペースを計算:区画のサイズから部屋のサイズを引いたもの
・部屋の左は区画の左から、空きスペースの範囲でランダムに決める
・部屋の右は左+部屋の幅
・部屋の上は区画の上から、空きスペースの範囲でランダムに決める
・部屋の下は上+部屋の高さ
・部屋の設定
・部屋を道で埋める

    // 各区画に部屋を作る
 void CreateRoom()
 {
     // 各区画に対して
     foreach(DgDivision div in divList)
     {
         // 基準サイズを決める:区画のサイズから余白を引いたもの
         int baseWidth = div.Outer.Width - OUTER_MERGIN;
         int baseHeight = div.Outer.Height - OUTER_MERGIN;
         // 大きさをランダムに決める
         int sizeWith = Random.Range(MIN_ROOM_SIZE, baseWidth);
         int sizeHeight = Random.Range(MIN_ROOM_SIZE, baseHeight);
         // 最大サイズを超えないようにする
         sizeWith = Mathf.Min(sizeWith, MAX_ROOM_SIZE);
         sizeHeight = Mathf.Min(sizeHeight, MAX_ROOM_SIZE);

         // 空きスペースを計算:基準サイズから部屋のサイズを引いたもの
         int spaceWidth = baseWidth - sizeWith;
         int spaceHeight = baseHeight - sizeHeight;
         // 部屋の左は区画の左から、空きスペースの範囲でランダムに決める
         int left = div.Outer.Left + Random.Range(0, spaceWidth) + POS_MERGIN;
         // 部屋の右は左+部屋の幅
         int right = left + sizeWith;
         // 部屋の上は区画の上から、空きスペースの範囲でランダムに決める
         int top = div.Outer.Top + Random.Range(0, spaceHeight) + POS_MERGIN;
         // 部屋の下は上+部屋の高さ
         int bottom = top + sizeHeight;

         // 部屋の設定
         div.Room.Set(left, top, right, bottom);
         // 部屋を道で埋める
         FillRoom(div.Room);
     }
 }
    void FillRoom(DgDivision.DgRect room)
    {
        layer2D.FillRectLTRB(room.Left, room.Top, room.Right, room.Bottom, CHIP_ROAD);
    }

部屋同士をつなげる通路を作る

考え方

1.隣接する部屋を選ぶ


2.部屋から通路を伸ばす場所をランダムに決める(部屋の幅からランダムに値を決めればOK)

3.通路を部屋から外周の位置まで伸ばす


4.伸ばした先を外周に沿って接続する通路を作る

    void ConnectRooms()
    {
        // 接続する2つの部屋を決める:隣接する部屋は接続できる
        for (int i = 0; i < divList.Count - 1; i++) 
        {
            DgDivision dgDivisionA = divList[i];
            DgDivision dgDivisionB = divList[i + 1];

            // 2つの部屋を接続する
            CreateRoad(dgDivisionA, dgDivisionB);
        }
    }

    // 部屋同士をつなぐ
    void CreateRoad(DgDivision dgDivisionA, DgDivision dgDivisionB)
    {
        // 部屋がどの面で接しているかを調べる
        if (dgDivisionA.Outer.Bottom == dgDivisionB.Outer.Top || dgDivisionA.Outer.Top == dgDivisionB.Outer.Bottom)
        {
            // 上下に接している場合
            CreateVerticalRoad(dgDivisionA, dgDivisionB);
            return;
        }
        else if(dgDivisionA.Outer.Right == dgDivisionB.Outer.Left || dgDivisionA.Outer.Left == dgDivisionB.Outer.Right)
        {
            // 左右に接している場合
            CreateHorizontalRoad(dgDivisionA, dgDivisionB);
            return;
        }
    }

    // 部屋から縦方向の道を作る
    void CreateVerticalRoad(DgDivision dgDivisionA, DgDivision dgDivisionB)
    {
        // 部屋の横幅から道を作る点を決める
        int pointXA = Random.Range(dgDivisionA.Room.Left, dgDivisionA.Room.Right);
        int pointXB = Random.Range(dgDivisionB.Room.Left, dgDivisionB.Room.Right);
        int pointY = 0;
        // Aが上にある場合
        if(dgDivisionA.Outer.Top > dgDivisionB.Outer.Top)
        {
            pointY = dgDivisionA.Outer.Top;
            dgDivisionA.CreateRoad(pointXA, pointY, pointXA + 1, dgDivisionA.Room.Bottom);
            dgDivisionB.CreateRoad(pointXB, dgDivisionB.Room.Bottom+1, pointXB + 1, pointY);
        }
        else
        {
            // Bが上にある場合
            pointY = dgDivisionB.Outer.Top;
            dgDivisionB.CreateRoad(pointXB, pointY, pointXB + 1, dgDivisionB.Room.Bottom);
            dgDivisionA.CreateRoad(pointXA, dgDivisionA.Room.Bottom + 1, pointXA + 1, pointY);
        }
        FillRoom(dgDivisionA.Road);
        FillRoom(dgDivisionB.Road);
        // 通路をつなぐ
        FillHLine(pointXA, pointXB, pointY);
    }

    void FillHLine(int left, int right, int y)
    {
        if (left > right)
        {
            // 左右の位置関係が逆なので値をスワップする
            int tmp = left;
            left = right;
            right = tmp;
        }
        layer2D.FillRectLTRB(left, y, right + 1, y + 1, CHIP_ROAD);
    }

    void FillVLine(int top, int bottom, int x)
    {
        if (top > bottom)
        {
            // 上下の位置関係が逆なので値をスワップする
            int tmp = top;
            top = bottom;
            bottom = tmp;
        }
        layer2D.FillRectLTRB(x, top, x + 1, bottom + 1, CHIP_ROAD);
    }

    void CreateHorizontalRoad(DgDivision dgDivisionA, DgDivision dgDivisionB)
    {
        // 部屋の縦幅から道を作る点を決める
        int pointYA = Random.Range(dgDivisionA.Room.Top, dgDivisionA.Room.Bottom);
        int pointYB = Random.Range(dgDivisionB.Room.Top, dgDivisionB.Room.Bottom);
        int pointX = 0;
        // Aが左にある場合
        if (dgDivisionA.Outer.Left < dgDivisionB.Outer.Left)
        {
            pointX = dgDivisionB.Outer.Left;
            dgDivisionB.CreateRoad(pointX, pointYB, dgDivisionB.Room.Left, pointYB + 1);
            dgDivisionA.CreateRoad(dgDivisionA.Room.Right, pointYA, pointX, pointYA + 1);
        }
        else
        {
            pointX = dgDivisionA.Outer.Left;
            dgDivisionA.CreateRoad(pointX, pointYA, dgDivisionA.Room.Left, pointYA + 1);
            dgDivisionB.CreateRoad(dgDivisionB.Room.Right, pointYB, pointX, pointYB + 1);
        }
        FillRoom(dgDivisionA.Road);
        FillRoom(dgDivisionB.Road);
        // 通路をつなぐ
        FillVLine(pointYA, pointYB, pointX);
    }
    // 道を作る(DgDivision.csのコード)
    public void CreateRoad(int left, int top, int right, int bottom)
    {
        Road = new DgRect();
        Road.Set(left, top, right, bottom);
    }

次回:ダンジョンにPlayerを歩かせる&壁判定

タイトルとURLをコピーしました