unity--Rougelike--地图随机生成算法
在Rougelike游戏里,地图随机生成是一大特色,方法也有很多,在这里记录一下各种实现方法
一、随机生成固定房间
参考自麦扣老师的教程:https://unity.cn/projects/chuang-jian-roguelikesui-ji-di-xia-cheng-yi
思路:从原点每次随机向上下左右生成一个房间
首先,创建两个空对象,RoomGenerator和Points
给RoomGenerator添加一个脚本,写上一些基本的信息
RoomPrefab是要生成的房间,这里用一个白色的长方形(BasicRoom)演示
RoomNumber是生成的房间数量
GeneratorPoint是上面创建的points对象,用来设置随机生成的房间坐标
X/YSet是每次随机生成的房间向上下左右移动的距离
生成房间:
要生成房间,只要在一个循环内生成指定数量的房间对象即可
for (var i = 0; i < roomNumber; i++)
{
//Instantiate()是创建or复制一份原对象的函数,第一个参数的要创建or复制的对象
//第二个参数是创建or复制后的位置,第三个是创建or复制后的旋转
Instantiate(roomPrefab,generatorPoint.position,Quaternion.identity);
}
现在就会在原点(generatorPoint.position
)生成指定数量的房间了
接下来只要将生成的房间随机生成即可
我们可以让生成的房间每次随机的向上/下/左/右移动固定的距离
可以用switch来实现
int dice = Random.Range(0,4);
//0向上 1向下 2向左 3向右
switch(dice)
{
case 0:
generatorPoint.position += new Vector3(0, ySet, 0);
break;
case 1:
generatorPoint.position += new Vector3(0, -ySet, 0);
break;
case 2:
generatorPoint.position += new Vector3(-xSet, 0, 0);
break;
case 3:
generatorPoint.position += new Vector3(xSet, 0, 0);
break;
}
这样就可以随机生成地图了,但是,会出现生成的房间在同一块地区的状况
因此,我们给房间加上碰撞体,在每次switch指定位置之后判断有没有和之前的房间碰撞
如果碰撞,就再随机向上下左右移动一次,直到移动到没有房间的地方
在原先的switch上加上一个do while判断即可
do
{
int dice = Random.Range(0,4);
//0向上 1向下 2向左 3向右
switch(dice)
{
case 0:
generatorPoint.position += new Vector3(0, ySet, 0);
break;
case 1:
generatorPoint.position += new Vector3(0, -ySet, 0);
break;
case 2:
generatorPoint.position += new Vector3(-xSet, 0, 0);
break;
case 3:
generatorPoint.position += new Vector3(xSet, 0, 0);
break;
}
}
while( Physics2D.OverlapCircle(generatorPoint.position, 0.2f, roomLayer) );
最终,就可以生成像这样的随机固定的房间了
完整代码:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class RoomGenerator : MonoBehaviour
{
[Header("房间信息")]
public GameObject roomPrefab;
public int roomNumber;
public Color startColor, endColor;
[Header("移动控制")]
public Transform generatorPoint;
public float xSet,ySet;
public LayerMask roomLayer;
public List<GameObject> rooms = new List<GameObject>();
// Start is called before the first frame update
void Start()
{
for (var i = 0; i < roomNumber; i++)
{
//Instantiate()是创建or复制一份原对象的函数,第一个参数的要创建or复制的对象
//第二个参数是创建or复制后的位置,第三个是创建or复制后的旋转
rooms.Add( Instantiate(roomPrefab,generatorPoint.position,Quaternion.identity) );
//生成随机
SetRoomPos();
}
rooms[0].GetComponent<SpriteRenderer>().color = startColor;
}
// Update is called once per frame
void Update()
{
}
//生成随机地图
void SetRoomPos()
{
do
{
int dice = Random.Range(0,4);
//0向上 1向下 2向左 3向右
switch(dice)
{
case 0:
generatorPoint.position += new Vector3(0, ySet, 0);
break;
case 1:
generatorPoint.position += new Vector3(0, -ySet, 0);
break;
case 2:
generatorPoint.position += new Vector3(-xSet, 0, 0);
break;
case 3:
generatorPoint.position += new Vector3(xSet, 0, 0);
break;
}
}
while( Physics2D.OverlapCircle(generatorPoint.position, 0.2f, roomLayer) );
}
}
二、TileMap 洗牌算法
运用tile来随机生成墙
与上面生成随机房间不同,这个是运用tile来生成房间内的随机地形
首先,创建一个TileGenerator和TileMap,其中,TileMap用来存放生成的Tile
给TileGenerator添加一个脚本
TilePrefab用来存放Tile的预制地板
MapSize是Tile的尺寸
OutlinePercent 是地板之间间隙的大小
生成Tile地板:
GeneratorMap函数用来生成地图
生成的Tile地板的坐标再存放到list中,方便之后使用
public List<coord> allTileCoords = new List<coord>();
void GeneratorMap()
{
for (var i = 0; i < mapSize.x; i++)
{
for (var j = 0; j < mapSize.y; j++)
{
Vector2 newPos = new Vector2 (i,j);
GameObject spawnTile = Instantiate(tilePrefab, newPos, Quaternion.identity);
spawnTile.transform.SetParent(tilePoint);
spawnTile.transform.localScale *= (1-outlinePercent);
//将生成的Tile坐标存放到list中
allTileCoords.Add(new coord(i,j));
}
}
}
//创建一个结构体才存放坐标
public struct coord{
public int x,y;
public coord(int _x , int _y)
{
x = _x;
y = _y;
}
}
这样就可以生成一个带有间隙的Tile地板,如果不要间隙,把outlinePercent设置为0
随机生成障碍物:
这里利用立方体来当障碍物
for (var i = 0; i < obsCount; i++)
{
//在生成的Tile地板中随机选择一个坐标
coord randomCoord = allTileCoords[UnityEngine.Random.Range(0, allTileCoords.Count)];
Vector2 newPos = new Vector2 (randomCoord.x, randomCoord.y);
GameObject spawnObs = Instantiate(obsPrefab, newPos, Quaternion.identity);
spawnObs.transform.SetParent(tilePoint);
spawnObs.transform.localScale *= (1-outlinePercent);
}
不过,这样随机生成会造成在同一个位置生成重复的障碍物
因此,利用洗牌算法,我们定义一个ShuffleCoords函数,把地板的坐标传进去,洗牌后再传回一个coord类的数组
coord[] ShuffleCoords( coord[] dataArray )
{
for (int i = 0; i < dataArray.Length; i++)
{
int randomNum = Random.Range(i,dataArray.Length);
coord temp = dataArray[randomNum];
dataArray[randomNum] = dataArray[i];
dataArray[i] = temp;
}
return dataArray;
}
之后,再改写一下生成障碍物的方法
定义一个shuffleQueue队列,把洗牌后的坐标存进去,每次生成障碍物就从队列里取出一个坐标,这样就不会产生重复的障碍物了
//调用洗牌算法,将洗牌后的坐标存入队列
shuffleQueue = new Queue<coord>(ShuffleCoords(allTileCoords.ToArray()));
for (var i = 0; i < obsCount; i++)
{
coord randomCoord = shuffleQueue.Dequeue();
shuffleQueue.Enqueue(randomCoord);
Vector2 newPos = new Vector2 (randomCoord.x,randomCoord.y);
GameObject spawnObs = Instantiate(obsPrefab, newPos, Quaternion.identity);
spawnObs.transform.SetParent(tilePoint);
spawnObs.transform.localScale *= (1-outlinePercent);
}