本文转自Unity Connect博主 超级汽水
一个简易的房间类 Roguelike 游戏地图生成系统
如果你还不知道《元气骑士》是什么游戏,也许你可以去看看这个视频
地图显示方式
1. 随机大小的矩形房间。
2. 随机的房间数量。
3. 通过走廊连接每个房间。
4. 每个图都有 俩个特殊房间:“出生房”、“传送房”。
地图生成思路梳理
- 因为在原版游戏中房间并不是完全的正方形,所以我们在设计时要考虑到边长的问题。
- 虽然游戏中的房间数量是随机的,但是也是有一个范围的。所以需要按照生成地图的最终面积来做一个约束。
- 走廊连接算是游戏中的一个难点,因为走廊的长度是不确定的要在生成房间后根据房间的位置和大小来确定走廊的生成位置以及长度。
- 在游戏中,每一个大关卡包含多个小关卡,每个小关卡中又包含了多张地图,每个地图中又有多个小房间。这样说可能有点不太好理解我画了张图来帮忙理解:
实现思路以及简化
在之前已经发过俩篇文章介绍tilemap生成随机地图的方法,如果对tilemap不太了解可以先去看看:
整体的实现流程如下:
1 .根据用户定义创建一张足够大的地图 (这里的地图指每个小关所有房间所在的大地图)。
2 .假设大地图中所有位置布满大小相同的房间 (这里的房间边长一定要奇数,比较好操作),然后根据所需的房间总数来删掉多余的房间。
3 .根据规定的长宽比,来随机修改每个房间的大小。
4 .生成过道连通所有房间,(这里黄色的点是原先没有改变大小时的正方形的中心点)
5 .确定特殊房间的位置。
Tilemap中单独房间效果图:
Unity中详细操作
1. 创建一个普通的Tilemap。
2. 创建一个空物体,用来挂载我们的地图生成脚本。
3. 创建所需要的变量
public class MapManager : MonoBehaviour
{
[Header("地图种子")] public int seed = 123456;
[Header("每行房间数")] public int mapMaxW;
[Header("每列房间数")] public int mapMaxH;
[Header("总生成房间数")] public int mapCount;
[Header("最大房间宽")] public int roomMaxW;
[Header("最大房间高")] public int roomMaxH;
[Header("最小房间宽")] public int roomMinW;
[Header("最小房间高")] public int roomMinH;
[Header("房间的间隔距离")] public int distance;
[Header("地板")] public TileBase floor;
[Header("墙")] public TileBase wall;
[Header("地图")] public Tilemap tilemap;
private int[,] _roomMap;
private List<Vector3Int> _centerPoint;
private Dictionary<Vector3Int,int> _mapPoint;
}
4. 列出所需函数
//画出地图
private void DrawMap(){}
//画出房间
private void DrawRoom(int roomX,int roomY){}
//画出路
private void DrawRoad(){}
//画出地板和墙壁
private void DrawFloor(){}
//生成房间 (用二维 int 数组表示)
private int[,] RandomRoom(int maxW, int maxH, int minW, int minH, out int width, out int height){}
//生成一个地图 (用二维 int 数组表示)
private int[,] GetRoomMap(int mapW, int mapH, int roomCount)
//获取一个范围内的随机奇数
private int GetOddNumber(int min, int max){}
//获取下一个房间的位置
private Vector2Int GetNextPoint(Vector2Int nowPoint, int maxW, int maxH)
5 .实现各个函数
代码已经放在文末的附件,这里我讲一下我的大体思路以及各个函数的作用
首先是使用,我在 Update 函数中检测按下 R 键重新生成地图,生成很简单只要调用 DrawMap 函数就可以
private void Update()
{
if (Input.GetKeyDown(KeyCode.R))
{
DrawMap();
}
}
在 DrawMap 中 会调用 DrawRoad 和 DrawFloor 俩个函数来分别画出道路和地板、墙壁。
部分函数解析:
//取一定范围内的一个奇数
private int GetOddNumber(int min, int max)
{
while (true)
{
var temp = Random.Range(min, max);
if ((temp & 1) != 1) continue;
return temp;
}
}
这个函数主要用来在房间生成的时候取一个随机的奇数出来,因为我们的房间连接需要一个中心点所以奇数更方便使用。
//生成一个房间,用二维 int 数组表示
private int[,] RandomRoom(int maxW, int maxH, int minW, int minH, out int width, out int height)
{
width = GetOddNumber(minW, maxW);
height = GetOddNumber(minH, maxH);
var room = new int[width, height];
//方便以后扩展使用了二维数组,这里为了演示方便对房间内生成其他物体
for (var i = 0; i < width; i++)
{
for (var j = 0; j < height; j++)
{
room[i, j] = 1;
}
}
return room;
}
这个函数主要用来生成一个房间,选用二维int数组主要是为了扩展、存储时方便。本案例中 普通地板为 1,墙壁为 0。同时为了后续步骤计算方便这里把生成的房间的长宽也返回了。
private int[,] GetRoomMap(int mapW, int mapH, int roomCount)
{
//第一个房间的坐标点
var nowPoint = Vector2Int.zero;
//当前生成的房间数
var mCount = 1;
//当前地图
var map = new int[mapW, mapH];
//第一个格子总有房间,作为出生房间
map[nowPoint.x, nowPoint.y] = 1;
while (mCount < roomCount)
{
nowPoint = GetNextPoint(nowPoint, mapW, mapH);
if (map[nowPoint.x, nowPoint.y] == 1) continue;
map[nowPoint.x, nowPoint.y] = 1;
mCount ++;
}
return map;
}
这个函数用来生成总体的地图,也就是用来查看哪里需要生成房间。需要生成房间的地方为 1 ,空白的地方为 0。与GetNextPoint 连用获取下一个房间的坐标点。因为房间总是相连所以不存在间隔。
private Vector2Int GetNextPoint(Vector2Int nowPoint, int maxW, int maxH)
{
while (true)
{
var mNowPoint = nowPoint;
switch (Random.Range(0, 4))
{
case 0:
mNowPoint.x += 1;
break;
case 1:
mNowPoint.y += 1;
break;
case 2:
mNowPoint.x -= 1;
break;
default:
mNowPoint.y -= 1;
break;
}
if (mNowPoint.x >= 0 && mNowPoint.y >= 0 && mNowPoint.x < maxW && mNowPoint.y < maxH)
{
return mNowPoint;
}
}
}
这个函数大体的思想还是参照我前俩篇文章中讲到的随机游走法的思路。
最终效果 :
1 .生成房间
2 .添加横向过道
3 .添加纵向过道
4 .添加墙壁
更多干货,戳上方链接下载Unity官方app,在线互动答疑技术社区,学习交友两不误!