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);
}