想做一个类似以撒的肉鸽游戏,然后记录一下进度,也做个学习的总结笔记吧,目前想到的要素:随机地图生成,敌人AI,角色控制和攻击,一些动画,还有随机掉落物品,商店。
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class RoomGenerator : MonoBehaviour
{
public enum Direction
{
up,
down,
left,
right
};
public Direction direction;
[Header("房间信息")]
public GameObject roomPrefab;
public int roomNumber;
public Color startColor, endColor;
private GameObject endRoom;
[Header("位置控制")]
public Transform generatorPoint;
public float xOffset;
public float yOffset;
public LayerMask roomLayer;
//这边以后可以考虑用自定义类,储存Room的详细数据
public List<GameObject> rooms = new List<GameObject>();
void Start()
{
for (int i = 0; i < roomNumber; i++)
{
rooms.Add(Instantiate(roomPrefab, generatorPoint.position, Quaternion.identity));
//改变point位置
ChangePointPos();
}
rooms[0].GetComponent<SpriteRenderer>().color = startColor;
endRoom = rooms[0];
foreach(var room in rooms)
{
//如果当前的room的向量长度比endRoom大,则将endRoom重新赋值
//sqrMagnitude是长度的平方,要比开方magnitude快很多
if (room.transform.position.sqrMagnitude > endRoom.transform.position.sqrMagnitude)
{
endRoom = room;
}
endRoom.GetComponent<SpriteRenderer>().color = endColor;
}
}
// Update is called once per frame
void Update()
{
if (Input.anyKeyDown)
{
//随机键刷新新地图
SceneManager.LoadScene(SceneManager.GetActiveScene().name);
}
}
private void ChangePointPos()
{
do
{
direction = (Direction)Random.Range(0, 4);
switch (direction)
{
case Direction.up:
generatorPoint.position += new Vector3(0, yOffset, 0);
break;
case Direction.down:
generatorPoint.position += new Vector3(0, -yOffset, 0);
break;
case Direction.left:
generatorPoint.position += new Vector3(-xOffset, 0, 0);
break;
case Direction.right:
generatorPoint.position += new Vector3(xOffset, 0, 0);
break;
}
} while (Physics2D.OverlapCircle(generatorPoint.position, 0.2f, roomLayer));
}
}
大致代码就如上所示,目前只是初步完成了房间的随机生成,以及初始和结束BOSS房的计算,目前是用List<GameObject>来储存生成的房间,之后可以考虑改成二维数组或者自定义类,可以储存更多信息,比如生成的房间类型、房间的Position、以及更细致的设定(比如当前房间生成怪物位置之类的)。
房间生成逻辑稍微简单总结一下:先生成一个起始点的初始房间,然后根据偏移量以及随机方向去寻找下一个房间,找到下一个房间生成点之后判断是否有房间,如果有的话就继续根据这个新点的上下左右继续寻找生成,如果没有的话就直接生成,这样可以避免一个房间四周都有房间时,导致无法生成新房间。
然后初始房间设定没问题,但是结束房间的判断可能还需要改进一下,目前这个性能比较好,但是有时候可能判断的不是最远房间。
上次说到要优化的内容,储存生成的房间改成了List<Room>,然后可以根据个人需要声明你想要用的变量,这边初步想要房间类型,以及判断房间上下左右是否有门的变量,最后四个bool值类型是用于BFS查找时,防止往来的方向查找的变量。
public enum RoomType
{
normalRoom,
startRoom,
treasureRoom,
bossRoom,
shop
}
public RoomType roomType;
public GameObject doorLeft, doorRight, doorUp, doorDown;
//上下左右是否有房间
public bool roomLeft, roomRight, roomUp, roomDown;
//判断上一个房间来源方向,用于查找步数
public bool fromLeft = false;
public bool fromRight = false;
public bool fromUp = false;
public bool fromDown = false;
还有最远房间判断优化,这里可以用xy偏移量大小来查找,但是由于Room的X为18,y为8,所以需要将X除以2在进行查找会更好,然后标出距离原点房间的step,但是如果遇到U型地图,我们需要的最末端应该是另一端的U,但是实际偏移量最大的可能不是另一端,因此需要优化一下查找算法,这边使用了BFS算法,计算了实际需要经过最多房间的房间设置为最终房间。
private GameObject GetFarthestRoom()
{
GameObject lastRoom = rooms[0].gameObject;
Room currentRoom = rooms[0];
currentRoomQue.Enqueue(rooms[0]);
//一层一层查找符合条件的房间
while (currentRoomQue.Count > 0)
{
currentRoom = currentRoomQue.Dequeue();
FindNextRoom(currentRoom.fromUp, currentRoom.roomUp, currentRoom.transform.position, Direction.up);
FindNextRoom(currentRoom.fromDown, currentRoom.roomDown, currentRoom.transform.position, Direction.down);
FindNextRoom(currentRoom.fromLeft, currentRoom.roomLeft, currentRoom.transform.position, Direction.left);
FindNextRoom(currentRoom.fromRight, currentRoom.roomRight, currentRoom.transform.position, Direction.right);
}
lastRoom = currentRoom.gameObject;
return lastRoom;
}
private void FindNextRoom(bool fromBool, bool dirBool, Vector3 currentPos, Direction dir)
{
Vector3 offset = new Vector3(0, 0, 0);
switch (dir)
{
case Direction.up:
offset = new Vector3(0, yOffset, 0);
break;
case Direction.down:
offset = new Vector3(0, -yOffset, 0);
break;
case Direction.left:
offset = new Vector3(-xOffset, 0, 0);
break;
case Direction.right:
offset = new Vector3(xOffset, 0, 0);
break;
}
//方向上有房间时,且排除上一个房间
if (dirBool && !fromBool)
{
for (int i = 0; i < rooms.Count; i++)
{
//找到下一个的房间
if (rooms[i].transform.position == currentPos + offset)
{
currentRoomQue.Enqueue(rooms[i]);
switch (dir)
{
case Direction.up:
rooms[i].fromDown = true;
break;
case Direction.down:
rooms[i].fromUp = true;
break;
case Direction.left:
rooms[i].fromRight = true;
break;
case Direction.right:
rooms[i].fromLeft = true;
break;
default:
break;
}
break;
}
}
}
}
然后关于BOSS房我想的游戏逻辑是:最终房间再上下左右生成一个新房间,作为最后的BOSS房,这样BOSS房就可以保证只有一个通路通向它。
这边不复用之前的生成房间的逻辑有两个理由,第一是因为最终房间不一定是rooms列表中的最后一个,因此此时的generatorPoint还在最后生成的那个房间处,此时如果将generatorPoint=endroom.transform的话,再方法中更改generatorPoint时,endroom.transform也会随之更改,因为这是引用类型,他们都引用了内存堆上的地址,需要使用深拷贝来解决。但还有第二个问题,就是如果新生成的房间方向上有房间,那他会从此处再寻找上下左右,此时新生成的房间就会离最终房间有>2的房间距离了,因此新写一个单独生成单个房间的方法,如果以后还要加隐藏房间的话可以用他来实现。
private GameObject CreateOneWayRoom(Room currentRoom)
{
Vector3 nextRoom;
if (!currentRoom.roomRight)
{
nextRoom = new Vector3(currentRoom.transform.position.x + xOffset, currentRoom.transform.position.y, currentRoom.transform.position.z);
rooms.Add(Instantiate(roomPrefab, nextRoom, Quaternion.identity).GetComponent<Room>());
currentRoom.roomRight = true;
rooms[rooms.Count - 1].roomLeft = true;
}
else if (!currentRoom.roomLeft)
{
nextRoom = new Vector3(currentRoom.transform.position.x - xOffset, currentRoom.transform.position.y, currentRoom.transform.position.z);
rooms.Add(Instantiate(roomPrefab, nextRoom, Quaternion.identity).GetComponent<Room>());
currentRoom.roomLeft = true;
rooms[rooms.Count - 1].roomRight = true;
}
else if (!currentRoom.roomDown)
{
nextRoom = new Vector3(currentRoom.transform.position.x, currentRoom.transform.position.y - yOffset, currentRoom.transform.position.z);
rooms.Add(Instantiate(roomPrefab, nextRoom, Quaternion.identity).GetComponent<Room>());
currentRoom.roomDown = true;
rooms[rooms.Count - 1].roomUp = true;
}
else if (!currentRoom.roomUp)
{
nextRoom = new Vector3(currentRoom.transform.position.x, currentRoom.transform.position.y + yOffset, currentRoom.transform.position.z);
rooms.Add(Instantiate(roomPrefab, nextRoom, Quaternion.identity).GetComponent<Room>());
currentRoom.roomUp = true;
rooms[rooms.Count - 1].roomDown = true;
}
return rooms[rooms.Count - 1].gameObject;
}
然后宝箱房也可以按照这个逻辑生成,但是传过来的currentRoom需要判断一下是否有至少一个的方向上没有房间 ,Boss房不用考虑最终房间四个方向都占满的情况,是因为既然是最远的房间,那不可能四个方向都有房间。可以在Room类型中提供一个方法获取本房间一共有多少门。
我还设置了商店,商店的生成逻辑是直接从rooms列表中随机一个房间,只要他是normalRoom的房间类型,就将它修改成shop类型,这样避免覆盖其他的特殊类型房间。
按照目前的生成逻辑的话,三种特殊类型房间生成顺序可以随意,因为他们互相不干扰,即使宝箱房和BOSS房都选中了最后一个房间衍生,因为最远房间的特性至少会让他空出两个方向,因此也没有大碍。
随机生成几个:(红色为BOSS房,绿色为起点,黄色为宝箱房,蓝色为商店)