基本的にはこちらの記事をほぼほぼ使っている
準備:独自クラスを作成
僕も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を歩かせる&壁判定