Unity2D Rouge地下城地图随机生成(BFS)第一期

1.目标

      随机生成房间,并在距离初始房间位置最远的房间生成BOSS房间。数字代表到达该房间需要经过的房间数目,0代表初始房间,数字最大值代表BOSS房间。

           

2.生成房间

      首先考虑如何生成随机的在不同方向上且不重叠的房间。

    [Header("房间信息")]
    public Room roomPrefab;
    public int roomCount = 10;

    [Header("位置控制")]
    public Transform gengratorPoint;
    public float xOffset; //根据自己的房间预制体长宽调整
    public float yOffset;

      我们先创建好自己的房间预制体,并在Unity中去拖动它们,看看它们的房间间隔是多少(不重叠情况下的间距),这关系到我们在生成房间的脚本中编写xOffset和yOffset的值。

      创建一个空的Room脚本,挂载到我们的房间预制体中,后续我们会在Room脚本中编写一些有用的代码。

public class Room : MonoBehaviour
{    
}

     我们回到正题,该如何生成房间呢?最直观的思路是,来一个for循环,根据生成的房间数量N执行N次,我们将房间的生成方向分为上下左右四个方向,那么创建一个枚举类型变量direction,随机从中抽取一个方向,在for循环中更改生成位置。

     我们先在Unity中创建一个空物体起名RoomGenerator,并在其下创建一个空子物体point,该子物体作为我们房间位置生成的起点。

      将下面创建的脚本RoomGenerator挂载到RoomGenerator上。

public class RoomGenerator : MonoBehaviour
{
    public enum direction { up, down, left, right };//创建房间生成方向的枚举
    public direction mydirection;//创建枚举实例

    [Header("房间信息")]
    public Room roomPrefab;
    public int roomCount = 10;
    public Room endRoom;

    [Header("位置控制")]
    public Transform gengratorPoint;//记得在Unity中将空物体Point拖入该位置,房间初始位置
    public float xOffset;//偏移量
    public float yOffset;

    public List<Room> rooms = new List<Room>();
    public IList<Vector2> roomsPosition = new List<Vector2>();创建一个Vector2 List存储已生成房间位置

    void Start()
    {
        //将初始房间位置加入已生成房间位置List
        roomsPosition.Add(new Vector2(gengratorPoint.position.x,gengratorPoint.position.y)) ;
        for (int i = 0; i < roomCount; i++)
        {
            rooms.Add(Instantiate(roomPrefab, gengratorPoint.position, Quaternion.identity));//将已生成房间加入rooms List
            if (i != roomCount - 1) ChangePointPos();    //无需知道最后一个点生成完之后的下个点的生成位置   
        }
    }
    public void ChangePointPos()//进行一个暴力循环,会从枚举中的四个方向随机抽取一个方向                               
    {                         //根据抽取的方向更改生成点的位置,然后在roomsPosition中查询
        Vector2 newPos;       //如果没有含有该位置的值,下次生成点的位置就确定了
        do                    //如果含有该位置的值,循环再循环直到找到为止
        {                      //不会出现死循环,如“回”字形,如果有这种想法,请再看看代码
            mydirection = (direction)UnityEngine.Random.Range(0, 4);
            switch (mydirection)
            {
                case direction.up:
                    newPos = new Vector2(gengratorPoint.position.x, gengratorPoint.position.y + yOffset);
                    break;
                case direction.down:
                    newPos = new Vector2(gengratorPoint.position.x, gengratorPoint.position.y - yOffset);
                    break;
                case direction.left:
                    newPos = new Vector2(gengratorPoint.position.x - xOffset, gengratorPoint.position.y);
                    break;
                case direction.right:
                    newPos = new Vector2(gengratorPoint.position.x + xOffset, gengratorPoint.position.y);
                    break;
                default:
                    newPos = new Vector2(gengratorPoint.position.x, gengratorPoint.position.y);
                    break;
            }
        } while (roomsPosition.Contains(newPos));
        roomsPosition.Add(newPos);  
        gengratorPoint.position = new Vector3(newPos.x, newPos.y, 0);
    }
}

    好,我们已经解决了房间生成重叠的问题,现在生成的房间不会重叠在一起了。我们接下来要确认最终房间的位置,起始房间的位置直接使用rooms[0]即可。

3.确认最终房间

       根据我们的设想,最终房间应该是距离初始房间最远的房间,那我们首先会想到的是,直接循环遍历rooms,将其中元素的坐标与初始房间坐标进行一个距离运算,选出最大的距离的房间,那我们来看是这个样子么。

        我们来看下面的图片,黄色箭头所指的位置是起始位置,而红色位置是我们通过坐标距离运算求得的最远坐标位置的房间,而实际上紫色才是我们预期的最远的BOSS房间。(如果选择利用曼哈顿距离确认最远房间同理)。 当我们遇到这种U型房间生成时就会出错。

       那我们接下来就会想到走迷宫时常用的BFS(广度优先算法来解决这个问题)

