文章原地址

上一个地图生成算法,这一次是一个地牢的生成算法,是一个国外的人写的算法,用dart语言写,我把它改成了unity-c#。

原作者博客地址:Rooms and Mazes: A Procedural Dungeon Generator

当然,我看英文很吃力,好不容易找了一篇翻译后的文章,分享给英语不太好的人。

一个翻译后的版本:房间和迷宫:一个地牢生成算法

然后原作者的算法代码地址(dart):github

算法的原理请看原文地址或者翻译地址,那里有各种动态演示图,讲解的也很清楚,代码可以看原作者的代码,因为我没有学过dart,改写c#的过程很有可能有错误,请见谅!

原作者的代码用到了第三方的一个库,github地址,可以参考这个看原作者代码。

c#代码:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Linq;
using System.Threading; class Directions{
public static Vector2 none = new Vector2 (0,0);
public static Vector2 up = new Vector2 (0,1);
public static Vector2 down = new Vector2 (0,-1);
public static Vector2 left = new Vector2 (-1,0);
public static Vector2 right = new Vector2 (1,0); public static Vector2[] all = {up,down,left,right}; } //确保地图的长宽是奇数
public class generateDungeon2 : MonoBehaviour {
//尝试生成房间的数量
public int numRoomTries = 50;
//在已经连接的房间和走廊中再次连接的机会,使得地牢不完美
public int extraConnectorChance = 20;
//控制生成房间的大小
public int roomExtraSize = 0;
//控制迷宫的曲折程度
public int windingPercent = 0;
public int width = 51;
public int height = 51;
public GameObject wall, floor,connect; private Transform mapParent;
//生成的有效房间
private List<Rect> rooms;
//正被雕刻的区域的索引。(每个房间一个索引,每个不连通的迷宫一个索引,在连通之前)
private int currentRegion = 0;
//原文https://github.com/munificent/piecemeal Array2D
//改成int[,]
private int[,] _regions;
private Tiles[,] map; void Start () {
rooms = new List<Rect> ();
map = new Tiles[width,height];
_regions = new int[width,height];
mapParent = GameObject.FindGameObjectWithTag ("mapParent").transform;
Generate ();
} void Update () {
if (Input.GetKeyDown (KeyCode.Q)) {
Generate ();
}
} public void Generate(){
if (width % 2 == 0 || height % 2 == 0) {
Debug.Log ("地图长宽不能为偶数");
return;
}
InitMap ();
AddRooms ();
FillMaze ();
ConnectRegions ();
RemoveDeadEnds ();
InstanceMap ();
} /*
*生成房间
*1.随机房间(随机大小,奇数)
*2.查看是否重叠,否则加入房间数组
*/
private void AddRooms(){
for (int i = 0; i < numRoomTries; i++) {
//确保房间长宽为奇数
int size = Random.Range(1,3+roomExtraSize)*2+1;
int rectangularity = Random.Range (0, 1 + size / 2) * 2;
int w = size, h = size;
if (0 == Random.Range (0, 1)) {
w += rectangularity;
} else {
h += rectangularity;
}
int x = Random.Range (0, (width - w) / 2) * 2 + 1;
int y = Random.Range (0, (height - h) / 2) * 2 + 1;
Rect room = new Rect (x,y,w,h);
//判断房间是否和已存在的重叠
bool overlaps = false;
foreach (Rect r in rooms) {
if(room.Overlaps(r)){
overlaps = true;
break;
}
}
//如果重叠,抛弃该房间
if (overlaps)
continue;
//如果不重叠,把房间放入rooms中
rooms.Add(room);
//设置新房间索引
StartRegion(); for (int j = x; j < x + w; j++) {
for (int k = y; k < y + h; k++) {
Carve (new Vector2 (k, j));
}
}
}
} /*
* 填充迷宫(洪水填充)
*
*/
private void FillMaze(){
//0处为墙
for (int x = 1; x < width; x += 2) {
for (int y = 1; y < height; y += 2) {
Vector2 pos = new Vector2 (x,y);
//if (map [pos] == Tiles.Wall) {
if (map [x,y] == Tiles.Wall) {
GrowMaze (pos);
}
}
}
} /*
* 生成迷宫
*/
private void GrowMaze(Vector2 start){
List<Vector2> cells = new List<Vector2> ();
Vector2 lastDir = Directions.none;
StartRegion ();
//cells添加之前需要变成Floor
Carve (start);
cells.Add (start);
while (cells != null && cells.Count != 0) {
Vector2 cell = cells [cells.Count - 1];
//可以扩展的方向的集合
List<Vector2> unmadeCells = new List<Vector2> ();
//加入能扩展迷宫的方向
foreach (Vector2 dir in Directions.all) {
if (CanCarve (cell, dir)) {
unmadeCells.Add (dir);
}
}
if (unmadeCells != null && unmadeCells.Count != 0) {
Vector2 dir;
//得到扩展方向 windingPercent用来控制是否为原方向
if (unmadeCells.Contains (lastDir) && Random.Range (0, 100) > windingPercent) {
dir = lastDir;
} else {
dir = unmadeCells [Random.Range (0, unmadeCells.Count - 1)];
} Carve (cell + dir);
Carve (cell + dir * 2);
//添加第二个单元
cells.Add (cell + dir * 2);
lastDir = dir;
} else {
//没有相邻可以雕刻的单元,就删除
cells.Remove (cells[cells.Count - 1]);
//置空路径
lastDir = Directions.none;
} }
} /*
* 连通房间和迷宫
*/
private void ConnectRegions(){
//找到区域所有可连接的空间墙wall
Dictionary<Vector2,List<int>> connectorRegions = new Dictionary<Vector2, List<int>> ();
for (int i = 1; i < width - 1; i++) {
for (int j = 1; j < height - 1; j++) {
//不是墙的跳过
if (map [i, j] != Tiles.Wall)
continue;
List<int> regions = new List<int> ();
foreach (Vector2 dir in Directions.all) {
int region = _regions [i + (int)dir.x, j + (int)dir.y];
//如果周围不是墙(墙的索引为regions的初始值为0)
//去重
if (region != 0 && !regions.Contains(region))
regions.Add (region);
}
//如果这个墙没有连接一个以上的区域,那就不是一个连接点
if (regions.Count < 2)
continue;
connectorRegions [new Vector2 (i, j)] = regions;
//标志连接点
//SetConnectCube(i,j);
}
}
//所有连接点
List<Vector2> connectors = connectorRegions.Keys.ToList<Vector2>();
//跟踪哪些区域已合并。将区域索引映射为它已合并的区域索引。
List<int> merged = new List<int>();
List<int> openRegions = new List<int> ();
for (int i = 0; i <= currentRegion; i++) {
merged.Add (i);
openRegions.Add (i);
}
//使区域连接最终只剩下一个
while (openRegions.Count > 1) {
//随机选择一个连接点
Vector2 connector = connectors[Random.Range(0,connectors.Count-1)];
//连接
AddJunction(connector);
//合并连接区域我们将选择第一个区域(任意)和
//将所有其他区域映射到其索引。
//connectorRegions[connector]
List<int> regions = connectorRegions[connector];
for (int i = 0; i < regions.Count; i++) {
regions[i] = merged[regions[i]];
}
int dest = regions[0];
regions.RemoveAt (0);
List<int> sources = regions;
//合并所有受影响的区域
for(int i=0;i<currentRegion;i++){
if (sources.Contains (merged [i])) {
merged [i] = dest;
}
}
//移除已经连接的区域
foreach (int s in sources) {
openRegions.RemoveAll (value => (value==s));
}
connectors.RemoveAll (index=>IsRemove(merged,connectorRegions,connector,index));
}
} /*
* 简化迷宫
*/
private void RemoveDeadEnds(){
bool done = false;
while (!done) {
done = true;
for (int i = 1; i < width - 1; i++) {
for (int j = 1; j < height - 1; j++) {
if (map [i, j] == Tiles.Wall)
continue;
int exists = 0;
foreach (Vector2 dir in Directions.all) {
if (map [i + (int)dir.x, j + (int)dir.y] != Tiles.Wall) {
exists++;
}
}
//如果exists==1则是三面环墙
if (exists != 1) {
continue;
}
done = false;
_regions [i, j] = 0;//变成墙
map [i, j] = Tiles.Wall;
}
}
}
} /*
*保存区域索引
*
*/
private void StartRegion() {
currentRegion++;
} /*
* 雕塑点,设置这个点的类型,默认地板
*
*/
private void Carve(Vector2 pos,Tiles type=Tiles.Floor) {
int x = (int)pos.x, y = (int)pos.y;
map [x, y] = Tiles.Floor;
_regions [x,y] = currentRegion;
} //dir是方向
private bool CanCarve(Vector2 pos,Vector2 dir){
Vector2 temp = pos + 3*dir;
int x = (int)temp.x, y = (int)temp.y;
//判断是否超过边界
if (x < 0 || x > width || y < 0 || y > height) {
return false;
}
//需要判断方向第二个单元的原因是cells中需要添加下一个cell
//所以下一个cell要变为Floor,然后需要判断是否第二个单元是否为墙
//如果不为墙,则第一个cell被变为Floor为,和第二个单元就连通了,不可行
//判断第二个单元主要用来判断不能&其他房间或走廊(regions)连通
temp = pos + 2 * dir;
x = (int)temp.x;
y = (int)temp.y;
//是墙则能雕刻迷宫
return map [x, y] == Tiles.Wall;
} private void AddJunction(Vector2 pos){
map [(int)pos.x, (int)pos.y] = Tiles.Floor;
} /*
* 删除不需要的连接点
*/
private bool IsRemove(List<int> merged,Dictionary<Vector2,List<int>> ConnectRegions,Vector2 connector,Vector2 pos){
//不让连接器相连(包括斜向相连)
if((connector-pos).SqrMagnitude() < 2){
return true;
}
List<int> temp = ConnectRegions[pos];
for(int i=0;i<temp.Count;i++){
temp[i] = merged[temp[i]];
}
HashSet<int> set = new HashSet<int>(temp);
//判断连接点是否和两个区域相邻,不然移除
if(set.Count>1){
return false;
}
//增加连接,使得地图连接不是单连通的
if(Random.Range(0,extraConnectorChance)==0) AddJunction(pos);
return true;
} private void SetConnectCube(int i,int j){
GameObject go = Instantiate (connect, new Vector3 (i, j, 1), Quaternion.identity) as GameObject;
go.transform.SetParent (mapParent);
go.layer = LayerMask.NameToLayer ("wall");
} /*
* 地图全部初始化为墙
*
*/
private void InitMap(){
for (int x = 0; x < width; x ++) {
for (int y = 0; y < height; y ++) {
map [x, y] = Tiles.Wall;
}
}
} private void InstanceMap (){
for (int i = 0; i < width; i++) {
for (int j = 0; j < height; j++) {
if (map [i, j] == Tiles.Floor) {
GameObject go = Instantiate (floor, new Vector3 (i, j, 1), Quaternion.identity) as GameObject;
go.transform.SetParent (mapParent);
//设置层级
go.layer = LayerMask.NameToLayer ("floor");
} else if (map [i, j] == Tiles.Wall) {
GameObject go = Instantiate (wall, new Vector3 (i, j, 1), Quaternion.identity) as GameObject;
go.transform.SetParent (mapParent);
go.layer = LayerMask.NameToLayer ("wall");
}
}
}
} } 效果图: 这个是一开始的生成房间的效果图:

