using System; using System.Collections.Generic; using System.Linq; using Cielonos.MainGame.Rewards; using Sirenix.OdinInspector; using SLSUtilities.General; using UnityEngine; using UnityEngine.AI; namespace Cielonos.MainGame.Map { public partial class ZoneManager : Singleton { [Title("Editor Tools")] [Button("Rebuild Spawn Points"), PropertyOrder(-100)] public void RebuildMapData() { if(spawnPointContainer == null) { Debug.LogWarning("[ZoneManager] Spawn Point Container is not assigned."); return; } spawnPoints.Clear(); // 1. 查找场景中所有生成点 SpawnPoint[] allPoints = spawnPointContainer.GetComponentsInChildren(true); // 2. 按 groupName 分组,并按 Hierarchy 顺序排序 (保证 Index 确定性) var grouped = allPoints .GroupBy(p => p.groupName) .OrderBy(g => g.Key); foreach (var group in grouped) { // 按在 Hierarchy 中的顺序排序列表 var list = group.OrderBy(p => p.transform.GetSiblingIndex()).ToList(); spawnPoints[group.Key] = list; // 3. 将计算好的 Index 写入到具体物体上(持久化) for (int i = 0; i < list.Count; i++) { list[i].SetData(i); // 调用子物体的方法设置数据 } } //Debug.Log($"[ZoneManager] Rebuilt map: Found {allPoints.Length} points in {spawnPoints.Count} groups."); } } // 继承 Singleton 保持运行时单例特性 public partial class ZoneManager { // 运行时查找用的字典 [Title("Runtime Data")] [Required] public GameObject spawnPointContainer; public Dictionary> spawnPoints = new Dictionary>(); protected override void Awake() { base.Awake(); RebuildMapData(); } public void SetupZone(ZoneData data) { // 生成敌人 if (data.enemySpawns != null) { foreach (KeyValuePair enemySpawn in data.enemySpawns) { string group = enemySpawn.Key.group; int index = enemySpawn.Key.index; if (spawnPoints.TryGetValue(group, out List points)) { SpawnPoint point = points[index]; point.GetTransform(out Vector3 position, out Quaternion rotation); GameObject enemyPrefab = MapBaseCollection.Enemies[enemySpawn.Value]; Instantiate(enemyPrefab, position, rotation); } } } // 生成交互物体 SpawnInteractables(data); } /// /// 仅生成交互物体,跳过敌人生成。用于重入已完成的战斗节点。 /// public void SetupZoneWithoutEnemies(ZoneData data) { SpawnInteractables(data); } /// /// 根据 ZoneData 中配置的 interactableSpawns 动态生成所有配置的交互物体。 /// private void SpawnInteractables(ZoneData data) { if (data.interactableSpawns == null) return; // 遍历关卡数据中配置的每一种交互物定义 foreach (var def in data.interactableSpawns) { // 从全局配置合集查找对应的交互物预制体 if (!MapBaseCollection.Interactables.TryGetValue(def.interactableId, out GameObject prefab)) { Debug.LogWarning($"[ZoneManager] 交互物体 '{def.interactableId}' 在 MapBaseCollection.Interactables 中未找到。"); continue; } // 遍历该类型交互物在场景里的所有生成点,逐个进行实例化 foreach (ZoneData.SpawnPointKey key in def.spawnPoints) { if (spawnPoints.TryGetValue(key.group, out List points) && key.index < points.Count) { SpawnPoint point = points[key.index]; point.GetTransform(out Vector3 position, out Quaternion rotation); Instantiate(prefab, position, rotation); } else { Debug.LogWarning($"[ZoneManager] 生成点 '{key.group}_{key.index}' 不存在,跳过 '{def.interactableId}' 的生成。"); } } } } public bool TrySpawnInteractable(string interactableId, out GameObject instance) { return TrySpawnInteractable(interactableId, interactableId, out instance); } public bool TrySpawnInteractable(string interactableId, string spawnGroup, out GameObject instance) { instance = null; if (string.IsNullOrWhiteSpace(interactableId)) { return false; } if (string.IsNullOrWhiteSpace(spawnGroup)) { spawnGroup = interactableId; } if (!MapBaseCollection.Interactables.TryGetValue(interactableId, out GameObject prefab) || prefab == null) { Debug.LogWarning($"[ZoneManager] 交互物体 '{interactableId}' 在 MapBaseCollection.Interactables 中未找到。"); return false; } if (!spawnPoints.TryGetValue(spawnGroup, out List points) || points == null || points.Count == 0) { Debug.LogWarning($"[ZoneManager] 交互物体 '{interactableId}' 没有可用生成点 group '{spawnGroup}'。"); return false; } SpawnPoint point = points[0]; point.GetTransform(out Vector3 position, out Quaternion rotation); instance = Instantiate(prefab, position, rotation); return instance != null; } public bool TrySpawnInteractable(string interactableId, CombatRewardSpawnRequest request, out GameObject instance) { instance = null; if (string.IsNullOrWhiteSpace(interactableId)) { return false; } if (!MapBaseCollection.Interactables.TryGetValue(interactableId, out GameObject prefab) || prefab == null) { Debug.LogWarning($"[ZoneManager] 交互物体 '{interactableId}' 在 MapBaseCollection.Interactables 中未找到。"); return false; } ResolveRewardSpawnPose(request, out Vector3 position, out Quaternion rotation); instance = Instantiate(prefab, position, rotation); return instance != null; } private void ResolveRewardSpawnPose(CombatRewardSpawnRequest request, out Vector3 position, out Quaternion rotation) { string spawnGroup = string.IsNullOrWhiteSpace(request.spawnGroup) ? "CombatReward" : request.spawnGroup; float spacing = Mathf.Max(0.25f, request.spacing); IReadOnlyList occupiedPositions = request.occupiedPositions; if (spawnPoints.TryGetValue(spawnGroup, out List points) && points != null && points.Count > 0) { int clampedIndex = Mathf.Clamp(request.spawnIndex, 0, points.Count - 1); SpawnPoint point = points[clampedIndex]; point.GetTransform(out position, out rotation); if (request.spawnIndex < points.Count && !IsNearOccupiedPosition(position, occupiedPositions, spacing)) { position = SampleNavMesh(position); return; } point = points[0]; point.GetTransform(out Vector3 center, out rotation); position = FindOffsetRewardPosition(center, request.spawnIndex - points.Count + 1, spacing, occupiedPositions); return; } Transform playerTransform = MainGameManager.Player != null ? MainGameManager.Player.transform : transform; position = request.spawnIndex <= 0 ? playerTransform.position : FindOffsetRewardPosition(playerTransform.position, request.spawnIndex, spacing, occupiedPositions); rotation = playerTransform.rotation; Debug.LogWarning($"[ZoneManager] 未找到奖励生成点 group '{spawnGroup}',使用玩家坐标作为 Combat 奖励兜底生成位置。"); } private static Vector3 FindOffsetRewardPosition(Vector3 center, int offsetIndex, float spacing, IReadOnlyList occupiedPositions) { int startIndex = Mathf.Max(0, offsetIndex); for (int attempt = 0; attempt < 24; attempt++) { int candidateIndex = startIndex + attempt; int ring = candidateIndex / 8 + 1; float angle = candidateIndex * 137.50777f * Mathf.Deg2Rad; Vector3 offset = new Vector3(Mathf.Cos(angle), 0f, Mathf.Sin(angle)) * spacing * ring; Vector3 candidate = SampleNavMesh(center + offset); if (!IsNearOccupiedPosition(candidate, occupiedPositions, spacing)) { return candidate; } } return SampleNavMesh(center + Vector3.right * spacing * (startIndex + 1)); } private static bool IsNearOccupiedPosition(Vector3 position, IReadOnlyList occupiedPositions, float spacing) { if (occupiedPositions == null) { return false; } float minSqrDistance = spacing * spacing; for (int i = 0; i < occupiedPositions.Count; i++) { if ((occupiedPositions[i] - position).sqrMagnitude < minSqrDistance) { return true; } } return false; } private static Vector3 SampleNavMesh(Vector3 position) { return NavMesh.SamplePosition(position, out NavMeshHit hit, 1.5f, NavMesh.AllAreas) ? hit.position : position; } } }