首先我们在我们的Room脚本中添加代码,初始值设置为-1。

public int stepTostart = -1;

我们在生成完所有房间以后在利用BFS来找到距离起点“最远”的房间

  roomsPosition.Add(new Vector2(gengratorPoint.position.x,gengratorPoint.position.y)) ;
    for (int i = 0; i < roomCount; i++)
      {
         rooms.Add(Instantiate(roomPrefab, gengratorPoint.position, Quaternion.identity));
         if (i != roomCount - 1) ChangePointPos();    //无需知道最后一个点生成完之后的下个点的生成位置   
      }
  BFS_SetStep();

private void BFS_SetStep()//设置遍历步数
{
    Queue<Room> queue=new Queue<Room>();
    queue.Enqueue(rooms[0]);
    int step = 0;
    while (queue.Count !=0) 
    {
        int length=queue.Count;
        for(int i=0;i<length; i++)
        {
            Room currentRoom= queue.Dequeue();
            endRoom = currentRoom;
            currentRoom.stepTostart=step;
            foreach(Room room in Neighbor(currentRoom))
            {
                if (room.stepTostart == -1)//若该点未遍历过则遍历
                {
                    queue.Enqueue(room);
                }
            }
        }
        ++step;
    }
}
    List<Room> Neighbor(Room currentRoom)//查找周围是否有可通路径
    {
       List<Room> neighbor=new List<Room>();
        Vector2 pos=(Vector2)currentRoom.transform.position;

        foreach (Room roomObject in rooms)//遍历所有已经生成的房间 如果有房间在当前的上下左右四个方向的任一方向则该路可以走通
        {
            Room room = roomObject.GetComponent<Room>();
            Vector2 roomPos = (Vector2)room.transform.position;

            if (roomPos == new Vector2(pos.x, pos.y + yOffset) ||
                roomPos == new Vector2(pos.x, pos.y - yOffset) ||
                roomPos == new Vector2(pos.x + xOffset, pos.y) ||
                roomPos == new Vector2(pos.x - xOffset, pos.y))
            {
                neighbor.Add(room);
            }
        }

        return neighbor;//返回新的需遍历名单
    }

我们来详细解释一下这段代码。 由于排版问题,可能这样看图并不直观,可移步图文讲解

好,当我们的程序运行到Neighbor方法时

继续往下走

至此,我们就能得到所有房间距离初始房间的距离了,将最大距离的房间设置为Boss房间就能完成我们的需求。

4.完整代码(包含图中房间 上下左右楼梯 的设置)

1.ROOM

public class Room : MonoBehaviour
{
    public int stepTostart = -1;

    public GameObject rightDoor;
    public GameObject leftDoor;
    public GameObject topDoor;
    public GameObject bottomDoor;

    public TextMeshProUGUI myText;
    // 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.");
        }
    }

    // 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?");
        }
    }
}

其中Grid是我的房间的TileMap的一些设置,不重要 

2.RoomGenerator 

public class RoomGenerator : MonoBehaviour
{
    [Header("Start And End")]
    public GameObject Player;
    public GameObject Boss;
    public enum direction { up, down, left, right };
    public direction mydirection;

    [Header("房间信息")]
    public Room roomPrefab;
    public int roomCount = 10;
    public Room endRoom;
    public float MaxFarDistance=0;

    [Header("位置控制")]
    public Transform gengratorPoint;
    public float xOffset;
    public float yOffset;