下图是对地图空白部分进行迷宫填充:
下图是对迷宫进行连接点计算:

下图是对地图的区域进行连接:

最后是对地图中的死胡同进行消除:

roguelike地牢生成算法的更多相关文章

  1. 一个UUID生成算法的C语言实现 --- WIN32版本 .

    一个UUID生成算法的C语言实现——WIN32版本   cheungmine 2007-9-16   根据定义,UUID(Universally Unique IDentifier,也称GUID)在时 ...

  2. 分布式全局不重复ID生成算法

    分布式全局不重复ID生成算法 算法全局id唯一id  在分布式系统中经常会使用到生成全局唯一不重复ID的情况.本篇博客介绍生成的一些方法. 常见的一些方式: 1.通过DB做全局自增操作 优点:简单.高 ...

  3. C++ 基于凸包的Delaunay三角网生成算法

    Delaunay三角网,写了用半天,调试BUG用了2天……醉了. 基本思路比较简单,但效率并不是很快. 1. 先生成一个凸包: 2. 只考虑凸包上的点,将凸包环切,生成一个三角网,暂时不考虑Delau ...

  4. C++ 凸包生成算法

    由于我的极差记忆力,我打算把这个破玩意先记下来.因为以后会有改动(Delaunay三角网生成算法),我不想把一个好的东西改坏了... 好吧-- 凸包生成算法,: 1.先在指定的宽(width)高(he ...

  5. RocketMQ msgId生成算法

    当我们用RocketMQ发送信息的时候通常都会返回如下信息: SendResult [sendStatus=SEND_OK, msgId=0A42333A0DC818B4AAC246C290FD000 ...

  6. 分布式系统的唯一id生成算法你了解吗?

    在分库分表之后你必然要面对的一个问题,就是id咋生成? 因为要是一个表分成多个表之后,每个表的id都是从1开始累加自增长,那肯定不对啊. 举个例子,你的订单表拆分为了1024张订单表,每个表的id都从 ...

  7. 转:体积阴影(Shadow Volumes)生成算法

    下面以最快的速度简单谈谈阴影生成技术,目前普遍采用的一般有三种:Planar Shadow.Shadow Mapping和Shadow Volume,前者类似投影,计算最简单,缺点只能绘制抛射在平面上 ...

  8. STL_算法_04_算术和生成算法

    ◆ 常用的算术和生成算法: 1.1.求和( accumulate 是求和的意思)(对指定范围内的元素求和,然后结果再加上一个由val指定的初始值.) T accumulate(iteratorBegi ...

  9. ES批量索引写入时的ID自动生成算法

    对bulk request的处理流程: 1.遍历所有的request,对其做一些加工,主要包括:获取routing(如果mapping里有的话).指定的timestamp(如果没有带timestamp ...

随机推荐

  1. 系统安全-Firewall

    Netfilter/iptables是与最新的2.6.x版本Linux内核集成的ip信息包过滤系统.如果Linux系统连接到因特网或LAN.服务器或连接LAN和因特网的代理服务器,则该系统有理由在Li ...

  2. Django缓存问题

    由于Django是动态网站,所有每次请求均会去数据进行相应的操作,当程序访问量大时,耗时必然会更加明显,最简单解决方式是使用:缓存,缓存将一个某个views的返回值保存至内存或者memcache中,5 ...

  3. 深入Garbage First垃圾收集器(三)G1中的垃圾收集

    G1 GC在收集暂停的过程中会回收绝大部分堆分区,唯一的例外是多级并发标记期间的清除阶段. 在清除阶段,如果G1遇到仅仅只存放了垃圾的分区,它就会立刻收集这些分区并将它们放回空闲分区列表中,因此这些分 ...

  4. JavaScript读书笔记(1)

    从今天开启每天看书记笔记模式,<JavaScript高级程序设计(第3版)> 1. Javascript最初是为了解决输入验证器的问题,现在已经发展成一门复杂的语言: 2.  语言标准为E ...

  5. JAVA调用命令行2

    package loadMBQL; import java.io.File; import java.io.FilenameFilter; public class LoadMBQL { /** * ...

  6. (转)SDP协议概述

    1 简介 SDP 完全是一种会话描述格式, 它不属于传输协议. 它使用不同的适当的传输协议,包括会话通知协议(SAP).会话初始协议(SIP). 实时流协议(RTSP).MIME 扩展协议的电子邮件以 ...

  7. linux apache服务器

    apache服务器 服务端功能是侦听和响应客户端的http请求.http协议的默认端口是80. 1996年以来,apache成为最流行的web服务器. IIS web服务器只能安装在windows上. ...

  8. jquery特效(2)—选项卡

    最近公司有个页面正好用到了选项卡,我就写了一下,感觉还不错,都挺简单的. 下面来看动态效果: 一.主体程序 <!DOCTYPE html> <html> <head> ...

  9. 人生苦短之Python多线程

    #encoding=utf-8 import threading import time ''' python多线程并不是真正意义上的多线程,通常我们所说的多线程是多个线程同时执行某功能,而在pyth ...

  10. 异步模式模式Future(结合Callable可以获取线程返回结果)

    submit 和 excute是有啥区别 如果有这样的需求: 多线程实现下载,提高效率. 不论是Thread类还是Runnable接口重写run方法,有个特点就是没有返回值~~~~~~ 我都主线程 如 ...