不管是在迷宮仍是相似於地牢的遊戲地圖中,利用程序來生成每次都不同的地圖是一件叫人興奮不已的事。數據結構
這時咱們須要解決兩個很是重要的隨機事件:dom
1.在必定範圍內隨機出各不相同但又不能互相重疊的房間ide
2.優美生成鏈接這些房間的通道ui
基本的UML思路圖:spa
此次咱們先討論如何快速生成符合各類隨機要求的房間。code
通常來講,一個房間的高度是一個相對固定的值,能夠根據面板上的參數進行必要的調整,而真正參與隨機的應該是房間的長,寬和位置。orm
創建房間的數據結構,根據需求能夠隨時補充和添加:blog
1 using System.Collections.Generic; 2 using UnityEngine; 3 4 public class RoomData 5 { 6 public int Id; 7 //房間的Transform等屬性 8 public RoomTran RoomTran; 9 //該房間的戰鬥類型 10 public RoomBattleType BattleType; 11 //該房間與哪些其他房間互相鏈接 12 public List<RoomData> CrossRooms; 13 //房間內的怪物列表 14 public List<GameObject> Monsters; 15 //是不是端點房間 16 public bool bEndRoom; 17 //是不是主路徑房間 18 public bool bMainCrossRoom; 19 } 20 21 public class RoomTran 22 { 23 public int Length; 24 public int Width; 25 //長寬中心點 26 public Vector2Int CenterPos; 27 //高度位置 28 public float PosY; 29 } 30 31 public enum RoomBattleType 32 { 33 Rest, 34 NormalBattle, 35 BossBattle 36 }
RoonBuilder屬性和控制參數:遊戲
1 //建築單位方塊 2 public GameObject BuildUnit; 3 4 //房間高度值 5 public int FixedUnitHeight; 6 //生成的房間層數 7 public int LayerCount; 8 //長寬隨機範圍 9 public Vector2Int GenRange; 10 11 //隨機類型 12 public RoomRandType RandType; 13 //隨機的房間形狀類型 14 public RoomShapeType Shape; 15 16 //房間大小的隨機列表,用於枚舉隨機 17 public List<Vector2Int> RoomRandSizes = new List<Vector2Int>(); 18 19 //隨機的房間最大面積 20 public int MaxRoomArea; 21 //最大隨機數量(隨機試驗次數) 22 public int MaxRoomCount; 23 24 //最小邊長度 25 private int MinRoomEdge; 26 //最大長寬比 27 public int MaxLengthWidthScale = 2; 28 29 //標準方向 30 Vector3Int Dx = new Vector3Int(1, 0, 0); 31 Vector3Int Dy = new Vector3Int(0, 1, 0); 32 Vector3Int Dz = new Vector3Int(0, 0, 1); 33 34 //建築單位標籤 35 const string S_TAG = "Unit"; 36 37 private MapSystem MapManager;
單房間輪廓生成:事件
1 /// <summary> 2 /// 生成單一房間的輪廓 3 /// </summary> 4 /// <param name="centerPos">房間中點位置</param> 5 /// <param name="length">長</param> 6 /// <param name="width">寬</param> 7 /// <param name="parent">父物體</param> 8 void GenOneRoom(Vector3 centerPos, int length, int width, Transform parent = null) 9 { 10 var to = new Vector3(length - 1, FixedUnitHeight - 1, width - 1) * .5f; 11 12 //頂點 13 var ned = centerPos - to; 14 var fod = centerPos + to; 15 16 var v3 = new Vector3(ned.x, fod.y, ned.z); 17 var v4 = new Vector3(ned.x, fod.y, fod.z); 18 var v5 = new Vector3(ned.x, ned.y, fod.z); 19 20 var v6 = new Vector3(fod.x, ned.y, ned.z); 21 var v7 = new Vector3(fod.x, ned.y, fod.z); 22 var v8 = new Vector3(fod.x, fod.y, ned.z); 23 24 //頂點位置(8個) 25 InsSetPos(ned, parent); 26 InsSetPos(fod, parent); 27 InsSetPos(v3, parent); 28 InsSetPos(v4, parent); 29 InsSetPos(v5, parent); 30 InsSetPos(v6, parent); 31 InsSetPos(v7, parent); 32 InsSetPos(v8, parent); 33 34 //12條棱(4*3) 35 //長 36 InsOneEdge(length, ned, Dx, parent); 37 InsOneEdge(length, v3, Dx, parent); 38 InsOneEdge(length, v4, Dx, parent); 39 InsOneEdge(length, v5, Dx, parent); 40 //高 41 InsOneEdge(FixedUnitHeight, ned, Dy, parent); 42 InsOneEdge(FixedUnitHeight, v5, Dy, parent); 43 InsOneEdge(FixedUnitHeight, v6, Dy, parent); 44 InsOneEdge(FixedUnitHeight, v7, Dy, parent); 45 //寬 46 InsOneEdge(width, ned, Dz, parent); 47 InsOneEdge(width, v3, Dz, parent); 48 InsOneEdge(width, v6, Dz, parent); 49 InsOneEdge(width, v8, Dz, parent); 50 } 51 52 //生成一條邊上的建築單位但不包含頂點位置 53 void InsOneEdge(int edge, Vector3 v, Vector3 dir, Transform parent = null) 54 { 55 //忽略首尾單位 56 for (int i = 1; i < edge - 1; i++) 57 { 58 InsSetPos(v + i * dir, parent); 59 } 60 } 61 62 void InsSetPos(Vector3 pos, Transform parent = null) 63 { 64 var ins = Instantiate(BuildUnit); 65 ins.transform.position = pos; 66 ins.transform.parent = parent; 67 }
這裏惟一值得注意的地方是房間頂點位置的單位不要重複生成。(由於想偷懶的話真的很容易重複Orz)。
隨機RoomTran結構:
1 RoomTran RanRoomTran(Vector3 centerPos) 2 { 3 var rt = new RoomTran(); 4 5 switch (RandType) 6 { 7 case RoomRandType.AllRand: 8 int temp; 9 var oe = MaxRoomArea / MinRoomEdge; 10 switch (Shape) 11 { 12 case RoomShapeType.LengthMain: 13 rt.Length = Random.Range(MinRoomEdge + 1, oe + 1); 14 temp = MaxRoomArea / rt.Length; 15 if (temp >= rt.Length) 16 rt.Width = Random.Range(MinRoomEdge, rt.Length); 17 else 18 rt.Width = Random.Range(MinRoomEdge, temp + 1); 19 break; 20 case RoomShapeType.WidthMain: 21 rt.Width = Random.Range(MinRoomEdge + 1, oe + 1); 22 temp = MaxRoomArea / rt.Width; 23 if (temp >= rt.Width) 24 rt.Length = Random.Range(MinRoomEdge, rt.Width); 25 else 26 rt.Length = Random.Range(MinRoomEdge, temp + 1); 27 break; 28 case RoomShapeType.Coustom: 29 rt.Length = Random.Range(MinRoomEdge, oe + 1); 30 temp = MaxRoomArea / rt.Length; 31 rt.Width = Random.Range(MinRoomEdge, temp + 1); 32 break; 33 } 34 break; 35 case RoomRandType.EnumRand: 36 var rc = RoomRandSizes.Count; 37 if (rc == 0) 38 { 39 //未填寫時設定隨機默認值 40 rt.Length = 3; 41 rt.Width = 3; 42 } 43 else 44 { 45 var ridx = Random.Range(0,rc); 46 var t = RoomRandSizes[ridx]; 47 if (t.x < 3 || t.y < 3) 48 { 49 //填寫錯誤時設定隨機默認值 50 rt.Length = 3; 51 rt.Width = 3; 52 } 53 else 54 { 55 switch (Shape) 56 { 57 case RoomShapeType.LengthMain: 58 rt.Length = t.x > t.y ? t.x : t.y; 59 rt.Width = t.x < t.y ? t.x : t.y; 60 break; 61 case RoomShapeType.WidthMain: 62 rt.Width = t.x > t.y ? t.x : t.y; 63 rt.Length = t.x < t.y ? t.x : t.y; 64 break; 65 case RoomShapeType.Coustom: 66 rt.Length = Random.value < .5f ? t.x : t.y; 67 rt.Width = t.y == rt.Length ? t.x : t.y; 68 break; 69 } 70 } 71 } 72 break; 73 } 74 75 rt.CenterPos = new Vector2Int(Random.Range((int)(centerPos.x - GenRange.x * .5f), (int)(centerPos.x + GenRange.x * .5f)), 76 Random.Range((int)(centerPos.z - GenRange.y * .5f), (int)(centerPos.z + GenRange.y * .5f))); 77 78 rt.PosY = centerPos.y; 79 80 var roomCenter = new Vector3(rt.CenterPos.x, rt.PosY, rt.CenterPos.y); 81 82 //射線檢測重疊 83 if (RayRoomCheck(roomCenter, rt.Length, rt.Width)) 84 { 85 return null; 86 } 87 return rt; 88 }
用的是射線檢測重疊,生成了重疊的房間就會被視做是一次失敗的隨機試驗,以前嘗試過直接用物理系統推開失敗了,多是使用有誤,若是有知道緣由的歡迎與筆者分享,共同進步;
固然了,你也能夠用Unity自帶的Bounds.Intersects(Bounds other)方法來判斷兩個生成的房間盒子是否重疊,但缺點是沒有辦法控制兩個房間邊緣的間隔的最小距離,用純粹的數學方法判斷三個軸向的邊緣值大小一樣也是可行的:
1 //生成房間前射線檢測下範圍內有無其餘房間 2 bool RayRoomCheck(Vector3 cp, int length, int width) 3 { 4 bool result = false; 5 //長寬至少留一格間隙,高度與地板格對齊 6 var to = new Vector3(length + 1, FixedUnitHeight - 1, width + 1) * .5f; 7 var ned = cp - to; 8 9 var vx2 = ned + new Vector3(0, 0, width + 1) * .5f; 10 var vx3 = ned + new Vector3(0, 0, width + 1); 11 12 var vx4 = ned + new Vector3(length + 1, 0, width * .5f + .5f); 13 var vx5 = ned + new Vector3(length + 1, 0, width + 1); 14 15 var vz2 = ned + new Vector3(length + 1, 0, 0) * .5f; 16 var vz3 = ned + new Vector3(length + 1, 0, 0); 17 18 var vz4 = ned + new Vector3(length * .5f + .5f, 0, width + 1); 19 var vz5 = ned + new Vector3(length + 1, 0, width + 1); 20 21 result = 22 //4組射線,每組3條 23 RayCast(ned, Dx, length + 1, S_TAG) || 24 RayCast(vx2, Dx, length + 1, S_TAG) || 25 RayCast(vx3, Dx, length + 1, S_TAG) || 26 27 RayCast(vx4, Dx * -1, length + 1, S_TAG) || 28 RayCast(vx5, Dx * -1, length + 1, S_TAG) || 29 RayCast(vz3, Dx * -1, length + 1, S_TAG) || 30 31 RayCast(ned, Dz, width + 1, S_TAG) || 32 RayCast(vz2, Dz, width + 1, S_TAG) || 33 RayCast(vz3, Dz, width + 1, S_TAG) || 34 35 RayCast(vz4, Dz * -1, width + 1, S_TAG) || 36 RayCast(vz5, Dz * -1, width + 1, S_TAG) || 37 RayCast(vx3, Dz * -1, width + 1, S_TAG); 38 39 return result; 40 }
這裏將射線的起點和終點都延長了一格,是爲了不兩個生成的房間貼得太緊,這樣至少每一個房間與其它房間間隔一個單位格或以上。
完整的房間結構生成腳本:
1 using System.Collections; 2 using System.Collections.Generic; 3 using UnityEngine; 4 using UnityEngine.Events; 5 6 public enum RoomRandType 7 { 8 //全隨機 9 AllRand, 10 //枚舉大小隨機 11 EnumRand 12 } 13 14 public enum RoomShapeType 15 { 16 //寬>=長 17 WidthMain, 18 //長>=寬 19 LengthMain, 20 //自定義,無形狀要求 21 Coustom 22 } 23 //x-length z-width y-height 24 25 public class RoomBuilder : MonoBehaviour 26 { 27 //建築單位方塊 28 public GameObject BuildUnit; 29 30 //房間高度值 31 public int FixedUnitHeight; 32 //生成的房間層數 33 public int LayerCount; 34 //長寬隨機範圍 35 public Vector2Int GenRange; 36 37 //隨機類型 38 public RoomRandType RandType; 39 //隨機的房間形狀類型 40 public RoomShapeType Shape; 41 42 //房間大小的隨機列表,用於枚舉隨機 43 public List<Vector2Int> RoomRandSizes = new List<Vector2Int>(); 44 45 //隨機的房間最大面積 46 public int MaxRoomArea; 47 //最大隨機數量(隨機試驗次數) 48 public int MaxRoomCount; 49 50 //最小邊長度 51 private int MinRoomEdge; 52 //最大長寬比 53 public int MaxLengthWidthScale = 2; 54 55 //標準方向 56 Vector3Int Dx = new Vector3Int(1, 0, 0); 57 Vector3Int Dy = new Vector3Int(0, 1, 0); 58 Vector3Int Dz = new Vector3Int(0, 0, 1); 59 60 //建築單位標籤 61 const string S_TAG = "Unit"; 62 63 private MapSystem MapManager; 64 65 void Awake() 66 { 67 MapManager = GetComponent<MapSystem>(); 68 } 69 70 public IEnumerator GenRooms(Vector3Int centerPos,UnityAction complete) 71 { 72 var temp = (int)Mathf.Sqrt(MaxRoomArea * 1.0f / MaxLengthWidthScale); 73 MinRoomEdge = temp > 3 ? temp : 3; 74 75 //每層至少1 76 for (int i = 1; i <= LayerCount; i++) 77 { 78 SetGenOneRoom(centerPos, i); 79 yield return new WaitForSeconds(.1f); 80 } 81 82 //超過的隨機佈置 83 var oc = MaxRoomCount - LayerCount; 84 if (oc > 0) 85 { 86 for (int i = 1; i <= oc; i++) 87 { 88 var r = Random.Range(1, LayerCount + 1); 89 SetGenOneRoom(centerPos, r); 90 yield return new WaitForSeconds(.1f); 91 } 92 } 93 94 //全部房間生成完成後發送一個委託信號,以便後續建立房間數據和計算必要鏈接 95 complete(); 96 } 97 98 void SetGenOneRoom(Vector3Int cp, int r) 99 { 100 var layerCenter = cp - new Vector3(0, (LayerCount - 2 * r + 1) * .5f * FixedUnitHeight, 0); 101 102 var rt = RanRoomTran(layerCenter); 103 if (rt != null) 104 { 105 var roomCenter = new Vector3(rt.CenterPos.x, rt.PosY, rt.CenterPos.y); 106 107 GameObject temp = new GameObject(r.ToString()); 108 temp.transform.position = roomCenter; 109 temp.tag = S_TAG; 110 111 //給生成的房間添加碰撞盒子並設置大小 112 GenOneRoom(roomCenter, rt.Length, rt.Width, temp.transform); 113 var bc = temp.AddComponent<BoxCollider>(); 114 bc.size = new Vector3(rt.Length, FixedUnitHeight, rt.Width); 115 116 //目前用物理方式彷佛難以推開重疊的房間,多是哪裏使用方法有誤,改成用用射線檢測解決... 117 //var rb = temp.AddComponent<Rigidbody>(); 118 //rb.useGravity = false; 119 //rb.drag = Mathf.Infinity; 120 //rb.constraints = RigidbodyConstraints.FreezePositionY; 121 //rb.freezeRotation = true; 122 123 //將房間數據存入臨時列表 124 MapManager.GenRooms.Add(rt); 125 MapManager.UnCrossRooms.Add(rt); 126 } 127 } 128 129 RoomTran RanRoomTran(Vector3 centerPos) 130 { 131 var rt = new RoomTran(); 132 133 switch (RandType) 134 { 135 case RoomRandType.AllRand: 136 int temp; 137 var oe = MaxRoomArea / MinRoomEdge; 138 switch (Shape) 139 { 140 case RoomShapeType.LengthMain: 141 rt.Length = Random.Range(MinRoomEdge + 1, oe + 1); 142 temp = MaxRoomArea / rt.Length; 143 if (temp >= rt.Length) 144 rt.Width = Random.Range(MinRoomEdge, rt.Length); 145 else 146 rt.Width = Random.Range(MinRoomEdge, temp + 1); 147 break; 148 case RoomShapeType.WidthMain: 149 rt.Width = Random.Range(MinRoomEdge + 1, oe + 1); 150 temp = MaxRoomArea / rt.Width; 151 if (temp >= rt.Width) 152 rt.Length = Random.Range(MinRoomEdge, rt.Width); 153 else 154 rt.Length = Random.Range(MinRoomEdge, temp + 1); 155 break; 156 case RoomShapeType.Coustom: 157 rt.Length = Random.Range(MinRoomEdge, oe + 1); 158 temp = MaxRoomArea / rt.Length; 159 rt.Width = Random.Range(MinRoomEdge, temp + 1); 160 break; 161 } 162 break; 163 case RoomRandType.EnumRand: 164 var rc = RoomRandSizes.Count; 165 if (rc == 0) 166 { 167 //未填寫時設定隨機默認值 168 rt.Length = 3; 169 rt.Width = 3; 170 } 171 else 172 { 173 var ridx = Random.Range(0,rc); 174 var t = RoomRandSizes[ridx]; 175 if (t.x < 3 || t.y < 3) 176 { 177 //填寫錯誤時設定隨機默認值 178 rt.Length = 3; 179 rt.Width = 3; 180 } 181 else 182 { 183 switch (Shape) 184 { 185 case RoomShapeType.LengthMain: 186 rt.Length = t.x > t.y ? t.x : t.y; 187 rt.Width = t.x < t.y ? t.x : t.y; 188 break; 189 case RoomShapeType.WidthMain: 190 rt.Width = t.x > t.y ? t.x : t.y; 191 rt.Length = t.x < t.y ? t.x : t.y; 192 break; 193 case RoomShapeType.Coustom: 194 rt.Length = Random.value < .5f ? t.x : t.y; 195 rt.Width = t.y == rt.Length ? t.x : t.y; 196 break; 197 } 198 } 199 } 200 break; 201 } 202 203 rt.CenterPos = new Vector2Int(Random.Range((int)(centerPos.x - GenRange.x * .5f), (int)(centerPos.x + GenRange.x * .5f)), 204 Random.Range((int)(centerPos.z - GenRange.y * .5f), (int)(centerPos.z + GenRange.y * .5f))); 205 206 rt.PosY = centerPos.y; 207 208 var roomCenter = new Vector3(rt.CenterPos.x, rt.PosY, rt.CenterPos.y); 209 210 //射線檢測重疊 211 if (RayRoomCheck(roomCenter, rt.Length, rt.Width)) 212 { 213 return null; 214 } 215 return rt; 216 } 217 218 //生成房間前射線檢測下範圍內有無其餘房間 219 bool RayRoomCheck(Vector3 cp, int length, int width) 220 { 221 bool result = false; 222 //長寬至少留一格間隙,高度與地板格對齊 223 var to = new Vector3(length + 1, FixedUnitHeight - 1, width + 1) * .5f; 224 var ned = cp - to; 225 226 var vx2 = ned + new Vector3(0, 0, width + 1) * .5f; 227 var vx3 = ned + new Vector3(0, 0, width + 1); 228 229 var vx4 = ned + new Vector3(length + 1, 0, width * .5f + .5f); 230 var vx5 = ned + new Vector3(length + 1, 0, width + 1); 231 232 var vz2 = ned + new Vector3(length + 1, 0, 0) * .5f; 233 var vz3 = ned + new Vector3(length + 1, 0, 0); 234 235 var vz4 = ned + new Vector3(length * .5f + .5f, 0, width + 1); 236 var vz5 = ned + new Vector3(length + 1, 0, width + 1); 237 238 result = 239 //4組射線,每組3條 240 RayCast(ned, Dx, length + 1, S_TAG) || 241 RayCast(vx2, Dx, length + 1, S_TAG) || 242 RayCast(vx3, Dx, length + 1, S_TAG) || 243 244 RayCast(vx4, Dx * -1, length + 1, S_TAG) || 245 RayCast(vx5, Dx * -1, length + 1, S_TAG) || 246 RayCast(vz3, Dx * -1, length + 1, S_TAG) || 247 248 RayCast(ned, Dz, width + 1, S_TAG) || 249 RayCast(vz2, Dz, width + 1, S_TAG) || 250 RayCast(vz3, Dz, width + 1, S_TAG) || 251 252 RayCast(vz4, Dz * -1, width + 1, S_TAG) || 253 RayCast(vz5, Dz * -1, width + 1, S_TAG) || 254 RayCast(vx3, Dz * -1, width + 1, S_TAG); 255 256 return result; 257 } 258 259 bool RayCast(Vector3 ori, Vector3 dir, float mD, string tag) 260 { 261 Ray ray = new Ray(ori, dir); 262 RaycastHit info; 263 if (Physics.Raycast(ray, out info, mD)) 264 { 265 if (info.transform.tag == tag) 266 return true; 267 } 268 return false; 269 } 270 271 /// <summary> 272 /// 生成單一房間的輪廓 273 /// </summary> 274 /// <param name="centerPos">房間中點位置</param> 275 /// <param name="length">長</param> 276 /// <param name="width">寬</param> 277 /// <param name="parent">父物體</param> 278 void GenOneRoom(Vector3 centerPos, int length, int width, Transform parent = null) 279 { 280 var to = new Vector3(length - 1, FixedUnitHeight - 1, width - 1) * .5f; 281 282 //頂點 283 var ned = centerPos - to; 284 var fod = centerPos + to; 285 286 var v3 = new Vector3(ned.x, fod.y, ned.z); 287 var v4 = new Vector3(ned.x, fod.y, fod.z); 288 var v5 = new Vector3(ned.x, ned.y, fod.z); 289 290 var v6 = new Vector3(fod.x, ned.y, ned.z); 291 var v7 = new Vector3(fod.x, ned.y, fod.z); 292 var v8 = new Vector3(fod.x, fod.y, ned.z); 293 294 //頂點位置(8個) 295 InsSetPos(ned, parent); 296 InsSetPos(fod, parent); 297 InsSetPos(v3, parent); 298 InsSetPos(v4, parent); 299 InsSetPos(v5, parent); 300 InsSetPos(v6, parent); 301 InsSetPos(v7, parent); 302 InsSetPos(v8, parent); 303 304 //12條棱(4*3) 305 //長 306 InsOneEdge(length, ned, Dx, parent); 307 InsOneEdge(length, v3, Dx, parent); 308 InsOneEdge(length, v4, Dx, parent); 309 InsOneEdge(length, v5, Dx, parent); 310 //高 311 InsOneEdge(FixedUnitHeight, ned, Dy, parent); 312 InsOneEdge(FixedUnitHeight, v5, Dy, parent); 313 InsOneEdge(FixedUnitHeight, v6, Dy, parent); 314 InsOneEdge(FixedUnitHeight, v7, Dy, parent); 315 //寬 316 InsOneEdge(width, ned, Dz, parent); 317 InsOneEdge(width, v3, Dz, parent); 318 InsOneEdge(width, v6, Dz, parent); 319 InsOneEdge(width, v8, Dz, parent); 320 } 321 322 //生成一條邊上的建築單位但不包含頂點位置 323 void InsOneEdge(int edge, Vector3 v, Vector3 dir, Transform parent = null) 324 { 325 //忽略首尾單位 326 for (int i = 1; i < edge - 1; i++) 327 { 328 InsSetPos(v + i * dir, parent); 329 } 330 } 331 332 void InsSetPos(Vector3 pos, Transform parent = null) 333 { 334 var ins = Instantiate(BuildUnit); 335 ins.transform.position = pos; 336 ins.transform.parent = parent; 337 } 338 }
在MapSystem中能夠在房間結構生成完後建立一個默認的數據結構:
1 public void RandRoomDatas() 2 { 3 if (RoomBuilder == null||MapData ==null) 4 return; 5 6 RoomBuilder.StartCoroutine(RoomBuilder.GenRooms(MapData.MapCenter,()=> 7 { 8 CreatRoomData(); 9 RandRoomCrosses(); 10 })); 11 } 12 13 void CreatRoomData() 14 { 15 for (int i = 1; i < GenRooms.Count + 1; i++) 16 { 17 var rd = new RoomData(); 18 rd.Id = i; 19 rd.RoomTran = GenRooms[i - 1]; 20 rd.BattleType = RoomBattleType.NormalBattle; 21 if (rd.Id == 1) 22 rd.BattleType = RoomBattleType.Rest; 23 rd.CrossRooms = new List<RoomData>(); 24 rd.Monsters = new List<GameObject>(); 25 rd.bEndRoom = false; 26 rd.bMainCrossRoom = false; 27 28 MapData.RoomDataDic.Add(rd.Id, rd); 29 } 30 }
效果圖:(單層-枚舉列表隨機)
單層(全隨機-長條形房間隨機):
多層(層數5)(自定義-全隨機):
參考資料:
https://indienova.com/indie-game-development/rooms-and-mazes-a-procedural-dungeon-generator/?tdsourcetag=s_pctim_aiomsg
https://mp.weixin.qq.com/s/3yM-mAAXq_fX5tcy82s0uQ