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