unity3d隨機地牢生成代碼


現在也是處於失業狀態,碰巧看到個面試題是要用unity生成個隨機地牢,就把做題過程中的思路和代碼記錄一下吧。

做完了以后我又想了一下,發現其實根本不需要這么麻煩,果然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;
}
}
}

最后運行效果如下:

unity3d隨機地牢生成代碼

可以看到三個走廊沒有封上口,因為這里預定走廊兩端必須要連接房間,不能出現死胡同的情況,這里可以看到左側和右側走廊的柱子是可以對上的。

好,這樣的話轉化為實體的方法就寫好了,現在考慮怎么生成邏輯地形。

思路是這樣的:第一次生成房間時,直接在地圖最中間生成,之后每次生成一個房間和一個走廊,把走廊和之前造出來的東西連起來。考慮到不想讓走廊拐彎,所以每次先生成走廊再生成房間。

最終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 () {
}
}

最終的效果預覽:

unity3d隨機地牢生成代碼

當然這個地牢還有很多東西沒加上去,比如我在枚舉類中定義的門(可以加在走廊的兩端),出入口。還有裝飾用的燈啊,小物品等,暫時就先這樣吧,也沒想到用這個后續搞點事情。

補發工程源碼:

鏈接:https://pan.baidu.com/s/1jIuh36m 密碼:hx9b


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM