http://pcg.wikidot.com/pcg-algorithm:dungeon-generation
先吐个槽,我想让unity自动生成roguelike类的地下城地图,但是非常反感长长的走廊,希望玩家在房间与房间中穿梭,但不能太复杂,做成迷宫状,玩家基本全职寻路了(个人不那么喜欢跑路玩)。
刚开始看到一个非常惊艳的方法

Procedural Dungeon Generation
在计算过程中发现位置不能控制住,出现完全分离的两个部分,对于我这种这么纠结走廊的人来说,一个多余的走廊都不想要,实在接受不了,中途放弃了这个算法。
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public enum Direction
{
North,East,South,West,Left,Right,
}
public class Test01 : MonoBehaviour {
public Room room;
public List<Room> rooms;
public int roomsCount = 10;
private class LineSegment
{
public Vector2 start;
public Vector2 stop;
public LineSegment(Vector2 start, Vector2 stop)
{
this.start = start;
this.stop = stop;
}
}
private Dictionary<Room, List<LineSegment>> neighborGraph = new Dictionary<Room, List<LineSegment>>();
private Dictionary<Room, List<Room>> neighborRoomCollection = new Dictionary<Room, List<Room>>();
void Start () {
rooms = new List<Room>();
GameObject roomHolder = new GameObject();
//1.开启创建房间协程
StartCoroutine(createRooms(roomHolder));
}
//创建房间
IEnumerator createRooms(GameObject obj)
{
for (int i = 0; i < roomsCount; i++)
{
int roomWidth = Random.Range(6, 9);
int roomHeight = Random.Range(6, 9);
Vector2 roomPos = Random.insideUnitCircle * 8;
room.RoomSetup(roomWidth, roomHeight, roomPos);
room.GetComponent<Transform>().localScale = new Vector3(roomWidth, roomHeight, 1);
//room.GetComponent<BoxCollider2D>().size = new Vector2(x, y);
//以半径为5的区域随机位置
Room temp=Instantiate(room, roomPos, Quaternion.identity) as Room;
rooms.Add(temp);
rooms[i].transform.SetParent(obj.transform);
yield return null;
}
//2.开启物理计算位置是否完成协程
StartCoroutine(checkSleep());
}
//检查物理计算是否完成
IEnumerator checkSleep()
{
bool touching;
do{
touching = false;
for (int i = 0; i < rooms.Count; i++)
{
Collider2D a = rooms[i].GetComponent<Collider2D>();
for (int j = i + 1; j < rooms.Count; j++)
{
Collider2D b = rooms[j].GetComponent<Collider2D>();
//相交检查
if (a.bounds.Intersects(b.bounds))
touching = true;
}
yield return null;
}
} while (touching == true);
Debug.Log("check Ok");
//3.开启房间位置吸附协程
StartCoroutine(roomsPositionRound());
}
//房间位置整理
IEnumerator roomsPositionRound()
{
for (int i = 0; i < rooms.Count; i++)
{
Destroy(rooms[i].GetComponent<Collider2D>());
rooms[i].transform.position = new Vector3(
Mathf.Floor(rooms[i].transform.position.x),
Mathf.Floor(rooms[i].transform.position.y),
rooms[i].transform.position.z);
yield return null;
}
StartCoroutine(FindNeighbors());
}
IEnumerator FindNeighbors()
{
Room a, b, c;
float abDist, acDist, bcDist;
bool skip;
for (int i = 0; i < roomsCount; i++)
{
a = rooms[i];
for (int j = i + 1; j < roomsCount; j++)
{
skip = false;
b = rooms[j];
abDist = GetDist(a, b);
for (int k = 0; k < roomsCount; k++)
{
if (k == i || k == j) continue;
c = rooms[k];
acDist = GetDist(a, c);
bcDist = GetDist(b, c);
if (acDist < abDist && bcDist < abDist)
skip = true;
if (skip)
break;
}
if (!skip)
{
if (!neighborGraph.ContainsKey(a))
{
neighborGraph.Add(a, new List<LineSegment>());
neighborRoomCollection.Add(a, new List<Room>());
}
Vector2 aCenter = a.GetComponent<Transform>().position ;
Vector2 bCenter = b.GetComponent<Transform>().position;
aCenter += new Vector2(a.roomWidth * 0.5f, a.roomHeight * 0.5f);
bCenter += new Vector2(b.roomWidth * 0.5f, b.roomHeight * 0.5f);
neighborGraph[a].Add(new LineSegment(aCenter,bCenter));
neighborRoomCollection[a].Add(b);
yield return new WaitForSeconds(0.001f);
}
}
}
}
float GetDist(Room a, Room b)
{
Vector2 aPos = a.GetComponent<Transform>().position;
Vector2 bPos = b.GetComponent<Transform>().position;
return Mathf.Pow((aPos.x + a.roomWidth * 0.5f) - (bPos.x + b.roomWidth * 0.5f), 2) + Mathf.Pow((aPos.y + a.roomWidth * 0.5f) - (bPos.y + b.roomWidth * 0.5f), 2);
}
void Update()
{
foreach (Room r in neighborGraph.Keys)
{
foreach (LineSegment l in neighborGraph[r])
{
Debug.DrawLine(l.start, l.stop, new Color(1f, 1f, 0f, 0.5f));
}
}
}
}
最后选取了这种方法,一开始没有太看得上,用了发现好东西简单而优美,而我的代码丑的自己都惊了。
一种 Roguelike 地牢生成算法
带墙壁的效果

