在过去的几个月里,我一直在创建一个类似于《以撒的结合》和《进入地牢》的 2D 地牢爬行器,灵感来自旧版塞尔达游戏。没有太多细节,主场景包含玩家、UI 和一些其他对象,如游戏控制器、房间控制器等。当按下播放键时,主场景会填充“房间”,这些房间是它们自己独立的场景。这些场景包含房间的 tilemap、根据敌人状态打开和关闭门的门处理程序,以及一个 ObjectRoomSpawner,它负责在房间内生成敌人/可生成物(例如 healthpots)。到目前为止,我一直在为敌人使用一个简单的 moveTowards 函数,但这让游戏玩法感觉有点无聊。
我的问题是,如何在运行时为我的场景烘焙导航网格?虽然它并不是真正按程序生成的,但游戏的每个实例都将具有与我创建的一定数量的房间不同的地牢布局,然后添加到 RoomController。到目前为止我尝试过的一切都没有成功。我设法完成的最远的事情是为每个房间的第一个实例生成导航网格,但是整个地牢中那个房间的任何副本都没有。我尝试过的一些事情是在每个“房间”场景中创建一个 NavMesh 游戏对象,然后将修改器组件添加到房间网格下的每个瓦片地图。我还尝试在主场景中创建一个游戏对象,并使用 Unity 在运行时烘焙 NavMesh 的教程页面,但无济于事。希望有人能帮助我指明正确的方向。下面是我的房间和房间控制器脚本。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Room : MonoBehaviour
{
public int Width;
public int Height;
public int X;
public int Y;
private bool updatedDoors = false;
public Room(int x, int y)
{
X = x;
Y = y;
}
public Door leftDoor;
public Door rightDoor;
public Door topDoor;
public Door bottomDoor;
public GameObject replaceWallTop;
public GameObject replaceWallBottom;
public GameObject replaceWallLeft;
public GameObject replaceWallRight;
public List<Door> doors = new List<Door>();
// Start is called before the first frame update
void Start()
{
if (RoomController.instance == null)
{
Debug.Log("You pressed play in the wrong scene!");
return;
}
Door[] ds = GetComponentsInChildren<Door>();
foreach (Door d in ds)
{
doors.Add(d);
switch (d.doorType)
{
case Door.DoorType.right:
rightDoor = d;
break;
case Door.DoorType.left:
leftDoor = d;
break;
case Door.DoorType.top:
topDoor = d;
break;
case Door.DoorType.bottom:
bottomDoor = d;
break;
}
}
RoomController.instance.RegisterRoom(this);
}
void Update()
{
if (name.Contains("End") && !updatedDoors)
{
RemoveUnconnectedDoors();
updatedDoors = true;
}
}
public void RemoveUnconnectedDoors()
{
Debug.Log("removing doors");
foreach (Door door in doors)
{
switch (door.doorType)
{
case Door.DoorType.right:
if (GetRight() == null)
{
door.gameObject.SetActive(false);
replaceWallRight.SetActive(true);
}
break;
case Door.DoorType.left:
if (GetLeft() == null)
{
door.gameObject.SetActive(false);
replaceWallLeft.SetActive(true);
}
break;
case Door.DoorType.top:
if (GetTop() == null)
{
door.gameObject.SetActive(false);
replaceWallTop.SetActive(true);
}
break;
case Door.DoorType.bottom:
if (GetBottom() == null)
{
door.gameObject.SetActive(false);
replaceWallBottom.SetActive(true);
}
break;
}
}
}
public Room GetRight()
{
if (RoomController.instance.DoesRoomExist(X + 1, Y))
{
return RoomController.instance.FindRoom(X + 1, Y);
}
return null;
}
public Room GetLeft()
{
if (RoomController.instance.DoesRoomExist(X - 1, Y))
{
return RoomController.instance.FindRoom(X - 1, Y);
}
return null;
}
public Room GetTop()
{
if (RoomController.instance.DoesRoomExist(X, Y + 1))
{
return RoomController.instance.FindRoom(X, Y + 1);
}
return null;
}
public Room GetBottom()
{
if (RoomController.instance.DoesRoomExist(X, Y - 1))
{
return RoomController.instance.FindRoom(X, Y - 1);
}
return null;
}
void OnDrawGizmos()
{
Gizmos.color = Color.red;
Gizmos.DrawWireCube(transform.position, new Vector3(Width, Height, 0));
}
public Vector3 GetRoomCentre()
{
return new Vector3(X * Width, Y * Height);
}
void OnTriggerEnter2D(Collider2D other)
{
if (other.tag == "Player")
{
RoomController.instance.OnPlayerEnterRoom(this);
}
}
}
和
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
using System.Linq;
public class RoomInfo
{
public string name;
public int X;
public int Y;
}
public class RoomController : MonoBehaviour
{
public static RoomController instance;
string currentWorldName = "Basement";
RoomInfo currentLoadRoomData;
Room currRoom;
Queue<RoomInfo> loadRoomQueue = new Queue<RoomInfo>();
public List<Room> loadedRooms = new List<Room>();
bool isLoadingRoom = false;
bool spawnedBossRoom = false;
bool updatedRooms = false;
void Awake()
{
instance = this;
}
void Start()
{
//LoadRoom("Start", 0, 0);
//LoadRoom("Empty", 1, 0);
//LoadRoom("Empty", -1, 0);
//LoadRoom("Empty", 0, 1);
//LoadRoom("Empty", 0, -1);
}
void Update()
{
UpdateRoomQueue();
}
void UpdateRoomQueue()
{
if (isLoadingRoom)
{
return;
}
if (loadRoomQueue.Count == 0)
{
if (!spawnedBossRoom)
{
StartCoroutine(SpawnBossRoom());
}
else if (spawnedBossRoom && !updatedRooms)
{
foreach (Room room in loadedRooms)
{
room.RemoveUnconnectedDoors();
}
UpdateRooms();
updatedRooms = true;
}
return;
}
currentLoadRoomData = loadRoomQueue.Dequeue();
isLoadingRoom = true;
StartCoroutine(LoadRoomRoutine(currentLoadRoomData));
}
IEnumerator SpawnBossRoom()
{
spawnedBossRoom = true;
yield return new WaitForSeconds(0.5f);
if (loadRoomQueue.Count == 0)
{
Room bossRoom = loadedRooms[loadedRooms.Count - 1];
Room tempRoom = new Room(bossRoom.X, bossRoom.Y);
Destroy(bossRoom.gameObject);
var roomToRemove = loadedRooms.Single(r => r.X == tempRoom.X && r.Y == tempRoom.Y);
loadedRooms.Remove(roomToRemove);
LoadRoom("End", tempRoom.X, tempRoom.Y);
}
}
public void LoadRoom(string name, int x, int y)
{
if (DoesRoomExist(x, y) == true)
{
return;
}
RoomInfo newRoomData = new RoomInfo();
newRoomData.name = name;
newRoomData.X = x;
newRoomData.Y = y;
loadRoomQueue.Enqueue(newRoomData);
}
IEnumerator LoadRoomRoutine(RoomInfo info)
{
string roomName = currentWorldName + info.name;
AsyncOperation loadRoom = SceneManager.LoadSceneAsync(roomName, LoadSceneMode.Additive);
while (loadRoom.isDone == false)
{
yield return null;
}
}
public void RegisterRoom(Room room)
{
if (!DoesRoomExist(currentLoadRoomData.X, currentLoadRoomData.Y))
{
room.transform.position = new Vector3(
currentLoadRoomData.X * room.Width,
currentLoadRoomData.Y * room.Height,
0
);
room.X = currentLoadRoomData.X;
room.Y = currentLoadRoomData.Y;
room.name = currentWorldName + "-" + currentLoadRoomData.name + " " + room.X + ", " + room.Y;
room.transform.parent = transform;
isLoadingRoom = false;
if (loadedRooms.Count == 0)
{
CameraController.instance.currRoom = room;
}
loadedRooms.Add(room);
}
else
{
Destroy(room.gameObject);
isLoadingRoom = false;
}
}
public bool DoesRoomExist(int x, int y)
{
return loadedRooms.Find(item => item.X == x && item.Y == y) != null;
}
public Room FindRoom(int x, int y)
{
return loadedRooms.Find(item => item.X == x && item.Y == y);
}
public string GetRandomRoomName()
{
string[] possibleRooms = new string[] {
"Empty",
"Basic1",
"Basic2",
"Basic3"
};
return possibleRooms[Random.Range(0, possibleRooms.Length)];
}
public void OnPlayerEnterRoom(Room room)
{
CameraController.instance.currRoom = room;
currRoom = room;
StartCoroutine(RoomCoroutine());
}
public IEnumerator RoomCoroutine()
{
yield return new WaitForSeconds(0.2f);
UpdateRooms();
}
public void UpdateRooms()
{
foreach (Room room in loadedRooms)
{
if (currRoom != room)
{
EnemyController[] enemies = room.GetComponentsInChildren<EnemyController>();
if (enemies != null)
{
foreach (EnemyController enemy in enemies)
{
enemy.notInRoom = true;
}
foreach (Door door in room.GetComponentsInChildren<Door>())
{
door.doorCollider.SetActive(false);
}
}
else
{
foreach (Door door in room.GetComponentsInChildren<Door>())
{
door.doorCollider.SetActive(false);
}
}
}
else
{
EnemyController[] enemies = room.GetComponentsInChildren<EnemyController>();
if (enemies.Length > 0)
{
foreach (EnemyController enemy in enemies)
{
enemy.notInRoom = false;
Debug.Log("In room");
}
foreach (Door door in room.GetComponentsInChildren<Door>())
{
door.doorCollider.SetActive(true);
}
}
else
{
foreach (Door door in room.GetComponentsInChildren<Door>())
{
door.doorCollider.SetActive(false);
}
}
}
}
}
}
这是我第一次在这里发帖,所以如果格式不对或有任何遗漏,请告诉我!