问小白 wenxiaobai
资讯
历史
科技
环境与自然
成长
游戏
财经
文学与艺术
美食
健康
家居
文化
情感
汽车
三农
军事
旅行
运动
教育
生活
星座命理

Unity2D Roguelike游戏房间随机生成技术详解

创作时间:
2025-01-22 09:25:21
作者:
@小白创作中心

Unity2D Roguelike游戏房间随机生成技术详解

1.目标

在第一期随机地图生成中,我们已经完成了我们想要的地图的随机生成功能,在这一期中我们将来完善每一个房间的随机生成,先来看效果。

可以自行添加房间中想要生成的物体类型(在这里我们有障碍物和宝箱两种),我们可以设置它们的生成数量,并且它们是不会重叠的,且我们可以在tileMap中设置它们的生成范围,以确保不会出现障碍物堵住房间出入口的情况。
可以自行设置障碍物和宝箱出现的区域,且设置方便简单,直接使用TilePalette编辑。


可以直接使用TilePalette去编辑我们的障碍物和宝箱出现的区域,非常方便。

2.实现原理

可以看到,我们的TileMap是由边长为1的正方形构成,每一个Tile的长宽相等,且间距相等。
我们可以把整个房间看作是由一个个边长为1的tile拼接而成。

Physics2D.OverlapCircleAll Unity自带的方法,该方法会返回一个Collider2D[]的数组(检测某一点一定范围内的拥有碰撞器的且处于参数中指定的LayerMask的游戏物体的数组)
我们来看看我们的代码该怎么写,首先填入一个checkPos(需要检测的位置),在填入一个检测范围(该方法为圆形检测,我们每一个方块的边长为1,每一个tile的生成位置都在每一个方块的正中心,所以我们的圆形检测的半径必须得小于0.5(不然会检测到其他块),在这里我们直接填入0.1),在第三个参数LayerMask中填入我们想要检测的物体的Layer,在这里我们想要检测物体是否带有ChestSpanwerLayerMask,也就是检测该位置是否是一个宝箱生成的预设位置。
我们先不用去管if语句块中的内容,来看看我们该怎么去设置ChestSpanwerLayerMask。

在Room脚本中设置 ChestSpawnerLayerMask。

在整体的TileMap下,设置你需要生成的障碍物或宝箱之类的Tile的位置,并为其挂载上不同的Layer与碰撞器,然后由于房间是由一个个边长为1的小方块组成的,那么我们很容易的就能去遍历所有房间中的小方块的中心位置,在每一个中心位置做一个OverlapCircleAll,根据OverlapCircleAll的来确定是否要在该位置生成障碍物或宝箱。

3.完整代码与项目设置

public class Room : MonoBehaviour
{
    public int stepTostart = -1;
    public GameObject rightDoor;
    public GameObject leftDoor;
    public GameObject topDoor;
    public GameObject bottomDoor;
    public TextMeshProUGUI myText;
    [Header("房间长宽")]
    public float X = 16;//根据你自己的房间的长宽自行设置,每一个小方块的边长为1,很容易得出
    public float Y = 8;
    [Header("房间内实体")]
    public LayerMask ObstalceSpawnerLayerMask;
    public int ObstacleCount = 10; // 设置生成障碍物的数量
    public GameObject Obstacle;
    public LayerMask ChestSpawnerLayerMask;
    public int ChestCount = 1;
    public GameObject Chest;
    // Start is called before the first frame update
    void Awake()
    {
        GameObject canvas = this.transform.Find("Canvas").gameObject;
        if (canvas != null)
        {
            myText = canvas.transform.Find("Text").GetComponent<TextMeshProUGUI>();
        }
        else
        {
            Debug.LogError("Canvas not found. Please check the spelling and the object hierarchy.");
        }
    }
    private void Start()
    {
        List<Vector3> possiblePositions = new List<Vector3>();//障碍物预生成点
        List<Vector3> chestPositions=new List<Vector3>();//宝箱预生成点
        for (int i = 0; i < X; i++)//两层for循环 遍历所有房间中的位置
        {
            for (int j = 0; j < Y; j++)
            {
                Vector3 checkPos = new Vector3(transform.position.x - (X / 2 - 0.5f) + i, transform.position.y - (Y / 2 - 0.5f)+j,0);//检查点
                if (Physics2D.OverlapCircleAll(checkPos, 0.1f, ObstalceSpawnerLayerMask).Length > 0)
                {
                    //GameObject newObstacle = Instantiate(Obstacle, new Vector3(transform.position.x - (X / 2 - 0.5f) + i, transform.position.y - (Y / 2 - 0.5f) + j, 0), Quaternion.identity);
                    //newObstacle.transform.parent = this.transform;
                    possiblePositions.Add(checkPos);
                }
                if(Physics2D.OverlapCircleAll(checkPos, 0.1f, ChestSpawnerLayerMask).Length > 0)
                {
                    chestPositions.Add(checkPos);
                    Debug.Log("find chest");
                }
            }
        }
        for (int i = 0; i < ObstacleCount && possiblePositions.Count > 0; i++)
        {
            int randomIndex = Random.Range(0, possiblePositions.Count);
            GameObject newObstacle = Instantiate(Obstacle, possiblePositions[randomIndex], Quaternion.identity);
            newObstacle.transform.parent = this.transform;
            possiblePositions.RemoveAt(randomIndex);
        }
        for (int i = 0; i < ChestCount && chestPositions.Count > 0; i++)
        { //根据设置的生成数量从预生成点中随机抽取位置生成
            int randomIndex = Random.Range(0, chestPositions.Count);
            GameObject newObstacle = Instantiate(Chest, chestPositions[randomIndex], Quaternion.identity);
            newObstacle.transform.parent = this.transform;
            chestPositions.RemoveAt(randomIndex);
        }
    }
    // Update is called once per frame
    void Update()
    {
    }
    public void ChangeText()
    {
        if (myText != null)
        {
            myText.text = stepTostart.ToString();
        }
        else
        {
            Debug.LogError("myText is null. Did you forget to attach a TextMeshPro component?");
        }
    }
}  

4.总结

整体功能的实现由于本人的技术不足,实现逻辑都非常暴力,整体性能消耗较大,如果有更好的方法欢迎提出,由于图文讲解不是很清晰与方便,如果本篇文章讲解的内容你很感兴趣,但是在阅读后并不是很能理解该功能的实现过程,欢迎留言告知,如果告知人数较多,将会在其他平台以视频形式制作一期教程。
如有错漏 恳请指出 谢谢

© 2023 北京元石科技有限公司 ◎ 京公网安备 11010802042949号