    public List<Room> rooms = new List<Room>();
    public IList<Vector2> roomsPosition = new List<Vector2>();
    // Start is called before the first frame update
    void Start()
    {
        roomsPosition.Add(new Vector2(gengratorPoint.position.x,gengratorPoint.position.y)) ;
        for (int i = 0; i < roomCount; i++)
        {
            //if (Mathf.Abs(gengratorPoint.position.x) + Mathf.Abs(gengratorPoint.position.y) > MaxFarDistance)
            //{
            //    endRoom = rooms[i];
            //    Debug.Log("endroom change");
            //    Debug.Log(rooms[i]);
            //}
            //MaxFarDistance = Mathf.Max(MaxFarDistance, Mathf.Abs(gengratorPoint.position.x) + Mathf.Abs(gengratorPoint.position.y));
            //Debug.Log(MaxFarDistance);
            rooms.Add(Instantiate(roomPrefab, gengratorPoint.position, Quaternion.identity));
            if (i != roomCount - 1) ChangePointPos();    //无需知道最后一个点生成完之后的下个点的生成位置   
        }

        //BFS设置最终房间。
        BFS_SetStep();
        foreach(var room in rooms)
        {
            room.ChangeText();
            if (room.stepTostart > MaxFarDistance)
            {
                MaxFarDistance = room.stepTostart;
                endRoom= room;
            }
        }

        for (int i = 0; i < rooms.Count; i++)
        {
            if (roomsPosition.Contains(new Vector2(rooms[i].GetComponent<Transform>().position.x, rooms[i].GetComponent<Transform>().position.y + yOffset)))
            {
                rooms[i].GetComponent<Room>().topDoor.SetActive(true); 
            }
            if (roomsPosition.Contains(new Vector2(rooms[i].GetComponent<Transform>().position.x, rooms[i].GetComponent<Transform>().position.y - yOffset)))
            {
                rooms[i].GetComponent<Room>().bottomDoor.SetActive(true);
            }
            if (roomsPosition.Contains(new Vector2(rooms[i].GetComponent<Transform>().position.x + xOffset, rooms[i].GetComponent<Transform>().position.y)))
            {
                rooms[i].GetComponent<Room>().rightDoor.SetActive(true);
            }
            if (roomsPosition.Contains(new Vector2(rooms[i].GetComponent<Transform>().position.x - xOffset, rooms[i].GetComponent<Transform>().position.y)))
            {
                rooms[i].GetComponent<Room>().leftDoor.SetActive(true);
            }
        }
        Instantiate(Player, rooms[0].GetComponent<Transform>().position, Quaternion.identity);
        Instantiate(Boss, endRoom.GetComponent<Transform>().position, Quaternion.identity);
    }

    private void BFS_SetStep()//设置遍历步数
    {
        Queue<Room> queue=new Queue<Room>();
        queue.Enqueue(rooms[0]);
        int step = 0;
        while (queue.Count !=0) 
        {
            int length=queue.Count;
            for(int i=0;i<length; i++)
            {
                Room currentRoom= queue.Dequeue();
                endRoom = currentRoom;
                currentRoom.stepTostart=step;
                foreach(Room room in Neighbor(currentRoom))
                {
                    if (room.stepTostart == -1)//若该点未遍历过则遍历
                    {
                        queue.Enqueue(room);
                    }
                }
            }
            ++step;
        }
    }

    List<Room> Neighbor(Room currentRoom)//查找周围是否有可通路径
    {
       List<Room> neighbor=new List<Room>();
        Vector2 pos=(Vector2)currentRoom.transform.position;

        foreach (Room roomObject in rooms)//遍历所有已经生成的房间 如果有房间在当前的上下左右四个方向的任一方向则该路可以走通
        {
            Room room = roomObject.GetComponent<Room>();
            Vector2 roomPos = (Vector2)room.transform.position;

            if (roomPos == new Vector2(pos.x, pos.y + yOffset) ||
                roomPos == new Vector2(pos.x, pos.y - yOffset) ||
                roomPos == new Vector2(pos.x + xOffset, pos.y) ||
                roomPos == new Vector2(pos.x - xOffset, pos.y))
            {
                neighbor.Add(room);
            }
        }

        return neighbor;//返回新的需遍历名单
    }



    // Update is called once per frame
    void Update()
    {

    }

    public void ChangePointPos()
    {
        Vector2 newPos;
        do
        {
            mydirection = (direction)UnityEngine.Random.Range(0, 4);
            switch (mydirection)
            {
                case direction.up:
                    newPos = new Vector2(gengratorPoint.position.x, gengratorPoint.position.y + yOffset);
                    break;
                case direction.down:
                    newPos = new Vector2(gengratorPoint.position.x, gengratorPoint.position.y - yOffset);
                    break;
                case direction.left:
                    newPos = new Vector2(gengratorPoint.position.x - xOffset, gengratorPoint.position.y);
                    break;
                case direction.right:
                    newPos = new Vector2(gengratorPoint.position.x + xOffset, gengratorPoint.position.y);
                    break;
                default:
                    newPos = new Vector2(gengratorPoint.position.x, gengratorPoint.position.y);
                    break;
            }
        } while (roomsPosition.Contains(newPos));
        roomsPosition.Add(newPos);  
        gengratorPoint.position = new Vector3(newPos.x, newPos.y, 0);
    }
}

 

    如有错漏,恳请指出,谢谢!

 参考自B站UP M_Studio的一部分思路。

  • 21
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值