我是靠谱客的博主 活泼烤鸡,最近开发中收集的这篇文章主要介绍一种简单的2D Roguelike地图生成算法,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

前言

最近刚确定下来毕业设计要做的内容——一个2DRoguelike类型的游戏。因此研究了几天的地图生成算法,终于于如今成功制作了地图的生成。先放几张示意图感受一下:



是不是还蛮有感觉的呢!

一、核心思路

首先我们要明白的是一点。地图的随机生成方法有很多,但是前提是必须要保证这些地图区域的复杂性和连通性。因此虽然随机生成难度不大,但是合理性还是需要考虑的。

大体思路是这样的:
1.首先我们要随机生成一些墙和通路。这些是完全随机的。当然最外圈是必然是墙的。
2.接下来对这些完全随机生成的方块按照一定规则进行反复处理。
3.在完全处理过后,会生成一些并不连通的区域,打通他们。
4.在生成入口和出口。

二、随机生成墙和通路

这一段没什么好说的。不过如果按照我的算法来做的话,这个初始随机是墙和通路的概率是多少并无什么太大关系。我分别测试了20%、50%、80%的概率初始生成墙,发现最终的地图区别并没有什么明显的区别。
我的地图是15*15的。首先我定义了一个Map类。它代表着每一个地图的格子。当然这个类我的名字起的并不好,不过我也想不出什么更好的名字了,所以就干脆不变了。这个Map类里面包含这些属性:

public int type = 0;
//0通路 1墙 2复活点 3上楼点
public int RoundWallCount = 0;
public int x;
public int y;
//数据结构
public bool IsVisited = false;

type代表的是当前格子的类型。RoundWallCount指的则是当前格子周围的墙(type==1)的数量。x和y指的是当前格子的坐标。至于IsVisited在后面的打通部分有用处。
首先定义一个Map[,]的二维数组。作为我当前的全部地图。然后对这个地图进行初始化,完全生成一个15*15的地面。


Object OFloor = Resources.Load("Prefabs/Floor", typeof(GameObject));
for(int x = 0; x < 15; x++)
{
for(int y = 0; y < 15; y++)
{
GameObject GO = Instantiate(OFloor, PFloor) as GameObject;
GO.transform.position = new Vector3(x, y, 0);
}
}

这些地面是完全不需要规则的,只管生成就好。接下来就需要随机生成墙了。

 for (int x = 0; x < 15; x++)
{
for (int y = 0; y < 15; y++)
{
Map[x, y] = new Map();
Map[x, y].x = x;
Map[x, y].y = y;
if (Random.Range(0, 100) < 50 ||x == 0 || y == 0 || x == 14 || y == 14)
{
Map[x, y].type = 1;
}
}
}

当然我的type的默认值是0.所以如果不是墙,那么并不需要赋值。
我放几张图表示一下这个算法生成的结果

二、循环处理地图

这时候我们发现这张图虽然有点感觉,但是不连通的区域实在是太多了。所以我们要进行一系列算法来修改地图,使这张地图尽可能地连通。
核心算法是这样的。反复进行以下操作:
1.取当前每一个格子周围的墙的数量
2.如果当前格子是墙,那么当他周围的墙<5个或者大于7个时,将其转变为路面
3.如果当前格子是路,那么当他周围的墙>=4个或者小于2个时,把他变成墙


for (int i = 0; i < 3; i++)
{
//记录墙的数量
for (int x = 1; x < 14; x++)
{
for (int y = 1; y < 14; y++)
{
//检查周围一圈墙的数量
Map[x, y].RoundWallCount = 0;
for (int tx = x - 1; tx < x + 2; tx++)
{
for (int ty = y - 1; ty < y + 2; ty++)
{
if (Map[tx, ty].type == 1)
{
Map[x, y].RoundWallCount += 1;
}
}
}
}
}
//修改地形
for (int x = 1; x < 14; x++)
{
for (int y = 1; y < 14; y++)
{
if (Map[x, y].type == 1)
{
if (Map[x, y].RoundWallCount < 5 || Map[x, y].RoundWallCount > 7
)
{
Map[x, y].type = 0;
}
}
else
{
if (Map[x, y].RoundWallCount >= 4 || Map[x, y].RoundWallCount < 2)
{
Map[x, y].type = 1;
}
}
}
}
}

接下来我们会得到类似于这样的一张图


可以看到大体而言,这张图会变得大块更加少一些,细碎的小路更加多一些。而且总体而言并没有之前那么多不连通的区域了。

三、打通所有不连通的区域

首先为了打通区域,我们要划分开哪些是同一个区域。这里我要感谢一下我的一个朋友。在我还在想深度优先遍历迷宫寻路的时候,他提供了一个相当棒的思路。只需要找到一个点,把这个点标为已访问。然后找寻他周围四个点是否是未访问的路,然后把他们加入。再查找他们周围的点。直到最后你的最后一个点四周并无新增点。整体就为一个区域。
我对区域的定义是这样的:

public class Area
{
public List<Map> MapList = new List<Map>();
}

一个含有Map的List变量的一个类。
接下来就是划分区域的代码了:


