做完了以后我又想了一下,發現其實根本不需要這么麻煩,果然demo里的代碼對我的思路影響還是有點大。demo里的c++代碼為了展示地牢的牆壁,在二維數組中加上了wall這個東西表示牆壁。事實上用unity來做的話,只需要考慮地板的位置,然后根據鄰接的地板有沒有東西來判斷是否生成牆壁即可。
Demo 使用素材以及題目地址:http://pan.baidu.com/s/1c2l3RFE 密碼:aseh
首先用一個枚舉類型代表地牢迷宮中的各個元素:
public enum Tile { Default, DirtFloor,// 房間地板 Wall_w,//上方的牆 Wall_s,//下方的牆 Wall_a,//左方的牆 Wall_d,//右方的牆 Corridor_ad,//橫向走廊 Corridor_ws,//縱向走廊 Door,// 房門 UpStairs,// 入口 DownStairs// 出口 }
然后考慮使用二維數組來保存地牢元素的信息。既然是用unity來做,先不考慮隨機地牢的邏輯數組要怎么生成,先把二維數組轉化為實體的方法寫出來:
建立一個test腳本,用於測試生成用的creat_dungeon方法是否好用,並在里面定義一個測試用的二維數組:
using UnityEngine; using System.Collections; using System; public class test : MonoBehaviour { void Start () { Action _Instantiate=(t,v)=>{Instantiate(t,v,t.rotation);};//這里考慮不當,理論上不應該在test腳本中寫Instantiate方法,而是應該在creat_dungeon中用。不過既然寫出來了也能顯示我懂一些委托的知識...將錯就錯好了 Tile[,] _map=new Tile[4,4]; _map [0, 1] = Tile.Wall_s; _map [0, 2] = Tile.Wall_s; _map [1, 1] = Tile.DirtFloor; _map [1, 2] = Tile.DirtFloor; _map [2, 1] = Tile.DirtFloor; _map [2, 2] = Tile.DirtFloor; _map [3, 1] = Tile.Corridor_ws; _map [3, 2] = Tile.Wall_w; _map [1, 3] = Tile.Corridor_ad; _map [2, 3] = Tile.Wall_d; _map [1, 0] = Tile.Corridor_ad; _map [2, 0] = Tile.Wall_a;//自定義一個地圖 creat_dungeon.Instance.creat (_map,_Instantiate);//調用生成函數 }
接下去就是考慮怎么寫creat_dungeon這個類了,首先這個類只用於生成實體,所以不應該存在多份,所以設計成單例:
public class creat_dungeon{ private static creat_dungeon _instance; private creat_dungeon(){} public static creat_dungeon Instance{ get { if (_instance == null) { _instance = new creat_dungeon (); } return _instance; } } }
然后這個類中肯定需要動態加載預設的資源,這里用resources.load方法加載,在初始化函數中加上加載資源的代碼:
private Transform floor,pillar,wall_ws,wall_ad; private creat_dungeon(){ floor = Resources.Load ("Floor_1_Prefab",typeof (Transform)) as Transform; pillar = Resources.Load ("Pillar_1_Prefab",typeof (Transform))as Transform; wall_ws = Resources.Load ("Wall_w", typeof(Transform))as Transform; wall_ad = Resources.Load ("Wall_a", typeof(Transform))as Transform; }
然后根據資源模型的大小,定義一個常量:
private float floor_length=1.518111f;
最后就是其中生成用的代碼:
/// 根據二維數組實例化地牢 public void creat(Tile[,] map,Action Instantiate){ for (int i = 0; i < map.GetLength (0); i++) for (int j = 0; j < map.GetLength (1); j++) { switch (map [i, j]) { case (Tile.DirtFloor)://地板的場合 Instantiate (floor,new Vector3(i*floor_length,0,j*floor_length)); break; case (Tile.Wall_s)://牆壁的場合 Instantiate (wall_ws,new Vector3((i+0.5f)*floor_length,0,j*floor_length)); Instantiate (pillar,new Vector3((i+0.5f)*floor_length,0,(j+0.5f)*floor_length));//在牆壁對應的位置生成柱子,由於最終生成的是一個封閉的空間,所以牆壁和柱子的數目必定是相等的。 break; case (Tile.Wall_w): Instantiate (wall_ws,new Vector3((i-0.5f)*floor_length,0,j*floor_length)); Instantiate (pillar,new Vector3((i-0.5f)*floor_length,0,(j-0.5f)*floor_length)); break; case (Tile.Wall_a): Instantiate (wall_ad,new Vector3((i)*floor_length,0,(j+0.5f)*floor_length)); Instantiate (pillar,new Vector3((i-0.5f)*floor_length,0,(j+0.5f)*floor_length)); break; case (Tile.Wall_d): Instantiate (wall_ad,new Vector3((i)*floor_length,0,(j-0.5f)*floor_length)); Instantiate (pillar,new Vector3((i+0.5f)*floor_length,0,(j-0.5f)*floor_length)); break; case (Tile.Corridor_ad)://走廊的話,則需要額外生成兩面牆和兩根柱子,不妨想想為什么要這么做 Instantiate (floor,new Vector3(i*floor_length,0,j*floor_length)); Instantiate (wall_ws,new Vector3((i+0.5f)*floor_length,0,j*floor_length)); Instantiate (wall_ws,new Vector3((i-0.5f)*floor_length,0,j*floor_length)); Instantiate (pillar,new Vector3((i-0.5f)*floor_length,0,(j+0.5f)*floor_length)); Instantiate (pillar,new Vector3((i+0.5f)*floor_length,0,(j-0.5f)*floor_length)); break; case (Tile.Corridor_ws): Instantiate (floor,new Vector3(i*floor_length,0,j*floor_length)); Instantiate (wall_ad,new Vector3(i*floor_length,0,(j+0.5f)*floor_length)); Instantiate (wall_ad,new Vector3(i*floor_length,0,(j-0.5f)*floor_length)); Instantiate (pillar,new Vector3((i+0.5f)*floor_length,0,(j+0.5f)*floor_length)); Instantiate (pillar,new Vector3((i-0.5f)*floor_length,0,(j-0.5f)*floor_length)); break; default: break; } } }
最后運行效果如下:
可以看到三個走廊沒有封上口,因為這里預定走廊兩端必須要連接房間,不能出現死胡同的情況,這里可以看到左側和右側走廊的柱子是可以對上的。
好,這樣的話轉化為實體的方法就寫好了,現在考慮怎么生成邏輯地形。
思路是這樣的:第一次生成房間時,直接在地圖最中間生成,之后每次生成一個房間和一個走廊,把走廊和之前造出來的東西連起來。考慮到不想讓走廊拐彎,所以每次先生成走廊再生成房間。
最終map類的代碼如下:
using UnityEngine; using System.Collections; using System.Collections.Generic; public class map { private Tile[,] full_map;//地圖 private int room_max_length, room_max_width, room_min_length, room_min_width, map_max_length, map_max_width, room_num,min_corridor_len,max_corridor_len;//房間的最大最小寬度,地圖的最大長寬,房間的個數 private bool _first; private static map _instance; private map () { } /// 地圖的單例 public static map Instance { get { if (null == _instance) { _instance = new map (); } return _instance; } } /// 初始化函數,全部賦予default public void Init (int room_max_length,int room_max_width,int room_min_length,int room_min_width,int map_max_length,int map_max_width,int room_num,int min_corridor_len,int max_corridor_len) { full_map = new Tile[map_max_width, map_max_length]; _first = true; this.room_max_length = room_max_length; this.room_max_width = room_max_width; this.room_min_length = room_min_length; this.room_min_width = room_min_width; this.map_max_length = map_max_length; this.map_max_width = map_max_width; this.room_num = room_num; this.min_corridor_len = min_corridor_len; this.max_corridor_len = max_corridor_len; } /// 判斷某一塊是否在地圖區域中 private bool IsInBounds_x (int x) { if ((x < 0) || (x > map_max_width - 1)) return false; else return true; } /// 判斷某一塊是否在地圖區域中 private bool IsInBounds_y (int y) { if ((y < 0) || (y > map_max_length - 1)) return false; else return true; } /// 將一塊區域設置為指定類型塊 private void SetCells (int xStart, int yStart, int xEnd, int yEnd, Tile cellType) { for (int i = xStart; i <= xEnd; i++) for (int j = yStart; j <= yEnd; j++) { full_map [i, j] = cellType; } } /// 判斷一個區域是否被使用過 private bool IsAreaUnused (int xStart, int yStart, int xEnd, int yEnd) { for (int i = xStart; i <= xEnd; i++) for (int j = yStart; j <= yEnd; j++) if (full_map [i, j] != Tile.Default) return false; return true; } /// 創建單個房間 private void Creat_room(int xStart, int yStart, int xEnd, int yEnd){ for (int i = xStart + 1; i < xEnd ; i++) for (int j = yStart + 1; j < yEnd; j++) full_map [i, j] = Tile.DirtFloor; for (int i = xStart + 1; i < xEnd ; i++) { full_map [i, yStart] = Tile.Wall_a; full_map [i, yEnd] = Tile.Wall_d; } for (int j = yStart + 1; j < yEnd; j++) { full_map [xStart, j] = Tile.Wall_s; full_map [xEnd, j] = Tile.Wall_w; } } /// 創建單個走廊 private void Creat_Corridor(int xStart, int yStart, int xEnd, int yEnd,Tile t){ for (int i = xStart; i <= xEnd ; i++) for (int j = yStart; j <= yEnd; j++) full_map [i, j] = t; } private Tile[] tiles = System.Enum.GetValues (typeof(Tile)) as Tile[]; /// 創建走廊與房間 private bool MakeRoomAndCorridor(int x,int y){ int xStart = -1, xEnd = -1, yStart = -1, yEnd = -1, width, length; width = Random.Range (room_min_width, room_max_width ); length = Random.Range (room_min_length, room_min_length );//隨機長寬 if (_first) { xStart = map_max_width / 2 - width / 2; yStart = map_max_length / 2 - length / 2; xEnd = xStart + width; yEnd = yStart + length; if (IsInBounds_x (xStart) && IsInBounds_x (xEnd) && IsInBounds_y (yStart) && (IsInBounds_y (yEnd))) { if (IsAreaUnused (xStart, yStart, xEnd, yEnd)) { _first = false;//如果是第一次創建房間的話,就在地圖中間生成一個 Creat_room (xStart, yStart, xEnd, yEnd); return true; } else return false; } } else { if ((full_map [x, y] == Tile.Wall_a) || (full_map [x, y] == Tile.Wall_w) || (full_map [x, y] == Tile.Wall_s) || (full_map [x, y] == Tile.Wall_d)) { //生成走廊與房間 int corridor_length = Random.Range (min_corridor_len - 2, max_corridor_len - 1); int c_xStart = -1, c_xEnd = -1, c_yStart = -1, c_yEnd = -1; int away = Random.Range (1, length - 1);//偏移量 Tile type=Tile.Default; //根據打穿的牆壁類型決定房間和走廊的位置 switch (full_map [x, y]) { case(Tile.Wall_a): xStart = x - away; xEnd = x + width; yEnd = y - corridor_length - 1; yStart = yEnd - length; c_yEnd = y; c_yStart = y - corridor_length - 1; c_xEnd = x; c_xStart = x; type = Tile.Corridor_ad; break; case(Tile.Wall_d): xStart = x - away; xEnd = x + width; yStart = y + corridor_length + 1; yEnd = yStart + length; c_yStart = y; c_yEnd = y + corridor_length + 1; c_xEnd = x; c_xStart = x; type = Tile.Corridor_ad; break; case(Tile.Wall_w): yStart = y - away; yEnd = yStart + length; xStart = x + corridor_length + 1; xEnd = xStart + width; c_xStart = x; c_xEnd = x + corridor_length + 1; c_yStart = y; c_yEnd = y; type = Tile.Corridor_ws; break; case(Tile.Wall_s): yStart = y - away; yEnd = yStart + length; xEnd = x - corridor_length - 1; xStart = xEnd - width; c_xEnd = x; c_xStart = x - corridor_length - 1; c_yStart = y; c_yEnd = y; type = Tile.Corridor_ws; break; default: break; } if (IsAreaUnused (xStart, yStart, xEnd, yEnd)) { Creat_room (xStart, yStart, xEnd, yEnd); Creat_Corridor (c_xStart, c_yStart, c_xEnd, c_yEnd,type);//判斷是否在地圖內,然后生成 return true; } else return false; } else return false; } return true; } public void make_dungeon(int step){ int x, y; int num=0; for (int i = 0; i < step; i++) { x = Random.Range (0,map_max_width); y = Random.Range (0,map_max_length); if (MakeRoomAndCorridor(x,y)){ num++; } if (num==room_num){ break; } } if (num Debug.Log ("無法生成指定個數的房間!請確認數據的合法性或加大步數"); } } public Tile[,] getmap(){ return(full_map); } } 然后是用於控制生成的類,綁在攝像機上就能運行: using UnityEngine; using System.Collections; using System; public class gameDriver : MonoBehaviour { private Transform dungeon; public int random_seed=1; public int room_max_length=5,room_max_width=5,room_min_length=3,room_min_width=3,map_max_length=100,map_max_width=100,room_num=5,min_corridor_len=1,max_corridor_len=3,step=10000;//房間的最大最小寬度,地圖的最大長寬,房間的個數 // Use this for initialization void Start () { dungeon = GameObject.Find ("dungeon").transform;//在地圖上事先創建了一個放實體的空物體 Action _Instantiate=(t,v)=>{object g=Instantiate(t,v,t.rotation);((Transform)g).SetParent(dungeon);}; UnityEngine.Random.InitState (random_seed);//初始化隨機種子 map.Instance.Init (room_max_length, room_max_width, room_min_length, room_min_width, map_max_length, map_max_width, room_num,min_corridor_len,max_corridor_len);//初始化參數 map.Instance.make_dungeon(step);//生成地牢 creat_dungeon.Instance.creat (map.Instance.getmap (), _Instantiate);//實體化地牢 } // Update is called once per frame void Update () { } }
最終的效果預覽:
當然這個地牢還有很多東西沒加上去,比如我在枚舉類中定義的門(可以加在走廊的兩端),出入口。還有裝飾用的燈啊,小物品等,暫時就先這樣吧,也沒想到用這個后續搞點事情。
補發工程源碼:
鏈接:https://pan.baidu.com/s/1jIuh36m 密碼:hx9b