843 lines
40 KiB
C#
843 lines
40 KiB
C#
using Base;
|
||
using Data;
|
||
using Entity;
|
||
using Prefab;
|
||
using System;
|
||
using System.Collections.Generic;
|
||
using System.Linq;
|
||
using UnityEngine;
|
||
using UnityEngine.SceneManagement;
|
||
|
||
namespace Managers
|
||
{
|
||
/// <summary>
|
||
/// 管理游戏中所有实体(角色、建筑、子弹等)的生命周期、存储和分发。
|
||
/// 实现了维度感知,这意味着不同维度的实体数据是独立管理的。
|
||
/// </summary>
|
||
public class EntityManage : Utils.MonoSingleton<EntityManage>, ITick
|
||
{
|
||
/// <summary>
|
||
/// 维度感知的实体存储结构。
|
||
/// 外层字典按维度ID(DimensionId)索引,内层字典按派系键(FactionKey)索引,
|
||
/// 存储该派系下的实体预制体链表。
|
||
/// </summary>
|
||
private Dictionary<string, Dictionary<string, LinkedList<EntityPrefab>>> _dimensionFactionEntities = new();
|
||
|
||
/// <summary>
|
||
/// 维度感知的层级Transform缓存。
|
||
/// 外层字典按维度ID(DimensionId)索引,内层字典按层级名称(LayerName)索引,
|
||
/// 存储其对应的Transform。
|
||
/// </summary>
|
||
private Dictionary<string, Dictionary<string, Transform>> _dimensionLayerCache = new();
|
||
|
||
/// <summary>
|
||
/// 待添加的实体列表,用于在Tick循环中安全地将实体加入管理。
|
||
/// Tuple包含:Item1=维度ID,Item2=派系键,Item3=实体预制体。
|
||
/// </summary>
|
||
private List<Tuple<string, string, EntityPrefab>>
|
||
_pendingAdditions = new(); // Item1: DimensionId, Item2: FactionKey, Item3: EntityPrefab
|
||
|
||
/// <summary> 角色实体的预制体。 </summary>
|
||
public EntityPrefab characterPrefab;
|
||
public EntityPrefab monsterPrefab;
|
||
/// <summary> 建筑实体的预制体。 </summary>
|
||
public EntityPrefab buildingPrefab;
|
||
|
||
/// <summary> 子弹实体的预制体。 </summary>
|
||
public EntityPrefab bulletPrefab;
|
||
|
||
public EntityPrefab pickupPrefab;
|
||
|
||
/// <summary> 默认实体的预制体,用于生成失败时的回退。 </summary>
|
||
public EntityPrefab defaultEntityPrefab;
|
||
|
||
|
||
/// <summary>
|
||
/// 在指定维度中,根据派系键查找所有实体。
|
||
/// </summary>
|
||
/// <param name="dimensionId">维度的唯一标识符。</param>
|
||
/// <param name="factionKey">派系键。</param>
|
||
/// <returns>指定派系下的实体列表,如果未找到则返回空列表。</returns>
|
||
public LinkedList<EntityPrefab> FindEntitiesByFaction(string dimensionId, string factionKey)
|
||
{
|
||
// 如果在场景加载前或维度未在Program中注册,_dimensionFactionEntities可能还没有该维度ID的条目。
|
||
if (!_dimensionFactionEntities.ContainsKey(dimensionId))
|
||
{
|
||
Debug.LogWarning($"实体管理器:尝试在维度 '{dimensionId}' 中查找实体,但其内部存储未初始化。");
|
||
return new LinkedList<EntityPrefab>();
|
||
}
|
||
|
||
if (_dimensionFactionEntities.TryGetValue(dimensionId, out var factionDict))
|
||
{
|
||
if (factionDict.TryGetValue(factionKey, out var entities))
|
||
{
|
||
return entities;
|
||
}
|
||
}
|
||
|
||
return new LinkedList<EntityPrefab>(); // 如果未找到,返回一个空列表
|
||
}
|
||
|
||
/// <summary>
|
||
/// 每帧更新(Tick)方法:处理实体逻辑,清理死亡实体,并将待添加的实体加入管理。
|
||
/// </summary>
|
||
public void Tick()
|
||
{
|
||
// 获取当前管理器中所有活跃维度ID的副本,以安全地迭代和移除。
|
||
var activeDimensionIdsInManager = _dimensionFactionEntities.Keys.ToList();
|
||
|
||
foreach (var dimensionId in activeDimensionIdsInManager)
|
||
{
|
||
// 从 Program 中获取维度对象,验证其是否仍然活跃。
|
||
var dimension = Program.Instance.GetDimension(dimensionId);
|
||
if (dimension == null)
|
||
{
|
||
Debug.LogWarning(
|
||
$"实体管理器:跳过维度 '{dimensionId}' 的Tick处理,因为其维度对象在程序(Program)中已不再活跃(或从未注册)。正在清理相关内部数据。");
|
||
// 如果 Program 不再有这个维度,说明它已经失效或被注销,EntityManage 需要清理自己的相关数据。
|
||
_dimensionFactionEntities.Remove(dimensionId);
|
||
_dimensionLayerCache.Remove(dimensionId);
|
||
// 清理属于该维度的待添加实体。
|
||
RemovePendingAdditionsForDimension(dimensionId);
|
||
continue; // 跳过处理这个不存在的维度
|
||
}
|
||
|
||
// 现在真正处理的是 _dimensionFactionEntities 中该维度ID对应的字典。
|
||
var factionDict = _dimensionFactionEntities[dimensionId];
|
||
|
||
// 迭代 factionDict 的副本,以允许在迭代中修改 factionDict.Keys (即移除派系)。
|
||
// ToList() 创建 KeyValuePair 的副本。
|
||
foreach (var factionEntry in factionDict.ToList())
|
||
{
|
||
var factionKey = factionEntry.Key;
|
||
var entities = factionEntry.Value;
|
||
|
||
var entitiesToRemove = new List<EntityPrefab>();
|
||
// 创建副本以避免在迭代时修改列表。
|
||
var currentEntities = entities.ToList();
|
||
|
||
foreach (var entityPrefab in currentEntities)
|
||
{
|
||
// 检查实体预制体或其内部实体是否已销毁或死亡。
|
||
if (entityPrefab == null || entityPrefab.entity == null || entityPrefab.entity.IsDead)
|
||
{
|
||
entitiesToRemove.Add(entityPrefab);
|
||
}
|
||
else
|
||
{
|
||
ITick itike = entityPrefab.entity;
|
||
itike.Tick();
|
||
}
|
||
}
|
||
|
||
// 删除所有标记为死亡的实体。
|
||
foreach (var entityToRemove in entitiesToRemove)
|
||
{
|
||
entities.Remove(entityToRemove);
|
||
if (entityToRemove != null)
|
||
{
|
||
Destroy(entityToRemove.gameObject);
|
||
}
|
||
}
|
||
|
||
// 如果一个派系下没有任何实体了,清理掉这个派系条目。
|
||
if (entities.Count == 0)
|
||
{
|
||
factionDict.Remove(factionKey);
|
||
}
|
||
}
|
||
|
||
// 如果某个维度在清理后已经没有了任何派系,也一并清理掉这个维度的层级缓存和实体数据。
|
||
if (factionDict.Count == 0)
|
||
{
|
||
_dimensionFactionEntities.Remove(dimensionId);
|
||
_dimensionLayerCache.Remove(dimensionId); // 对应的层级缓存也可以清理。
|
||
}
|
||
}
|
||
|
||
// 处理待添加实体 (现在维度感知)。
|
||
if (_pendingAdditions.Any())
|
||
{
|
||
// 使用 ToList() 创建副本进行迭代,允许在循环内部修改或清空 _pendingAdditions。
|
||
foreach (var pending in _pendingAdditions.ToList())
|
||
{
|
||
var dimensionId = pending.Item1;
|
||
var factionKey = pending.Item2;
|
||
var entityPrefab = pending.Item3;
|
||
|
||
// 再次检查维度是否活跃(在 Program 中注册),防止在添加前维度被 Program 注销。
|
||
if (Program.Instance.GetDimension(dimensionId) == null)
|
||
{
|
||
Debug.LogError(
|
||
$"实体管理器:尝试将实体 '{entityPrefab?.name ?? "null"}' 添加到在程序(Program)中未注册或不活跃的维度 '{dimensionId}'。该实体将被销毁。");
|
||
if (entityPrefab != null && entityPrefab.gameObject != null) Destroy(entityPrefab.gameObject);
|
||
_pendingAdditions.Remove(pending); // 立即从待添加列表中移除已处理的条目。
|
||
continue;
|
||
}
|
||
|
||
// 确保 _dimensionFactionEntities 包含该 dimensionId,否则初始化(以防_OnSceneLoaded时Program中没有,但之后又注册了)。
|
||
if (!_dimensionFactionEntities.ContainsKey(dimensionId))
|
||
{
|
||
// 这情况意味着 Program 中有该维度,但在 EntityManage 内部数据结构中没有。
|
||
// 这可能发生在 Program 动态注册了一个维度,但 EntityManage 尚未在 Tick 中检测到并初始化。
|
||
// 或者 OnSceneLoaded 时 Program 中没有这个维度,但在游戏运行中被注册了。
|
||
_dimensionFactionEntities[dimensionId] = new Dictionary<string, LinkedList<EntityPrefab>>();
|
||
_dimensionLayerCache[dimensionId] = new Dictionary<string, Transform>();
|
||
Debug.LogWarning($"实体管理器:在处理待添加实体时,按需为维度 '{dimensionId}' 初始化内部数据结构。");
|
||
}
|
||
|
||
var factionDict = _dimensionFactionEntities[dimensionId];
|
||
if (!factionDict.ContainsKey(factionKey))
|
||
{
|
||
factionDict[factionKey] = new LinkedList<EntityPrefab>();
|
||
}
|
||
|
||
factionDict[factionKey].AddLast(entityPrefab);
|
||
_pendingAdditions.Remove(pending); // 成功添加后从待添加列表中移除。
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 辅助方法:清理属于特定维度的所有待添加实体。
|
||
/// </summary>
|
||
/// <param name="dimensionId">要清理的维度ID。</param>
|
||
private void RemovePendingAdditionsForDimension(string dimensionId)
|
||
{
|
||
// 创建一个新的列表来存储不属于指定维度的待添加实体。
|
||
var remainingPendingAdditions = new List<Tuple<string, string, EntityPrefab>>();
|
||
foreach (var pending in _pendingAdditions)
|
||
{
|
||
if (pending.Item1 == dimensionId)
|
||
{
|
||
// 如果实体属于要清理的维度,则销毁其GameObject。
|
||
if (pending.Item3 != null && pending.Item3.gameObject != null)
|
||
{
|
||
Destroy(pending.Item3.gameObject);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
remainingPendingAdditions.Add(pending);
|
||
}
|
||
}
|
||
|
||
_pendingAdditions = remainingPendingAdditions;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 根据给定的Def生成实体对象(内部通用方法)。
|
||
/// </summary>
|
||
/// <param name="dimensionId">实体所属的维度ID。</param>
|
||
/// <param name="prefab">要实例化的预制体。</param>
|
||
/// <param name="pos">生成位置。</param>
|
||
/// <param name="def">实体定义对象。</param>
|
||
/// <param name="extraInit">额外的初始化操作(如子弹方向设置)。</param>
|
||
/// <returns>成功时返回EntityPrefab组件,失败时返回null。</returns>
|
||
private EntityPrefab GenerateEntityInternal(
|
||
string dimensionId,
|
||
GameObject prefab,
|
||
Vector3 pos,
|
||
EntityDef def,
|
||
Action<EntityPrefab> extraInit = null)
|
||
{
|
||
// 验证维度是否活跃 (通过 Program)。
|
||
var dimension = Program.Instance.GetDimension(dimensionId);
|
||
if (dimension == null)
|
||
{
|
||
Debug.LogError($"实体管理器:无法生成实体:维度 '{dimensionId}' 在程序(Program)中不活跃或未注册。");
|
||
return null;
|
||
}
|
||
|
||
// 获取或创建实体所属的层级Transform,并确保其在维度根下。
|
||
var parentLayer = EnsureLayerExists(dimensionId, "DefaultEntityLevel");
|
||
if (parentLayer == null)
|
||
{
|
||
Debug.LogError($"实体管理器:无法在维度 '{dimensionId}' 中获取或创建实体的父层。");
|
||
return null;
|
||
}
|
||
|
||
GameObject instantiatedEntity = null;
|
||
try
|
||
{
|
||
// 实例化实体,并将其父级设置为维度下的层级Transform。
|
||
instantiatedEntity = Instantiate(prefab, pos, Quaternion.identity, parentLayer);
|
||
|
||
var entityComponent = instantiatedEntity.GetComponent<EntityPrefab>();
|
||
if (!entityComponent)
|
||
{
|
||
throw new InvalidOperationException(
|
||
$"在 '{instantiatedEntity.name}' 上缺少 EntityPrefab 组件,无法完成实体初始化。");
|
||
}
|
||
entityComponent.entity.currentDimensionId = dimensionId;
|
||
entityComponent.Init(def);
|
||
extraInit?.Invoke(entityComponent);
|
||
|
||
var factionKey = def.attributes.defName ?? "default";
|
||
_pendingAdditions.Add(Tuple.Create(dimensionId, factionKey, entityComponent));
|
||
return entityComponent;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
if (instantiatedEntity) Destroy(instantiatedEntity);
|
||
Debug.LogError($"实体管理器:在维度 '{dimensionId}' 中生成实体失败:{ex.Message}\n{ex.StackTrace}");
|
||
return null;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 动态创建层(如果层不存在),现在是维度感知的。
|
||
/// 每个维度有自己的层级结构,根在 Dimension.DimensionRoot 下。
|
||
/// </summary>
|
||
/// <param name="dimensionId">维度的唯一标识符。</param>
|
||
/// <param name="layerName">要确保存在的层级名称。</param>
|
||
/// <returns>层级Transform,如果维度不存在或其根Transform为空则返回null。</returns>
|
||
private Transform EnsureLayerExists(string dimensionId, string layerName)
|
||
{
|
||
// 尝试从维度层级缓存中获取。如果没有该维度的缓存,先尝试初始化。
|
||
if (!_dimensionLayerCache.TryGetValue(dimensionId, out var layerCacheForDimension))
|
||
{
|
||
// 如果 Program 中有这个维度,但在 EntityManage 内部没有初始化,则尝试进行初始化。
|
||
// 这种情况可能发生在 OnSceneLoaded 之后,Program 动态注册了新的维度。
|
||
if (Program.Instance.GetDimension(dimensionId) != null)
|
||
{
|
||
_dimensionLayerCache[dimensionId] = new Dictionary<string, Transform>();
|
||
layerCacheForDimension = _dimensionLayerCache[dimensionId]; // 更新引用。
|
||
}
|
||
else
|
||
{
|
||
return null;
|
||
}
|
||
}
|
||
|
||
if (layerCacheForDimension.TryGetValue(layerName, out var layerTransform))
|
||
{
|
||
return layerTransform;
|
||
}
|
||
|
||
// 如果缓存中没有,尝试在维度根下查找。
|
||
var dimension = Program.Instance.GetDimension(dimensionId);
|
||
if (dimension == null || dimension.DimensionRoot == null)
|
||
{
|
||
Debug.LogError($"实体管理器:维度 '{dimensionId}' (来自 Program) 或其根 Transform 为空。无法创建层级 '{layerName}'。");
|
||
return null;
|
||
}
|
||
|
||
layerTransform = dimension.DimensionRoot.Find(layerName);
|
||
|
||
if (!layerTransform)
|
||
{
|
||
// 如果层不存在,动态创建并将其父级设置为维度的根Transform。
|
||
var layerObject = new GameObject(layerName);
|
||
layerTransform = layerObject.transform;
|
||
layerTransform.SetParent(dimension.DimensionRoot);
|
||
// 移除了此处的 Debug.Log,因为它属于非必要的普通日志。
|
||
}
|
||
|
||
// 将新创建的层加入缓存。
|
||
layerCacheForDimension[layerName] = layerTransform;
|
||
return layerTransform;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 在指定维度和位置生成一个通用实体。
|
||
/// </summary>
|
||
/// <param name="dimensionId">实体所属的维度ID。</param>
|
||
/// <param name="entityDef">实体定义对象。</param>
|
||
/// <param name="pos">生成位置。</param>
|
||
public void GenerateEntity(string dimensionId, EntityDef entityDef, Vector3 pos)
|
||
{
|
||
if (!characterPrefab)
|
||
{
|
||
Debug.LogError("实体管理器:characterPrefab 为空!请分配一个有效的预制体。");
|
||
GenerateDefaultEntity(dimensionId, pos);
|
||
return;
|
||
}
|
||
|
||
if (entityDef == null)
|
||
{
|
||
Debug.LogError("实体管理器:EntityDef 为空!无法生成实体。");
|
||
GenerateDefaultEntity(dimensionId, pos);
|
||
return;
|
||
}
|
||
|
||
var result = GenerateEntityInternal(
|
||
dimensionId,
|
||
characterPrefab.gameObject,
|
||
pos,
|
||
entityDef
|
||
);
|
||
if (result == null) GenerateDefaultEntity(dimensionId, pos);
|
||
}
|
||
public void GenerateMonsterEntity(string dimensionId, MonsterDef monsterDef, Vector3 pos)
|
||
{
|
||
if (!monsterPrefab)
|
||
{
|
||
Debug.LogError("实体管理器:monsterPrefab 为空!请分配一个有效的预制体。");
|
||
GenerateDefaultEntity(dimensionId, pos);
|
||
return;
|
||
}
|
||
|
||
if (monsterDef == null)
|
||
{
|
||
Debug.LogError("实体管理器:monsterDef 为空!无法生成实体。");
|
||
GenerateDefaultEntity(dimensionId, pos);
|
||
return;
|
||
}
|
||
|
||
var result = GenerateEntityInternal(
|
||
dimensionId,
|
||
monsterPrefab.gameObject,
|
||
pos,
|
||
monsterDef
|
||
);
|
||
if (result == null) GenerateDefaultEntity(dimensionId, pos);
|
||
}
|
||
/// <summary>
|
||
/// 在指定维度和网格位置生成一个建筑实体。
|
||
/// </summary>
|
||
/// <param name="dimensionId">实体所属的维度ID。</param>
|
||
/// <param name="buildingDef">建筑定义对象。</param>
|
||
/// <param name="pos">网格位置。</param>
|
||
public void GenerateBuildingEntity(string dimensionId, BuildingDef buildingDef, Vector3Int pos)
|
||
{
|
||
if (!buildingPrefab)
|
||
{
|
||
Debug.LogError("实体管理器:buildingPrefab 为空!请分配一个有效的预制体。");
|
||
GenerateDefaultEntity(dimensionId, pos);
|
||
return;
|
||
}
|
||
|
||
if (buildingDef == null)
|
||
{
|
||
Debug.LogError("实体管理器:BuildingDef 为空!无法生成建筑。");
|
||
GenerateDefaultEntity(dimensionId, pos);
|
||
return;
|
||
}
|
||
|
||
var worldPos = new Vector3(pos.x, pos.y, pos.z);
|
||
var result = GenerateEntityInternal(
|
||
dimensionId,
|
||
buildingPrefab.gameObject,
|
||
worldPos,
|
||
buildingDef
|
||
);
|
||
if (result == null) GenerateDefaultEntity(dimensionId, worldPos);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 在指定维度、位置和方向上生成一个子弹实体。
|
||
/// </summary>
|
||
/// <param name="dimensionId">实体所属的维度ID。</param>
|
||
/// <param name="bulletDef">子弹定义对象。</param>
|
||
/// <param name="pos">生成位置。</param>
|
||
/// <param name="dir">子弹的初始方向。</param>
|
||
/// <param name="source">发射子弹的源实体。</param>
|
||
public void GenerateBulletEntity(string dimensionId, BulletDef bulletDef, Vector3 pos, Vector3 dir,
|
||
Entity.Entity source = null)
|
||
{
|
||
if (!bulletPrefab)
|
||
{
|
||
Debug.LogError("实体管理器:bulletPrefab 为空!请分配一个有效的预制体。");
|
||
GenerateDefaultEntity(dimensionId, pos);
|
||
return;
|
||
}
|
||
|
||
if (bulletDef == null)
|
||
{
|
||
Debug.LogError("实体管理器:BulletDef 为空!无法生成子弹。");
|
||
GenerateDefaultEntity(dimensionId, pos);
|
||
return;
|
||
}
|
||
|
||
var result = GenerateEntityInternal(
|
||
dimensionId,
|
||
bulletPrefab.gameObject,
|
||
pos,
|
||
bulletDef,
|
||
entityComponent =>
|
||
{
|
||
if (entityComponent != null && entityComponent.entity != null)
|
||
{
|
||
entityComponent.entity.SetTarget(pos + dir);
|
||
}
|
||
else
|
||
{
|
||
Debug.LogWarning($"实体管理器:在维度 '{dimensionId}' 的额外初始化期间,子弹实体组件或其内部实体为空。");
|
||
}
|
||
}
|
||
);
|
||
|
||
if (result != null && result.entity is Bullet bullet)
|
||
{
|
||
bullet.bulletSource = source;
|
||
if (source != null) bullet.affiliation = source.affiliation;
|
||
}
|
||
|
||
if (result == null) GenerateDefaultEntity(dimensionId, pos);
|
||
}
|
||
public void GeneratePickupEntity(string dimensionId, ItemDef itemDef, Vector3 pos)
|
||
{
|
||
if (!pickupPrefab)
|
||
{
|
||
Debug.LogError("实体管理器:pickupPrefab 为空!请分配一个有效的预制体。");
|
||
GenerateDefaultEntity(dimensionId, pos);
|
||
return;
|
||
}
|
||
|
||
if (itemDef == null)
|
||
{
|
||
Debug.LogError("实体管理器:itemDef 为空!无法生成子弹。");
|
||
GenerateDefaultEntity(dimensionId, pos);
|
||
return;
|
||
}
|
||
var dimension = Program.Instance.GetDimension(dimensionId);
|
||
if (dimension == null)
|
||
{
|
||
Debug.LogError($"实体管理器:无法生成实体:维度 '{dimensionId}' 在程序(Program)中不活跃或未注册。");
|
||
return;
|
||
}
|
||
// 获取或创建实体所属的层级Transform,并确保其在维度根下。
|
||
var parentLayer = EnsureLayerExists(dimensionId, "DefaultEntityLevel");
|
||
if (parentLayer == null)
|
||
{
|
||
Debug.LogError($"实体管理器:无法在维度 '{dimensionId}' 中获取或创建实体的父层。");
|
||
return;
|
||
}
|
||
|
||
var result = Instantiate(pickupPrefab, pos, Quaternion.identity);
|
||
var pickup = result.GetComponent<Pickup>();
|
||
result.transform.SetParent(parentLayer);
|
||
pickup.Init(itemDef);
|
||
if (result == null) GenerateDefaultEntity(dimensionId, pos);
|
||
_pendingAdditions.Add(Tuple.Create(dimensionId, "default", result));
|
||
|
||
}
|
||
/// <summary>
|
||
/// 在指定维度和位置生成一个默认实体(通常作为回退选项)。
|
||
/// </summary>
|
||
/// <param name="dimensionId">实体所属的维度ID。</param>
|
||
/// <param name="pos">生成位置。</param>
|
||
public void GenerateDefaultEntity(string dimensionId, Vector3 pos)
|
||
{
|
||
if (Program.Instance.GetDimension(dimensionId) == null)
|
||
{
|
||
Debug.LogError($"实体管理器:无法生成默认实体:维度 '{dimensionId}' 在程序(Program)中不活跃或未注册。");
|
||
return;
|
||
}
|
||
|
||
var parentLayer = EnsureLayerExists(dimensionId, "DefaultEntityLevel");
|
||
if (parentLayer == null)
|
||
{
|
||
Debug.LogError($"实体管理器:无法在维度 '{dimensionId}' 中获取默认实体的父 Transform。");
|
||
return;
|
||
}
|
||
|
||
if (defaultEntityPrefab == null || defaultEntityPrefab.gameObject == null)
|
||
{
|
||
Debug.LogError($"实体管理器:defaultEntityPrefab 为空或没有GameObject。无法为维度 '{dimensionId}' 生成默认实体。");
|
||
return;
|
||
}
|
||
|
||
var entity = Instantiate(defaultEntityPrefab, pos, Quaternion.identity, parentLayer);
|
||
var entityComponent = entity.GetComponent<EntityPrefab>();
|
||
|
||
if (entityComponent == null)
|
||
{
|
||
Debug.LogError($"实体管理器:默认实体预制体 '{defaultEntityPrefab.name}' 缺少 EntityPrefab 组件。正在销毁已实例化的对象。");
|
||
Destroy(entity.gameObject);
|
||
return;
|
||
}
|
||
|
||
const string factionKey = "default";
|
||
_pendingAdditions.Add(Tuple.Create(dimensionId, factionKey, entityComponent));
|
||
entityComponent.DefaultInit();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 在指定维度中,查找与源实体具有特定关系的最近实体。
|
||
/// </summary>
|
||
/// <param name="dimensionId">要搜索的维度ID。</param>
|
||
/// <param name="sourceEntityPrefab">作为参照的源实体预制体。</param>
|
||
/// <param name="targetRelationship">目标实体与源实体之间的期望关系(例如,Friendly, Hostile)。</param>
|
||
/// <returns>找到的最近实体预制体,如果没有找到符合条件的实体则返回 null。</returns>
|
||
public EntityPrefab FindNearestEntityByRelation(string dimensionId, EntityPrefab sourceEntityPrefab,
|
||
Relation targetRelationship)
|
||
{
|
||
// 参数校验:确保输入参数有效,避免空引用异常。
|
||
if (sourceEntityPrefab == null || sourceEntityPrefab.entity == null)
|
||
{
|
||
Debug.LogWarning("实体管理器:FindNearestEntityByRelation 方法中,源实体预制体或其内部实体为空。无法执行搜索。");
|
||
return null;
|
||
}
|
||
|
||
// 维度数据存在性检查:验证目标维度是否在实体管理器的内部数据结构中被初始化和管理。
|
||
if (!_dimensionFactionEntities.TryGetValue(dimensionId, out var factionDict))
|
||
{
|
||
Debug.LogWarning($"实体管理器:FindNearestEntityByRelation 方法中,维度 '{dimensionId}' 未被初始化或未在内部管理实体。");
|
||
return null;
|
||
}
|
||
|
||
// 初始化追踪变量:设置初始值,用于在遍历过程中追踪最近的实体及其距离。
|
||
// 使用平方距离 (SqrMagnitude) 可以避免在每次距离计算时进行昂贵的开方运算,从而提高性能。
|
||
EntityPrefab nearestTarget = null;
|
||
var minDistanceSqr = float.MaxValue;
|
||
var sourcePos = sourceEntityPrefab.transform.position;
|
||
|
||
// 关系管理器实例检查:确保 AffiliationManager 可用,它是判断实体关系的核心组件。
|
||
var affiliationManager = AffiliationManager.Instance;
|
||
if (affiliationManager == null)
|
||
{
|
||
Debug.LogError("实体管理器:FindNearestEntityByRelation 方法中,AffiliationManager 实例为空。无法确定实体关系。");
|
||
return null;
|
||
}
|
||
|
||
// 遍历所有派系和实体:_dimensionFactionEntities 按维度和派系组织,需要遍历所有派系才能找到维度内的所有实体。
|
||
foreach (var factionEntities in factionDict.Values) // factionDict.Values 是 LinkedList<EntityPrefab> 的集合
|
||
{
|
||
foreach (var currentEntityPrefab in factionEntities)
|
||
{
|
||
// 实体有效性及排除源实体自身:
|
||
// 1. 排除无效或已死亡的实体,确保只处理活跃的实体。
|
||
// 2. 在寻找“最近”实体时,通常指的是 *除了自身以外* 的实体。
|
||
// 如果需要包含自身(例如,当 targetRelationship 是 AffiliationManager.Relation.Self 时),
|
||
// 可以根据具体需求调整此逻辑,但默认行为是排除自身。
|
||
if (currentEntityPrefab == null || currentEntityPrefab.entity == null ||
|
||
currentEntityPrefab.entity.IsDead || currentEntityPrefab == sourceEntityPrefab)
|
||
{
|
||
continue;
|
||
}
|
||
|
||
// 关系判断:使用 AffiliationManager 提供的接口判断源实体与当前遍历实体之间的关系。
|
||
var currentRelation =
|
||
affiliationManager.GetRelation(sourceEntityPrefab.entity, currentEntityPrefab.entity);
|
||
if (currentRelation == targetRelationship)
|
||
{
|
||
// 距离计算与最近实体更新:
|
||
// 1. 计算与源实体的距离(使用平方距离优化)。
|
||
// 2. 如果当前实体更近,则更新 nearestTarget 和 minDistanceSqr。
|
||
var distanceSqr = Vector3.SqrMagnitude(currentEntityPrefab.transform.position - sourcePos);
|
||
if (distanceSqr < minDistanceSqr)
|
||
{
|
||
minDistanceSqr = distanceSqr;
|
||
nearestTarget = currentEntityPrefab;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
return nearestTarget; // 返回找到的最近实体
|
||
}
|
||
|
||
/// <summary>
|
||
/// 在指定维度中,判断是否存在任何与源实体敌对的活跃实体。
|
||
/// </summary>
|
||
/// <param name="dimensionId">要搜索的维度ID。</param>
|
||
/// <param name="sourceEntityPrefab">作为参照的源实体预制体。</param>
|
||
/// <returns>如果存在敌对实体则返回 true,否则返回 false。</returns>
|
||
public bool ExistsHostile(string dimensionId, EntityPrefab sourceEntityPrefab)
|
||
{
|
||
// 参数校验:确保输入参数有效,避免空引用异常。
|
||
if (sourceEntityPrefab == null || sourceEntityPrefab.entity == null)
|
||
{
|
||
Debug.LogWarning("实体管理器:ExistsHostile 方法中,源实体预制体或其内部实体为空。无法执行搜索。");
|
||
return false;
|
||
}
|
||
|
||
// 维度数据存在性检查:验证目标维度是否在实体管理器的内部数据结构中被初始化和管理。
|
||
if (!_dimensionFactionEntities.TryGetValue(dimensionId, out var factionDict))
|
||
{
|
||
Debug.LogWarning($"实体管理器:ExistsHostile 方法中,维度 '{dimensionId}' 未被初始化或未在内部管理实体。");
|
||
return false;
|
||
}
|
||
|
||
// 关系管理器实例检查:确保 AffiliationManager 可用,它是判断实体关系的核心组件。
|
||
var affiliationManager = AffiliationManager.Instance;
|
||
if (affiliationManager == null)
|
||
{
|
||
Debug.LogError("实体管理器:ExistsHostile 方法中,AffiliationManager 实例为空。无法确定实体关系。");
|
||
return false;
|
||
}
|
||
|
||
// 遍历所有实体:遍历维度内的所有派系,再遍历每个派系下的所有实体。
|
||
foreach (var factionEntities in factionDict.Values)
|
||
{
|
||
foreach (var currentEntityPrefab in factionEntities)
|
||
{
|
||
// 实体有效性及排除源实体自身:
|
||
// 1. 排除无效或已死亡的实体。
|
||
// 2. 排除源实体自身,避免自身判断为敌对。
|
||
if (currentEntityPrefab == null || currentEntityPrefab.entity == null ||
|
||
currentEntityPrefab.entity.IsDead || currentEntityPrefab == sourceEntityPrefab)
|
||
{
|
||
continue;
|
||
}
|
||
|
||
// 关系判断:判断当前实体与源实体是否为“敌对”关系。
|
||
if (affiliationManager.GetRelation(sourceEntityPrefab.entity, currentEntityPrefab.entity) ==
|
||
Relation.Hostile)
|
||
{
|
||
return true; // 找到第一个敌对实体即返回 true,提高效率。
|
||
}
|
||
}
|
||
}
|
||
|
||
return false; // 遍历完所有实体都未找到敌对实体。
|
||
}
|
||
|
||
/// <summary>
|
||
/// 在指定维度中,判断源实体视野范围内是否存在任何与源实体敌对的活跃实体。
|
||
/// </summary>
|
||
/// <param name="dimensionId">要搜索的维度ID。</param>
|
||
/// <param name="sourceEntityPrefab">作为参照的源实体预制体。</param>
|
||
/// <param name="sightRange">视野范围,实体间的距离小于等于此值视为在视野内。</param>
|
||
/// <returns>如果视野范围内存在敌对实体则返回 true,否则返回 false。</returns>
|
||
public bool ExistsHostileInSightRange(string dimensionId, EntityPrefab sourceEntityPrefab, float sightRange)
|
||
{
|
||
// 参数校验:确保输入参数有效,避免空引用异常及不合理的范围值。
|
||
if (sourceEntityPrefab == null || sourceEntityPrefab.entity == null)
|
||
{
|
||
Debug.LogWarning("实体管理器:ExistsHostileInSightRange 方法中,源实体预制体或其内部实体为空。无法执行搜索。");
|
||
return false;
|
||
}
|
||
|
||
if (sightRange <= 0)
|
||
{
|
||
Debug.LogWarning($"实体管理器:ExistsHostileInSightRange 方法中,视野范围必须为正值。接收到的值为: {sightRange}。");
|
||
return false;
|
||
}
|
||
|
||
// 维度数据存在性检查:验证目标维度是否在实体管理器的内部数据结构中被初始化和管理。
|
||
if (!_dimensionFactionEntities.TryGetValue(dimensionId, out var factionDict))
|
||
{
|
||
Debug.LogWarning($"实体管理器:ExistsHostileInSightRange 方法中,维度 '{dimensionId}' 未被初始化或未在内部管理实体。");
|
||
return false;
|
||
}
|
||
|
||
// 关系管理器实例检查:确保 AffiliationManager 可用,它是判断实体关系的核心组件。
|
||
var affiliationManager = AffiliationManager.Instance;
|
||
if (affiliationManager == null)
|
||
{
|
||
Debug.LogError("实体管理器:ExistsHostileInSightRange 方法中,AffiliationManager 实例为空。无法确定实体关系。");
|
||
return false;
|
||
}
|
||
|
||
// 预计算平方距离:避免在循环内部重复计算平方根,提高性能。
|
||
var sightRangeSqr = sightRange * sightRange;
|
||
var sourcePos = sourceEntityPrefab.transform.position;
|
||
// 遍历所有实体:遍历维度内的所有派系,再遍历每个派系下的所有实体。
|
||
foreach (var factionEntities in factionDict.Values)
|
||
{
|
||
foreach (var currentEntityPrefab in factionEntities)
|
||
{
|
||
// 实体有效性及排除源实体自身:
|
||
// 1. 排除无效或已死亡的实体。
|
||
// 2. 排除源实体自身。
|
||
if (currentEntityPrefab == null || currentEntityPrefab.entity == null ||
|
||
currentEntityPrefab.entity.IsDead || currentEntityPrefab == sourceEntityPrefab)
|
||
{
|
||
continue;
|
||
}
|
||
|
||
// 关系判断:判断当前实体与源实体是否为“敌对”关系。
|
||
if (affiliationManager.GetRelation(sourceEntityPrefab.entity, currentEntityPrefab.entity) ==
|
||
Relation.Hostile)
|
||
{
|
||
// 距离判断:如果关系敌对,进一步判断其是否在视野范围内。
|
||
var distanceSqr = Vector3.SqrMagnitude(currentEntityPrefab.transform.position - sourcePos);
|
||
if (distanceSqr <= sightRangeSqr)
|
||
{
|
||
return true; // 找到第一个视野范围内的敌对实体即返回 true。
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
return false; // 遍历完所有实体都未找到视野范围内的敌对实体。
|
||
}
|
||
|
||
/// <summary>
|
||
/// 单例管理器启动时调用,订阅场景加载事件。
|
||
/// </summary>
|
||
protected override void OnStart()
|
||
{
|
||
SceneManager.sceneLoaded += OnSceneLoaded;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 单例管理器销毁时调用,取消订阅场景加载事件。
|
||
/// </summary>
|
||
private void OnDestroy()
|
||
{
|
||
SceneManager.sceneLoaded -= OnSceneLoaded;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 场景加载完成时的回调。
|
||
/// 清理旧场景的实体数据,并基于 Program 中已注册的维度重新初始化内部结构。
|
||
/// </summary>
|
||
private void OnSceneLoaded(Scene scene, LoadSceneMode mode)
|
||
{
|
||
_dimensionFactionEntities.Clear(); // 清理所有旧场景的实体数据。
|
||
_dimensionLayerCache.Clear(); // 清理所有旧场景的层级缓存。
|
||
|
||
// 场景加载时清理待添加列表中的所有实体,因为它们可能属于旧场景或未在任何维度中被处理。
|
||
foreach (var pending in _pendingAdditions)
|
||
{
|
||
if (pending.Item3 != null && pending.Item3.gameObject != null)
|
||
{
|
||
Destroy(pending.Item3.gameObject);
|
||
}
|
||
}
|
||
|
||
_pendingAdditions.Clear(); // 清理待添加实体列表。
|
||
|
||
// 在新场景加载完成后,遍历 Program 中已注册的所有维度,并为每个维度初始化 EntityManage 的内部数据结构。
|
||
// 此时 Program.Awake() 应该已经完成,所有 Dimension 组件的 Awake() 也已执行,Program.RegisteredDimensions 应该包含了当前场景的所有维度。
|
||
foreach (var registeredDimensionEntry in Program.Instance.RegisteredDimensions)
|
||
{
|
||
var dimensionId = registeredDimensionEntry.Key;
|
||
var dimension = registeredDimensionEntry.Value;
|
||
|
||
if (dimension != null) // 确保维度实例本身不是null。
|
||
{
|
||
// 为该维度ID初始化实体存储结构。
|
||
_dimensionFactionEntities[dimensionId] = new Dictionary<string, LinkedList<EntityPrefab>>();
|
||
// 为该维度ID初始化层级缓存。
|
||
_dimensionLayerCache[dimensionId] = new Dictionary<string, Transform>();
|
||
}
|
||
else
|
||
{
|
||
Debug.LogWarning(
|
||
$"实体管理器:在场景加载初始化期间,Program 为ID '{dimensionId}' 注册了一个空的 Dimension 对象。这可能表明维度注册存在问题。");
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// MonoBehaviour 的 Start 方法,用于初始化默认实体预制体。
|
||
/// </summary>
|
||
private void Start()
|
||
{
|
||
if (defaultEntityPrefab == null)
|
||
{
|
||
var pre = Resources.Load<GameObject>("Default/DefaultEntity");
|
||
if (pre != null)
|
||
{
|
||
defaultEntityPrefab = pre.GetComponent<EntityPrefab>();
|
||
if (defaultEntityPrefab == null)
|
||
{
|
||
Debug.LogError(
|
||
$"实体管理器:已加载 DefaultEntity 预制体,但它缺少来自 Resources/Default/DefaultEntity 的 EntityPrefab 组件。请确保它包含该组件。");
|
||
}
|
||
}
|
||
else
|
||
{
|
||
Debug.LogError(
|
||
"实体管理器:无法从 Resources/Default/DefaultEntity 加载 DefaultEntity 预制体。请确保它存在于 'Assets/Resources/Default/DefaultEntity.prefab'。");
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|