人物活动范围

using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public enum Directions
{
North,East,South,West,
}
public class test : MonoBehaviour {
private enum TileType
{
Dirt,Floor,Wall,Door,Corner,
}
private int bb;
private TileType[][] grid;
private int COLS = 80;
private int ROWS = 80;
public GameObject[] Type;
private float scale = 0.1f;
private GameObject boardHolder;
private Directions dir;
private Vector2 roomPos;
private Vector2 doorPos;
private int roomWidth;
private int roomHeight;
private int roomCount;
void Start () {
roomCount = 0;
boardHolder = new GameObject();
//StartCoroutine(aa());
init();
}
private void init()
{
grid = new TileType[COLS][];
for (int i = 0; i < grid.Length; i++)
{
grid[i] = new TileType[ROWS];
}
createFirstRoom();
//StartCoroutine(createFeature());
while (roomCount < 30)
createFeature();
InstantiateTiles();
Debug.Log(grid[0][0]);
}
private void createFeature()
{
do
{
selectPoint();
} while (!inState());
if (createRoom((int)roomPos.x, (int)roomPos.y, roomWidth, roomHeight))
{
int tx = (int)doorPos.x;
int ty = (int)doorPos.y;
grid[tx][ty] = TileType.Door;
switch (dir)
{
case Directions.North:
grid[tx][ty + 1] = TileType.Floor;
break;
case Directions.East:
grid[tx + 1][ty] = TileType.Floor;
break;
case Directions.South:
grid[tx][ty - 1] = TileType.Floor;
break;
case Directions.West:
grid[tx - 1][ty] = TileType.Floor;
break;
default:
break;
}
roomCount++;
}
}
private void selectPoint()
{
bool inWall = true;
int x, y;
TileType tt, tb, tl, tr;
do
{
x = Random.Range(2, COLS - 2);
y = Random.Range(2, ROWS - 2);
if (grid[x][y] == TileType.Wall)
{
tt = grid[x][y + 1];
tb = grid[x][y - 1];
tl = grid[x - 1][y];
tr = grid[x + 1][y];
if (tt == TileType.Dirt && (tl == TileType.Wall && tr == TileType.Wall))
{
dir = Directions.North;
inWall = false;
}//North
else if (tb == TileType.Dirt && (tl == TileType.Wall && tr == TileType.Wall))
{
dir = Directions.South;
inWall = false;
}//South
else if (tl == TileType.Dirt && (tt == TileType.Wall && tb == TileType.Wall))
{
dir = Directions.West;
inWall = false;
}//West
else if (tr == TileType.Dirt && (tt == TileType.Wall && tb == TileType.Wall))
{
dir = Directions.East;
inWall = false;
}//East
}
} while (inWall);
doorPos = new Vector2(x, y);
}
private bool inState()
{
Vector2 Pos = doorPos;
int fw = Random.Range(6, 12);
int fh = Random.Range(6, 12);
if (dir == Directions.North || dir == Directions.South)
{
if (Pos.x - fw / 2 - 1 < 1 || Pos.x + fw / 2 + 1 > COLS - 1)
return false;
Pos.x -= Mathf.CeilToInt(fw * 0.5f);
if (dir == Directions.North)
{
Pos.y += 1;
if (Pos.y + fh > ROWS - 1)
return false;
}
else
{
Pos.y -= fh + 1;
if (Pos.y < 1)
return false;
}
}
else
{
if (Pos.y - fh / 2 - 1 < 1 || Pos.y + fh / 2 + 1 > ROWS - 1)
return false;
Pos.y -= Mathf.CeilToInt(fh * 0.5f);
if (dir == Directions.East)
{
Pos.x += 1;
if (Pos.x + fw > COLS - 1)
return false;
}
else
{
Pos.x -= fw + 1;
if (Pos.x < 1)
return false;
}
}
roomPos = Pos;
roomWidth = fw;
roomHeight = fh;
return true;
}
private void createFirstRoom()
{
int fw = Random.Range(6, 12);
int fh = Random.Range(6, 12);
//create room
createRoom((COLS / 2 - fw / 2), (ROWS / 2 - fh / 2), fw, fh);
}
private bool createRoom(int s, int e, int w, int h)
{
w += s;
h += e;
//check Area
if (checkArea(s, e, w, h) && (s != w && e != h))
{
for (int i = s; i <= w; i++)
{
for (int j = e; j <= h; j++)
{
if (i == s || i == w || j == e || j == h)
grid[i][j] = TileType.Wall;
else
grid[i][j] = TileType.Floor;
}
}
grid[s][e] = TileType.Corner;
grid[w][e] = TileType.Corner;
grid[s][h] = TileType.Corner;
grid[w][h] = TileType.Corner;
return true;
}
return false;
}
private bool checkArea(int s, int e, int w, int h)
{
for (int i = s; i < w; i++)
{
for (int j = e; j < h; j++)
{
if (grid[i][j] != TileType.Dirt)
return false;
}
}
return true;
}
private void InstantiateTiles()
{
for (int i = 0; i < COLS; i++)
{
for (int j = 0; j < ROWS; j++)
{
switch (grid[i][j])
{
case TileType.Dirt:
InstantiateFromArray(Type[0], i, j);
break;
case TileType.Floor:
InstantiateFromArray(Type[1], i, j);
break;
case TileType.Wall:
InstantiateFromArray(Type[2], i, j);
break;
case TileType.Door:
InstantiateFromArray(Type[3], i, j);
break;
case TileType.Corner:
InstantiateFromArray(Type[4], i, j);
break;
default:
break;
}
}
}
}
private void InstantiateFromArray(GameObject prefab, int xCoord, int yCoord)
{
float x = (float)xCoord * scale;
float y = (float)yCoord * scale;
prefab.GetComponent<Transform>().localScale = new Vector2(scale, scale);
GameObject tileInstance = Instantiate(prefab, new Vector2(x, y), Quaternion.identity) as GameObject;
tileInstance.transform.parent = boardHolder.transform;
}
}



啊》?
嘿嘿,网站的教程还好使吧?
@Humble Ray:非常好使,感谢所有为网站做出努力的人,也希望自己今后能走出贡献。
很好的心得啊
@eastecho:大叔萌新疯狂的学习游戏开发中,地图的思路算是有了,正在考虑游戏数据储存的问题。