List<Map> RoadList = new List<Map>();
List<Area> AreaList = new List<Area>();
//初始化RoadList
for (int x = 1; x < 14; x++)
{
for (int y = 1; y < 14; y++)
{
if (Map[x, y].type == 0)
{
RoadList.Add(Map[x, y]);
}
}
}
//区分Area
while (RoadList.Count > 0)
{
if (RoadList.Count == 1)
{
Area area = new Area();
area.MapList.Add(RoadList[0]);
AreaList.Add(area);
break;
}
else
{
Area area = new Area();
area.MapList.Add(RoadList[0]);
for (int i = 0; i < area.MapList.Count; i++)
{
int x = area.MapList[i].x;
int y = area.MapList[i].y;
area.MapList[i].IsVisited = true;
if (Map[x + 1, y].type == 0 && Map[x + 1, y].IsVisited == false)
{
area.MapList.Add(Map[x + 1, y]);
}
if (Map[x - 1, y].type == 0 && Map[x - 1, y].IsVisited == false)
{
area.MapList.Add(Map[x - 1, y]);
}
if (Map[x, y + 1].type == 0 && Map[x, y + 1].IsVisited == false)
{
area.MapList.Add(Map[x, y + 1]);
}
if (Map[x, y - 1].type == 0 && Map[x, y - 1].IsVisited == false)
{
area.MapList.Add(Map[x, y - 1]);
}
}
foreach(Map m in area.MapList)
{
foreach(Map ma in RoadList)
{
if (m == ma)
{
RoadList.Remove(ma);
break;
}
}
}
AreaList.Add(area);
}
}

当你划分好区域之后,你就可以对这些区域进行打通操作。我这里采用了一种粗暴的打通方式:
在区域>1个时无限循环
1.寻找前两个区域之间最近的两个点
2.先横向打通,再纵向打通
3.合并这两个区域为1个区域
4.继续循环
上代码:

while (AreaList.Count > 1)
{
int tempdis = 99;
Map Start = null, End = null;
foreach(Map m in AreaList[0].MapList)
{
foreach(Map ma in AreaList[1].MapList)
{
if(Mathf.Abs(m.x-ma.x)+ Mathf.Abs(m.y - ma.y) < tempdis)
{
tempdis = Mathf.Abs(m.x - ma.x) + Mathf.Abs(m.y - ma.y);
Start = m;
End = ma;
}
}
}
if (Start.x < End.x)
{
for(int x = Start.x; x < End.x; x++)
{
Map[x, Start.y].type = 0;
}
if(Start.y < End.y)
{
for (int y = Start.y; y < End.y; y++)
{
Map[End.x, y].type = 0;
}
}
else
{
for (int y = Start.y; y > End.y; y--)
{
Map[End.x, y].type = 0;
}
}
}
else
{
for (int x = Start.x; x > End.x; x--)
{
Map[x, Start.y].type = 0;
}
if (Start.y < End.y)
{
for (int y = Start.y; y < End.y; y++)
{
Map[End.x, y].type = 0;
}
}
else
{
for (int y = Start.y; y > End.y; y--)
{
Map[End.x, y].type = 0;
}
}
}
foreach(Map m in AreaList[1].MapList)
{
AreaList[0].MapList.Add(m);
}
AreaList.Remove(AreaList[1]);
}

OK!大功告成。最后你就会得到如开头所说的比较好看的完全随机地图啦!

四、生成起始点和终点

这里我使用了一种并不完全随机的算法。我首先在周围一圈随机寻找一个通路,把他设置为起始。如果这一圈都没有,那么再往里找一圈。直至找到为止。终点则是寻找最远的点然后设置为终点。

//生成起点
int tempx=0, tempy=0;
int Startx = 0,Starty = 0;
for(int i = 1; i < 6; i++)
{
List<Map> temp = new List<Map>();
for(int x = i; x < 15 - i; x++)
{
if (Map[x, i].type == 0)
{
temp.Add(Map[x, i]);
}
if (Map[x, 14 - i].type == 0)
{
temp.Add(Map[x, 14 - i]);
}
}
for (int y = i; y < 15 - i; y++)
{
if (Map[i, y].type == 0)
{
temp.Add(Map[i, y]);
}
if (Map[14 - i, y].type == 0)
{
temp.Add(Map[14 - i, y]);
}
}
if (temp.Count > 0)
{
int a = Random.Range(0, temp.Count);
Map maptemp = temp[a];
maptemp.type = 2;
tempx = maptemp.x;
tempy = maptemp.y;
Object DF = Resources.Load("Prefabs/DF", typeof(GameObject));
GameObject GO = Instantiate(DF, PFloor) as GameObject;
GO.transform.position = new Vector3(maptemp.x, maptemp.y, 0);
GOMap[maptemp.x, maptemp.y] = GO;
Startx = maptemp.x;
Starty = maptemp.y;
break;
}
}
//生成终点
List<Map> RoadList = new List<Map>();
for (int x = 1; x < 14; x++)
{
for (int y = 1; y < 14; y++)
{
if (Map[x, y].type == 0)
{
RoadList.Add(Map[x, y]);
}
}
}
RoadList.Remove(Map[Startx, Starty]);
int tempdis = 0;
Map TempMap = null;
foreach (Map m in RoadList)
{
if (Mathf.Abs(m.x - Startx) + Mathf.Abs(m.y - Starty) > tempdis)
{
tempdis = Mathf.Abs(m.x - Startx) + Mathf.Abs(m.y - Starty);
TempMap = m;
}
}
TempMap.type = 3;
Object UF = Resources.Load("Prefabs/UF", typeof(GameObject));
GameObject GO2 = Instantiate(UF, PFloor) as GameObject;
GO2.transform.position = new Vector3(TempMap.x, TempMap.y);
GOMap[TempMap.x, TempMap.y] = GO2;

结语

这种算法据说来源于计算机图形学。其实我刚开始写他的时候是不太了解的,仅仅在看了这个文章之后我在此基础上修改和添加了一些不同的算法,形成的我的地图算法。后来又查阅了很多资料,问了朋友,才最终确定这个算法。学无止境,各位加油。

最后

以上就是活泼烤鸡为你收集整理的一种简单的2D Roguelike地图生成算法的全部内容,希望文章能够帮你解决一种简单的2D Roguelike地图生成算法所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(49)

评论列表共有 0 条评论

立即
投稿
返回
顶部