Unity中生成关卡内容时独立Boss/传送门房间的实现问题
程序化地牢关卡生成问题:Boss/传送门终点房间异常修复
问题现象
- 仅玩家房间生成物品,Boss/传送门终点房间无对应内容生成
- 生成对象未正确挂载到
RoomContent(itemParent)父节点 - 偶发Boss错误生成在敌人房间的情况
问题代码
RoomContentGenerator.cs
using Cinemachine; using System.Collections; using System.Collections.Generic; using System.Linq; using UnityEngine; using UnityEngine.Events; public class RoomContentGenerator : MonoBehaviour { [SerializeField] private RoomGenerator playerRoom, defaultRoom, bossRoom; List<GameObject> spawnedObjects = new List<GameObject>(); [SerializeField] private GraphTest graphTest; public Transform itemParent; [SerializeField] private CinemachineVirtualCamera cinemachineCamera; public UnityEvent RegenerateDungeon; private void Update() { if (Input.GetKeyDown(KeyCode.Space)) { foreach (var item in spawnedObjects) { Destroy(item); } RegenerateDungeon?.Invoke(); } } public void GenerateRoomContent(DungeonData dungeonData) { foreach (GameObject item in spawnedObjects) { DestroyImmediate(item); } spawnedObjects.Clear(); SelectPlayerSpawnPoint(dungeonData); SelectBossSpawnPoints(dungeonData); SelectEnemySpawnPoints(dungeonData); foreach (GameObject item in spawnedObjects) { if(item != null) item.transform.SetParent(itemParent, false); } } private void SelectPlayerSpawnPoint(DungeonData dungeonData) { int randomRoomIndex = UnityEngine.Random.Range(0, dungeonData.roomsDictionary.Count); Vector2Int playerSpawnPoint = dungeonData.roomsDictionary.Keys.ElementAt(randomRoomIndex); graphTest.RunDijkstraAlgorithm(playerSpawnPoint, dungeonData.floorPositions); Vector2Int roomIndex = dungeonData.roomsDictionary.Keys.ElementAt(randomRoomIndex); List<GameObject> placedPrefabs = playerRoom.ProcessRoom( playerSpawnPoint, dungeonData.roomsDictionary.Values.ElementAt(randomRoomIndex), dungeonData.GetRoomFloorWithoutCorridors(roomIndex) ); FocusCameraOnThePlayer(placedPrefabs[placedPrefabs.Count - 1].transform); spawnedObjects.AddRange(placedPrefabs); dungeonData.roomsDictionary.Remove(playerSpawnPoint); } private void FocusCameraOnThePlayer(Transform playerTransform) { cinemachineCamera.LookAt = playerTransform; cinemachineCamera.Follow = playerTransform; } private void SelectBossSpawnPoints(DungeonData dungeonData) { Vector2Int playerSpawnPoint = dungeonData.roomsDictionary.Keys.First(); Vector2Int farthestRoom = Vector2Int.zero; float maxDistance = 0f; foreach (KeyValuePair<Vector2Int, HashSet<Vector2Int>> roomData in dungeonData.roomsDictionary) { float distance = Vector2Int.Distance(playerSpawnPoint, roomData.Key); if (distance > maxDistance) { maxDistance = distance; farthestRoom = roomData.Key; } } List<GameObject> bossRoomObjects = bossRoom.ProcessRoom( farthestRoom, dungeonData.roomsDictionary[farthestRoom], dungeonData.GetRoomFloorWithoutCorridors(farthestRoom) ); spawnedObjects.AddRange(bossRoomObjects); } private void SelectEnemySpawnPoints(DungeonData dungeonData) { foreach (KeyValuePair<Vector2Int,HashSet<Vector2Int>> roomData in dungeonData.roomsDictionary) { spawnedObjects.AddRange( defaultRoom.ProcessRoom( roomData.Key, roomData.Value, dungeonData.GetRoomFloorWithoutCorridors(roomData.Key) ) ); } } }
BossRoom.cs
using System; using System.Collections; using System.Collections.Generic; using System.Linq; using UnityEngine; public class BossRoom : RoomGenerator { public enum RoomType { Boss, Portal } [SerializeField] private PrefabPlacer prefabPlacer; public List<EnemyPlacementData> enemyPlacementData; public List<ItemPlacementData> itemData; public GameObject bossObjectPrefab; public GameObject portalObjectPrefab; public RoomType roomType; public override List<GameObject> ProcessRoom(Vector2Int roomCenter, HashSet<Vector2Int> roomFloor, HashSet<Vector2Int> roomFloorNoCorridors) { ItemPlacementHelper itemPlacementHelper = new ItemPlacementHelper(roomFloor, roomFloorNoCorridors); List<GameObject> placedObjects = prefabPlacer.PlaceAllItems(itemData, itemPlacementHelper); placedObjects.AddRange(prefabPlacer.PlaceEnemies(enemyPlacementData, itemPlacementHelper)); GameObject selectedObjectPrefab = GetSelectedObjectPrefab(); GameObject selectedObject = prefabPlacer.CreateObject(selectedObjectPrefab, roomCenter + new Vector2(0.5f, 0.5f)); placedObjects.Add(selectedObject); return placedObjects; } private GameObject GetSelectedObjectPrefab() { return roomType == RoomType.Boss ? bossObjectPrefab : portalObjectPrefab; } }
关卡示意图

