Files
Cielonos/Assets/Scripts/MainGame/GameRun/Map/Zone/ZoneManager.cs
SoulliesOfficial 39b43680a9 爆更
2026-07-18 03:16:20 -04:00

270 lines
11 KiB
C#

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<ZoneManager>
{
[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<SpawnPoint>(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<string, List<SpawnPoint>> spawnPoints = new Dictionary<string, List<SpawnPoint>>();
protected override void Awake()
{
base.Awake();
RebuildMapData();
}
public void SetupZone(ZoneData data)
{
// 生成敌人
if (data.enemySpawns != null)
{
foreach (KeyValuePair<ZoneData.SpawnPointKey, string> enemySpawn in data.enemySpawns)
{
string group = enemySpawn.Key.group;
int index = enemySpawn.Key.index;
if (spawnPoints.TryGetValue(group, out List<SpawnPoint> 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);
}
/// <summary>
/// 仅生成交互物体,跳过敌人生成。用于重入已完成的战斗节点。
/// </summary>
public void SetupZoneWithoutEnemies(ZoneData data)
{
SpawnInteractables(data);
}
/// <summary>
/// 根据 ZoneData 中配置的 interactableSpawns 动态生成所有配置的交互物体。
/// </summary>
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<SpawnPoint> 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<SpawnPoint> 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<Vector3> occupiedPositions = request.occupiedPositions;
if (spawnPoints.TryGetValue(spawnGroup, out List<SpawnPoint> 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<Vector3> 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<Vector3> 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;
}
}
}