You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.05 20:12:03