修复方案
1. 让Boss房间仅生成目标内容
修改BossRoom.ProcessRoom方法,移除物品和敌人的生成逻辑,只保留Boss或传送门:
public override List<GameObject> ProcessRoom(Vector2Int roomCenter, HashSet<Vector2Int> roomFloor, HashSet<Vector2Int> roomFloorNoCorridors) { List<GameObject> placedObjects = new List<GameObject>(); GameObject selectedObjectPrefab = GetSelectedObjectPrefab(); GameObject selectedObject = prefabPlacer.CreateObject(selectedObjectPrefab, roomCenter + new Vector2(0.5f, 0.5f)); placedObjects.Add(selectedObject); return placedObjects; }
2. 修正最远房间计算基准+避免重复处理Boss房间
- 在
RoomContentGenerator中添加成员变量保存玩家出生房间坐标 - 在
SelectBossSpawnPoints处理完后移除Boss房间,防止敌人房间逻辑重复处理 - 以玩家出生房间为基准计算最远房间,而非随机房间
修改后代码片段:
public class RoomContentGenerator : MonoBehaviour { // ... 原有成员 private Vector2Int _playerSpawnRoom; // 添加此变量保存玩家出生房间坐标 private void SelectPlayerSpawnPoint(DungeonData dungeonData) { int randomRoomIndex = UnityEngine.Random.Range(0, dungeonData.roomsDictionary.Count); Vector2Int playerSpawnPoint = dungeonData.roomsDictionary.Keys.ElementAt(randomRoomIndex); _playerSpawnRoom = playerSpawnPoint; // 保存玩家出生房间坐标 // ... 原有逻辑不变 } private void SelectBossSpawnPoints(DungeonData dungeonData) { if (dungeonData.roomsDictionary.Count == 0) return; Vector2Int farthestRoom = Vector2Int.zero; float maxDistance = 0f; foreach (KeyValuePair<Vector2Int, HashSet<Vector2Int>> roomData in dungeonData.roomsDictionary) { float distance = Vector2Int.Distance(_playerSpawnRoom, roomData.Key); // 用玩家出生房间计算距离 if (distance > maxDistance) { maxDistance = distance; farthestRoom = roomData.Key; } } List<GameObject> bossRoomObjects = bossRoom.ProcessRoom( farthestRoom, dungeonData.roomsDictionary[farthestRoom], dungeonData.GetRoomFloorWithoutCorridors(farthestRoom) ); spawnedObjects.AddRange(bossRoomObjects); dungeonData.roomsDictionary.Remove(farthestRoom); // 移除Boss房间,避免被敌人房间逻辑处理 } }
3. 确保父对象正确挂载
可选优化:在PrefabPlacer.CreateObject方法中直接设置父对象,避免统一设置阶段遗漏:
// 在PrefabPlacer类中修改CreateObject方法 public GameObject CreateObject(GameObject prefab, Vector3 position, Transform parent) { GameObject obj = Instantiate(prefab, position, Quaternion.identity); obj.transform.SetParent(parent, false); return obj; }
调用时传入itemParent即可。
内容的提问来源于stack exchange,提问作者Rejwen
相关产品推荐
相关产品推荐

