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的一部分思路。