(client) feat:添加临时动画组件,添加逃跑逻辑
This commit is contained in:
@ -30,7 +30,7 @@ namespace Managers
|
||||
/// <summary>
|
||||
/// 获取当前启动步骤的描述。
|
||||
/// </summary>
|
||||
public string StepDescription { get; private set; } = "音频管理器正在准备中...";
|
||||
public string StepDescription { get;} = "音频管理器正在加载中";
|
||||
|
||||
/// <summary>
|
||||
/// 初始化音频管理器,加载默认音频剪辑并处理所有 AudioDef 定义。
|
||||
@ -178,7 +178,6 @@ namespace Managers
|
||||
// 可能需要 Resources.UnloadAsset(clip) 或 AssetBundle.UnloadAllAssets(true)。
|
||||
// 但对于单独的 AudioClip 引用,通常在不再被引用时Unity会自动清理。
|
||||
audioClips.Clear();
|
||||
StepDescription = "音频管理器数据已清理。"; // 更新状态
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -132,7 +132,7 @@ namespace Managers
|
||||
foreach (var entityPrefab in currentEntities)
|
||||
{
|
||||
// 检查实体预制体或其内部实体是否已销毁或死亡。
|
||||
if (entityPrefab == null || entityPrefab.entity == null || entityPrefab.entity.IsDead)
|
||||
if (!entityPrefab || !entityPrefab.entity || entityPrefab.entity.IsDead)
|
||||
{
|
||||
entitiesToRemove.Add(entityPrefab);
|
||||
}
|
||||
@ -196,7 +196,6 @@ namespace Managers
|
||||
// 或者 OnSceneLoaded 时 Program 中没有这个维度,但在游戏运行中被注册了。
|
||||
_dimensionFactionEntities[dimensionId] = new Dictionary<string, LinkedList<EntityPrefab>>();
|
||||
_dimensionLayerCache[dimensionId] = new Dictionary<string, Transform>();
|
||||
Debug.LogWarning($"实体管理器:在处理待添加实体时,按需为维度 '{dimensionId}' 初始化内部数据结构。");
|
||||
}
|
||||
|
||||
var factionDict = _dimensionFactionEntities[dimensionId];
|
||||
@ -470,7 +469,7 @@ namespace Managers
|
||||
bulletDef,
|
||||
entityComponent =>
|
||||
{
|
||||
if (entityComponent != null && entityComponent.entity != null)
|
||||
if (entityComponent && entityComponent.entity)
|
||||
{
|
||||
entityComponent.entity.SetTarget(pos + dir);
|
||||
}
|
||||
@ -481,14 +480,15 @@ namespace Managers
|
||||
}
|
||||
);
|
||||
|
||||
if (result != null && result.entity is Bullet bullet)
|
||||
if (result && result.entity is Bullet bullet)
|
||||
{
|
||||
bullet.bulletSource = source;
|
||||
if (source != null) bullet.affiliation = source.affiliation;
|
||||
if (source) bullet.affiliation = source.affiliation;
|
||||
}
|
||||
|
||||
if (result == null) GenerateDefaultEntity(dimensionId, pos);
|
||||
if (!result) GenerateDefaultEntity(dimensionId, pos);
|
||||
}
|
||||
|
||||
public void GeneratePickupEntity(string dimensionId, ItemDef itemDef, Vector3 pos)
|
||||
{
|
||||
if (!pickupPrefab)
|
||||
@ -500,32 +500,55 @@ namespace Managers
|
||||
|
||||
if (itemDef == null)
|
||||
{
|
||||
Debug.LogError("实体管理器:itemDef 为空!无法生成子弹。");
|
||||
// --- 逻辑修改点 1:更正错误信息 ---
|
||||
Debug.LogError("实体管理器:itemDef 为空!无法生成拾取物实体。");
|
||||
GenerateDefaultEntity(dimensionId, pos);
|
||||
return;
|
||||
}
|
||||
|
||||
var dimension = Program.Instance.GetDimension(dimensionId);
|
||||
if (dimension == null)
|
||||
if (!dimension) // 更安全的检查
|
||||
{
|
||||
Debug.LogError($"实体管理器:无法生成实体:维度 '{dimensionId}' 在程序(Program)中不活跃或未注册。");
|
||||
return;
|
||||
}
|
||||
// 获取或创建实体所属的层级Transform,并确保其在维度根下。
|
||||
|
||||
var parentLayer = EnsureLayerExists(dimensionId, "DefaultEntityLevel");
|
||||
if (parentLayer == null)
|
||||
if (!parentLayer)
|
||||
{
|
||||
Debug.LogError($"实体管理器:无法在维度 '{dimensionId}' 中获取或创建实体的父层。");
|
||||
return;
|
||||
}
|
||||
|
||||
var result = Instantiate(pickupPrefab, pos, Quaternion.identity);
|
||||
var pickup = result.GetComponent<Pickup>();
|
||||
var result = GameObject.Instantiate(pickupPrefab.gameObject, pos, Quaternion.identity);
|
||||
if (!result)
|
||||
{
|
||||
Debug.LogError($"实体管理器:无法实例化预制体 '{pickupPrefab.name}'。");
|
||||
GenerateDefaultEntity(dimensionId, pos);
|
||||
return;
|
||||
}
|
||||
|
||||
var entityPrefab = result.GetComponent<EntityPrefab>();
|
||||
if (!entityPrefab)
|
||||
{
|
||||
Debug.LogError($"实体管理器:实例化出的拾取物实体 '{pickupPrefab.name}' 缺少 'EntityPrefab' 组件!无法完成初始化。已销毁实例。");
|
||||
GameObject.Destroy(result); // 清理未正确初始化的实例
|
||||
return;
|
||||
}
|
||||
|
||||
var pickup = entityPrefab.entity as Pickup; // 使用 as 运算符进行安全类型转换
|
||||
if (!pickup)
|
||||
{
|
||||
Debug.LogError($"实体管理器:实例化出的实体 '{entityPrefab.name}' 的核心实体对象无法转换为 'Pickup' 类型或为空。无法完成初始化。已销毁实例。");
|
||||
GameObject.Destroy(result); // 清理未正确初始化的实例
|
||||
return;
|
||||
}
|
||||
|
||||
result.transform.SetParent(parentLayer);
|
||||
pickup.Init(itemDef);
|
||||
if (result == null) GenerateDefaultEntity(dimensionId, pos);
|
||||
_pendingAdditions.Add(Tuple.Create(dimensionId, "default", result));
|
||||
|
||||
_pendingAdditions.Add(Tuple.Create(dimensionId, "default", entityPrefab));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 在指定维度和位置生成一个默认实体(通常作为回退选项)。
|
||||
/// </summary>
|
||||
@ -533,7 +556,7 @@ namespace Managers
|
||||
/// <param name="pos">生成位置。</param>
|
||||
public void GenerateDefaultEntity(string dimensionId, Vector3 pos)
|
||||
{
|
||||
if (Program.Instance.GetDimension(dimensionId) == null)
|
||||
if (!Program.Instance.GetDimension(dimensionId))
|
||||
{
|
||||
Debug.LogError($"实体管理器:无法生成默认实体:维度 '{dimensionId}' 在程序(Program)中不活跃或未注册。");
|
||||
return;
|
||||
@ -615,7 +638,7 @@ namespace Managers
|
||||
// 2. 在寻找“最近”实体时,通常指的是 *除了自身以外* 的实体。
|
||||
// 如果需要包含自身(例如,当 targetRelationship 是 AffiliationManager.Relation.Self 时),
|
||||
// 可以根据具体需求调整此逻辑,但默认行为是排除自身。
|
||||
if (currentEntityPrefab == null || currentEntityPrefab.entity == null ||
|
||||
if (!currentEntityPrefab || !currentEntityPrefab.entity ||
|
||||
currentEntityPrefab.entity.IsDead || currentEntityPrefab == sourceEntityPrefab)
|
||||
{
|
||||
continue;
|
||||
@ -644,19 +667,36 @@ namespace Managers
|
||||
|
||||
/// <summary>
|
||||
/// 在指定维度中,判断是否存在任何与源实体敌对的活跃实体。
|
||||
/// 此版本修正了将掉落物或子弹识别为敌对派系的问题,现在只考虑可转换为 CombatantEntity 的实体。
|
||||
/// </summary>
|
||||
/// <param name="dimensionId">要搜索的维度ID。</param>
|
||||
/// <param name="sourceEntityPrefab">作为参照的源实体预制体。</param>
|
||||
/// <returns>如果存在敌对实体则返回 true,否则返回 false。</returns>
|
||||
/// <returns>如果存在敌对的 CombatantEntity 则返回 true,否则返回 false。</returns>
|
||||
public bool ExistsHostile(string dimensionId, EntityPrefab sourceEntityPrefab)
|
||||
{
|
||||
// 参数校验:确保输入参数有效,避免空引用异常。
|
||||
if (sourceEntityPrefab == null || sourceEntityPrefab.entity == null)
|
||||
// 使用 Unity 的 null 检查运算符 '!'
|
||||
if (!sourceEntityPrefab || !sourceEntityPrefab.entity)
|
||||
{
|
||||
Debug.LogWarning("实体管理器:ExistsHostile 方法中,源实体预制体或其内部实体为空。无法执行搜索。");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 新增校验:确保源实体自身是一个 CombatantEntity。
|
||||
// 因为我们现在只关注 CombatantEntity 之间的敌对关系。
|
||||
var sourceCombatant = sourceEntityPrefab.entity as CombatantEntity;
|
||||
if (!sourceCombatant) // 使用 Unity 的 null 检查运算符 '!'
|
||||
{
|
||||
Debug.LogWarning(
|
||||
$"实体管理器:ExistsHostile 方法中,源实体 '{sourceEntityPrefab.name}' 无法转换为 CombatantEntity 类型。无法有效判断是否存在敌对的活跃实体。");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sourceCombatant.IsDead)
|
||||
{
|
||||
return false; // 源 CombatantEntity 已经失效,它无法判断敌对实体。
|
||||
}
|
||||
|
||||
// 维度数据存在性检查:验证目标维度是否在实体管理器的内部数据结构中被初始化和管理。
|
||||
if (!_dimensionFactionEntities.TryGetValue(dimensionId, out var factionDict))
|
||||
{
|
||||
@ -664,38 +704,47 @@ namespace Managers
|
||||
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)
|
||||
// 1. 排除无效的实体预制体或内部实体。
|
||||
// 2. 排除已失效的 CombatantEntity。
|
||||
// 3. 排除源实体自身,避免自身判断为敌对。
|
||||
if (!currentEntityPrefab || !currentEntityPrefab.entity ||
|
||||
currentEntityPrefab == sourceEntityPrefab) // 源实体自身已被转换为 sourceCombatant 并检查,这里只检查 prefab 引用是否相同
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// 关系判断:判断当前实体与源实体是否为“敌对”关系。
|
||||
if (affiliationManager.GetRelation(sourceEntityPrefab.entity, currentEntityPrefab.entity) ==
|
||||
Relation.Hostile)
|
||||
// 【核心改动】新增类型检查:只有能转换为 CombatantEntity 的实体才会被进一步判断。
|
||||
// 这样可以排除掉落物、子弹等非 CombatantEntity 的实体。
|
||||
var currentCombatant = currentEntityPrefab.entity as CombatantEntity;
|
||||
if (!currentCombatant) // 使用 Unity 的 null 检查运算符 '!'
|
||||
{
|
||||
return true; // 找到第一个敌对实体即返回 true,提高效率。
|
||||
continue; // 如果不是 CombatantEntity,则跳过此实体。
|
||||
}
|
||||
|
||||
// 检查目标 CombatantEntity 是否已失效(死亡或摧毁)。
|
||||
// 同理,这里假设 CombatantEntity 包含 IsDead 属性。
|
||||
// **重要:请确保 CombatantEntity 定义了 IsDead 属性,或者替换为更通用的失效状态属性。**
|
||||
if (currentCombatant.IsDead)
|
||||
{
|
||||
continue; // 目标 CombatantEntity 已经失效,不是活跃的敌对实体。
|
||||
}
|
||||
|
||||
// 关系判断:判断当前 CombatantEntity 与源 CombatantEntity 是否为“敌对”关系。
|
||||
// AffiliationManager.GetRelation 应该接收 CombatantEntity 类型。
|
||||
if (AffiliationManager.Instance.GetRelation(sourceCombatant, currentCombatant) == Relation.Hostile)
|
||||
{
|
||||
return true; // 找到第一个敌对的 CombatantEntity 即返回 true,提高效率。
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false; // 遍历完所有实体都未找到敌对实体。
|
||||
return false; // 遍历完所有实体都未找到敌对的 CombatantEntity。
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -714,6 +763,22 @@ namespace Managers
|
||||
return false;
|
||||
}
|
||||
|
||||
// 新增校验:确保源实体自身是一个 CombatantEntity。
|
||||
var sourceCombatant = sourceEntityPrefab.entity as CombatantEntity;
|
||||
if (sourceCombatant == null)
|
||||
{
|
||||
Debug.LogWarning(
|
||||
$"实体管理器:ExistsHostileInSightRange 方法中,源实体 '{sourceEntityPrefab.name}' 无法转换为 CombatantEntity 类型。无法有效判断是否存在敌对的活跃实体。");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sourceCombatant.IsDead)
|
||||
{
|
||||
// 源 CombatantEntity 已经失效,它无法判断敌对实体。
|
||||
// 此时它本身可能也无法移动或感知,所以直接返回false。
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sightRange <= 0)
|
||||
{
|
||||
Debug.LogWarning($"实体管理器:ExistsHostileInSightRange 方法中,视野范围必须为正值。接收到的值为: {sightRange}。");
|
||||
@ -737,27 +802,42 @@ namespace Managers
|
||||
|
||||
// 预计算平方距离:避免在循环内部重复计算平方根,提高性能。
|
||||
var sightRangeSqr = sightRange * sightRange;
|
||||
var sourcePos = sourceEntityPrefab.transform.position;
|
||||
var sourcePos = sourceCombatant.transform.position; // 修改点:使用 sourceCombatant 的位置
|
||||
// 遍历所有实体:遍历维度内的所有派系,再遍历每个派系下的所有实体。
|
||||
foreach (var factionEntities in factionDict.Values)
|
||||
{
|
||||
foreach (var currentEntityPrefab in factionEntities)
|
||||
{
|
||||
// 实体有效性及排除源实体自身:
|
||||
// 1. 排除无效或已死亡的实体。
|
||||
// 1. 排除无效的实体预制体或内部实体。
|
||||
// 2. 排除源实体自身。
|
||||
if (currentEntityPrefab == null || currentEntityPrefab.entity == null ||
|
||||
currentEntityPrefab.entity.IsDead || currentEntityPrefab == sourceEntityPrefab)
|
||||
currentEntityPrefab == sourceEntityPrefab) // 源实体自身已被转换为 sourceCombatant 并检查,这里只检查 prefab 引用是否相同
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// 关系判断:判断当前实体与源实体是否为“敌对”关系。
|
||||
if (affiliationManager.GetRelation(sourceEntityPrefab.entity, currentEntityPrefab.entity) ==
|
||||
Relation.Hostile)
|
||||
// 【核心改动】新增类型检查:只有能转换为 CombatantEntity 的实体才会被进一步判断。
|
||||
var currentCombatant = currentEntityPrefab.entity as CombatantEntity;
|
||||
if (currentCombatant == null)
|
||||
{
|
||||
continue; // 如果不是 CombatantEntity,则跳过此实体。
|
||||
}
|
||||
|
||||
// 检查目标 CombatantEntity 是否已失效(死亡或摧毁)。
|
||||
if (currentCombatant.IsDead)
|
||||
{
|
||||
continue; // 目标 CombatantEntity 已经失效,不是活跃的敌对实体。
|
||||
}
|
||||
|
||||
// 关系判断:判断当前 CombatantEntity 与源 CombatantEntity 是否为“敌对”关系。
|
||||
if (affiliationManager.GetRelation(sourceCombatant, currentCombatant) ==
|
||||
Relation.Hostile) // 修改点:使用 CombatantEntity 类型参数
|
||||
{
|
||||
// 距离判断:如果关系敌对,进一步判断其是否在视野范围内。
|
||||
var distanceSqr = Vector3.SqrMagnitude(currentEntityPrefab.transform.position - sourcePos);
|
||||
var distanceSqr =
|
||||
Vector3.SqrMagnitude(currentCombatant.transform.position -
|
||||
sourcePos); // currentCombatant.transform
|
||||
if (distanceSqr <= sightRangeSqr)
|
||||
{
|
||||
return true; // 找到第一个视野范围内的敌对实体即返回 true。
|
||||
|
@ -107,7 +107,7 @@ namespace Managers
|
||||
}
|
||||
|
||||
// 2. 构造完整的类型名称。
|
||||
string fullTypeName = $"{targetNamespace}.{className}";
|
||||
var fullTypeName = $"{targetNamespace}.{className}";
|
||||
Type targetType = null;
|
||||
|
||||
// 3. 尝试直接从程序集获取类型。
|
||||
@ -136,7 +136,7 @@ namespace Managers
|
||||
try
|
||||
{
|
||||
// 使用 Activator.CreateInstance 实例化对象。它默认调用无参公共构造函数。
|
||||
object instance = Activator.CreateInstance(targetType);
|
||||
var instance = Activator.CreateInstance(targetType);
|
||||
return instance as EventWorkClassBase;
|
||||
}
|
||||
catch (MissingMethodException ex)
|
||||
|
@ -65,11 +65,11 @@ namespace Managers
|
||||
|
||||
public Item.ItemResource GetItem(string defName)
|
||||
{
|
||||
return _items.GetValueOrDefault(defName, null);
|
||||
return _items.GetValueOrDefault(defName, defaultItem);
|
||||
}
|
||||
public Item.ItemResource FindItemByName(string itemName)
|
||||
{
|
||||
if (string.IsNullOrEmpty(itemName)) return null;
|
||||
if (string.IsNullOrEmpty(itemName)) return defaultItem;
|
||||
return _itemsByName.GetValueOrDefault(itemName)?.FirstOrDefault();
|
||||
}
|
||||
|
||||
|
@ -34,7 +34,7 @@ namespace Managers
|
||||
/// <summary>
|
||||
/// 获取当前启动步骤的描述。
|
||||
/// </summary>
|
||||
public string StepDescription { get; private set; } = "包图像管理器正在准备中...";
|
||||
public string StepDescription { get; } = "图像管理器正在加载中";
|
||||
|
||||
/// <summary>
|
||||
/// 初始化图像管理器,加载默认精灵并处理所有 ImageDef 定义。
|
||||
@ -240,7 +240,6 @@ namespace Managers
|
||||
{
|
||||
packagesImages.Clear();
|
||||
sprites.Clear();
|
||||
StepDescription = "包图像管理器数据已清理。"; // 更新状态
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -263,40 +262,9 @@ namespace Managers
|
||||
/// <returns>如果找到对应的精灵,则返回该精灵;否则返回 <see cref="defaultSprite"/>。</returns>
|
||||
public Sprite GetSprite(ImageDef ima)
|
||||
{
|
||||
if (ima == null) return defaultSprite;
|
||||
return GetSprite(ima.packID, ima.defName);
|
||||
return ima == null ? defaultSprite : GetSprite(ima.defName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据包ID和精灵名称获取对应的精灵。
|
||||
/// </summary>
|
||||
/// <param name="packID">精灵所属的包ID。此参数在此版本中已不再用于字典查找,但为保持兼容性而保留。</param>
|
||||
/// <param name="name">精灵的名称(全局唯一的DefName)。</param>
|
||||
/// <returns>如果找到对应的精灵,则返回该精灵;否则返回 <see cref="defaultSprite"/>。</returns>
|
||||
public Sprite GetSprite(string packID, string name)
|
||||
{
|
||||
if (sprites.TryGetValue(name, out var sprite))
|
||||
return sprite;
|
||||
|
||||
// 如果未找到,返回默认精灵
|
||||
return defaultSprite;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据包ID、基础名称和索引获取被分割的子精灵。
|
||||
/// </summary>
|
||||
/// <param name="packID">精灵所属的包ID。此参数在此版本中已不再用于字典查找,但为保持兼容性而保留。</param>
|
||||
/// <param name="name">精灵的基础名称(全局唯一的DefName)。</param>
|
||||
/// <param name="index">子精灵的索引。</param>
|
||||
/// <returns>如果找到对应的子精灵,则返回该精灵;否则返回 <see cref="defaultSprite"/>。</returns>
|
||||
public Sprite GetSprite(string packID, string name, int index)
|
||||
{
|
||||
var fullName = $"{name}_{index}";
|
||||
return GetSprite(packID, fullName);
|
||||
}
|
||||
|
||||
// ---------- 新增的查询接口 ----------
|
||||
|
||||
/// <summary>
|
||||
/// 根据精灵名称(全局唯一的DefName)获取对应的精灵。
|
||||
/// </summary>
|
||||
@ -304,9 +272,7 @@ namespace Managers
|
||||
/// <returns>如果找到对应的精灵,则返回该精灵;否则返回 <see cref="defaultSprite"/>。</returns>
|
||||
public Sprite GetSprite(string name)
|
||||
{
|
||||
if (sprites.TryGetValue(name, out var sprite))
|
||||
return sprite;
|
||||
return defaultSprite;
|
||||
return sprites.GetValueOrDefault(name, defaultSprite);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -320,5 +286,12 @@ namespace Managers
|
||||
var fullName = $"{name}_{index}";
|
||||
return GetSprite(fullName);
|
||||
}
|
||||
|
||||
public Sprite[] GetSprites(string[] names)
|
||||
{
|
||||
if (names == null || names.Length == 0)
|
||||
return new[] { defaultSprite };
|
||||
return names.Select(name => GetSprite(name)).ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
230
Client/Assets/Scripts/Managers/TemporaryAnimationManager.cs
Normal file
230
Client/Assets/Scripts/Managers/TemporaryAnimationManager.cs
Normal file
@ -0,0 +1,230 @@
|
||||
using System;
|
||||
using Prefab;
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
using UnityEngine.SceneManagement;
|
||||
using UnityEngine.UI;
|
||||
using Utils;
|
||||
|
||||
namespace Managers
|
||||
{
|
||||
public class TemporaryAnimationManager : MonoSingleton<TemporaryAnimationManager>
|
||||
{
|
||||
// 将包装图层改为由代码动态创建和管理
|
||||
private GameObject _temporaryAnimationLevel;
|
||||
private GameObject _temporaryAnimationUILevel;
|
||||
|
||||
[SerializeField] private TemporaryAnimatorImageUI temporaryAnimatorImageUIPrefab;
|
||||
[SerializeField] private TemporaryAnimatorSprite temporaryAnimatorSpritePrefab;
|
||||
[SerializeField] private TemporaryAnimatorText temporaryAnimatorTextPrefab;
|
||||
[SerializeField] private TemporaryAnimatorText temporaryAnimatorUITextPrefab;
|
||||
|
||||
private Func<float, float> defaultXoffset = f => Mathf.Sin(f * 3);
|
||||
private Func<float, float> defaultYoffset = f => f;
|
||||
|
||||
protected override void OnStart()
|
||||
{
|
||||
SceneManager.sceneLoaded += OnSceneLoaded;
|
||||
|
||||
CreateAnimationLayersForCurrentScene(SceneManager.GetActiveScene(), LoadSceneMode.Single);
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
SceneManager.sceneLoaded -= OnSceneLoaded;
|
||||
// 在Manager销毁时,也销毁创建的图层,防止残留
|
||||
if (_temporaryAnimationLevel != null)
|
||||
{
|
||||
Destroy(_temporaryAnimationLevel);
|
||||
}
|
||||
if (_temporaryAnimationUILevel != null)
|
||||
{
|
||||
Destroy(_temporaryAnimationUILevel);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnSceneLoaded(Scene scene, LoadSceneMode mode)
|
||||
{
|
||||
// 在新场景加载时,重新创建动画图层
|
||||
CreateAnimationLayersForCurrentScene(scene, mode);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 为当前活动场景创建或重新创建临时动画的父级图层。
|
||||
/// </summary>
|
||||
/// <param name="scene">当前加载的场景。</param>
|
||||
/// <param name="mode">场景加载模式。</param>
|
||||
private void CreateAnimationLayersForCurrentScene(Scene scene, LoadSceneMode mode)
|
||||
{
|
||||
// 销毁旧的图层(如果存在),确保每个场景都有独立的图层
|
||||
// 注意:如果TemporaryAnimationManager是DontDestroyOnLoad,
|
||||
// 那么旧的图层可能还在,需要显式销毁。
|
||||
if (_temporaryAnimationLevel != null)
|
||||
{
|
||||
Destroy(_temporaryAnimationLevel);
|
||||
}
|
||||
if (_temporaryAnimationUILevel != null)
|
||||
{
|
||||
Destroy(_temporaryAnimationUILevel);
|
||||
}
|
||||
|
||||
// 创建非UI动画的包装图层
|
||||
_temporaryAnimationLevel = new GameObject("TemporaryAnimations");
|
||||
// 将其移动到当前加载的场景的根目录,使其成为场景的一部分
|
||||
SceneManager.MoveGameObjectToScene(_temporaryAnimationLevel, scene);
|
||||
|
||||
// 1. 获取或创建 Canvas
|
||||
var mainCanvas = GetOrCreateMainCanvas();
|
||||
// 2. 创建 _temporaryAnimationUILevel GameObject
|
||||
_temporaryAnimationUILevel = new GameObject("TemporaryUIAnimations");
|
||||
// 3. 将其设置为 Canvas 的子对象
|
||||
// SetParent(parentTransform, worldPositionStay: false) 会将子对象的局部位置重置为 (0,0,0)
|
||||
// 这对于新的UI元素通常是期望的行为
|
||||
_temporaryAnimationUILevel.transform.SetParent(mainCanvas.transform, false);
|
||||
// 4. 重置新创建的UI容器的局部变换
|
||||
// 确保它相对于父级 Canvas 处于一个干净的初始状态
|
||||
_temporaryAnimationUILevel.transform.localPosition = Vector3.zero;
|
||||
_temporaryAnimationUILevel.transform.localScale = Vector3.one;
|
||||
_temporaryAnimationUILevel.transform.localRotation = Quaternion.identity;
|
||||
|
||||
}
|
||||
|
||||
public void GenerateTemporaryAnimation(string str, Vector3 position, float lifeTime = 3, float fps = 3,
|
||||
Func<float, float> xShift = null,
|
||||
Func<float, float> yShift = null)
|
||||
{
|
||||
if (!_temporaryAnimationLevel)
|
||||
{
|
||||
Debug.LogError("TemporaryAnimationLevel is not initialized. Cannot generate non-UI animation.");
|
||||
return;
|
||||
}
|
||||
|
||||
xShift ??= defaultXoffset;
|
||||
yShift ??= defaultYoffset;
|
||||
var textObj = Instantiate(temporaryAnimatorTextPrefab, _temporaryAnimationLevel.transform);
|
||||
textObj.transform.position = new Vector3(position.x,position.y);
|
||||
textObj.Init(str, fps);
|
||||
textObj.SetAnimationFunctions(xShift, yShift);
|
||||
textObj.lifeTime = lifeTime;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 生成一个非UI精灵临时动画。
|
||||
/// </summary>
|
||||
/// <param name="sprites">动画的精灵帧数组。</param>
|
||||
/// <param name="position">动画在世界坐标系中的初始位置。</param>
|
||||
/// <param name="fps">动画帧率。</param>
|
||||
/// <param name="xShift">X轴位移函数。</param>
|
||||
/// <param name="yShift">Y轴位移函数。</param>
|
||||
public void GenerateTemporaryAnimation(Sprite[] sprites, Vector3 position,float lifeTime = 3, float fps = 3, Func<float, float> xShift = null,
|
||||
Func<float, float> yShift = null)
|
||||
{
|
||||
if (_temporaryAnimationLevel == null)
|
||||
{
|
||||
Debug.LogError("TemporaryAnimationLevel is not initialized. Cannot generate non-UI animation.");
|
||||
return;
|
||||
}
|
||||
|
||||
xShift ??= defaultXoffset;
|
||||
yShift ??= defaultYoffset;
|
||||
var obj = Instantiate(temporaryAnimatorSpritePrefab, _temporaryAnimationLevel.transform);
|
||||
obj.transform.position = new Vector3(position.x,position.y);
|
||||
obj.Init(sprites, fps);
|
||||
obj.SetAnimationFunctions(xShift, yShift);
|
||||
obj.lifeTime = lifeTime;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 生成一个UI文本临时动画。
|
||||
/// </summary>
|
||||
/// <param name="str">动画的文本内容。</param>
|
||||
/// <param name="position">动画在UI坐标系(RectTransform.anchoredPosition)中的初始位置。</param>
|
||||
/// <param name="fps">动画帧率。</param>
|
||||
/// <param name="xShift">X轴位移函数。</param>
|
||||
/// <param name="yShift">Y轴位移函数。</param>
|
||||
public void GenerateTemporaryAnimationUI(string str, Vector2 position,float lifeTime = 3, float fps = 3, Func<float, float> xShift = null,
|
||||
Func<float, float> yShift = null)
|
||||
{
|
||||
if (!_temporaryAnimationUILevel)
|
||||
{
|
||||
Debug.LogError("TemporaryAnimationUILevel is not initialized. Cannot generate UI animation.");
|
||||
return;
|
||||
}
|
||||
|
||||
xShift ??= defaultXoffset;
|
||||
yShift ??= defaultYoffset;
|
||||
var textObj = Instantiate(temporaryAnimatorUITextPrefab, _temporaryAnimationUILevel.transform);
|
||||
textObj.transform.position = position;
|
||||
textObj.Init(str, fps);
|
||||
textObj.SetAnimationFunctions(xShift, yShift);
|
||||
textObj.lifeTime = lifeTime;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 生成一个UI精灵临时动画。
|
||||
/// </summary>
|
||||
/// <param name="sprites">动画的精灵帧数组。</param>
|
||||
/// <param name="position">动画在UI坐标系(RectTransform.anchoredPosition)中的初始位置。</param>
|
||||
/// <param name="fps">动画帧率。</param>
|
||||
/// <param name="xShift">X轴位移函数。</param>
|
||||
/// <param name="yShift">Y轴位移函数。</param>
|
||||
public void GenerateTemporaryAnimationUI(Sprite[] sprites, Vector2 position,float lifeTime = 3, float fps = 3, Func<float, float> xShift = null,
|
||||
Func<float, float> yShift = null)
|
||||
{
|
||||
if (_temporaryAnimationUILevel == null)
|
||||
{
|
||||
Debug.LogError("TemporaryAnimationUILevel is not initialized. Cannot generate UI animation.");
|
||||
return;
|
||||
}
|
||||
|
||||
xShift ??= defaultXoffset;
|
||||
yShift ??= defaultYoffset;
|
||||
var obj = Instantiate(temporaryAnimatorImageUIPrefab, _temporaryAnimationUILevel.transform);
|
||||
obj.transform.position = position;
|
||||
obj.Init(sprites, fps);
|
||||
obj.SetAnimationFunctions(xShift, yShift);
|
||||
obj.lifeTime = lifeTime;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 寻找场景中现有的 Canvas,如果不存在则创建一个新的。
|
||||
/// 同时确保场景中存在 EventSystem。
|
||||
/// </summary>
|
||||
/// <returns>返回场景中的 Canvas 组件。</returns>
|
||||
private static Canvas GetOrCreateMainCanvas()
|
||||
{
|
||||
var existingCanvas = FindFirstObjectByType<Canvas>();
|
||||
if (existingCanvas != null)
|
||||
{
|
||||
EnsureEventSystemExists(); // 即使Canvas已存在,也要确保EventSystem存在
|
||||
return existingCanvas;
|
||||
}
|
||||
|
||||
// 创建 Canvas GameObject
|
||||
var canvasGO = new GameObject("Canvas");
|
||||
canvasGO.layer = LayerMask.NameToLayer("UI"); // 建议将UI设置为UI层
|
||||
// 添加 Canvas 组件
|
||||
var newCanvas = canvasGO.AddComponent<Canvas>();
|
||||
newCanvas.renderMode = RenderMode.ScreenSpaceOverlay; // 最常见的UI渲染模式
|
||||
// 添加其他必要的UI组件
|
||||
canvasGO.AddComponent<CanvasScaler>();
|
||||
canvasGO.AddComponent<GraphicRaycaster>();
|
||||
// 确保场景中存在 EventSystem,以便UI交互(按钮点击等)能正常工作
|
||||
EnsureEventSystemExists();
|
||||
return newCanvas;
|
||||
}
|
||||
/// <summary>
|
||||
/// 检查场景中是否存在 EventSystem,如果不存在则创建一个。
|
||||
/// </summary>
|
||||
private static void EnsureEventSystemExists()
|
||||
{
|
||||
var existingEventSystem = FindFirstObjectByType<EventSystem>();
|
||||
if (existingEventSystem != null) return;
|
||||
var eventSystemGO = new GameObject("EventSystem");
|
||||
eventSystemGO.AddComponent<EventSystem>();
|
||||
eventSystemGO.AddComponent<StandaloneInputModule>(); // 最常见的输入模块
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 91e1c480bf534c098d8ca7ad1fd81230
|
||||
timeCreated: 1757051915
|
@ -80,7 +80,7 @@ namespace Managers
|
||||
continue;
|
||||
}
|
||||
// 获取对应精灵
|
||||
var sprite = imagePack.GetSprite(mappingTableDef.packID, spriteName);
|
||||
var sprite = imagePack.GetSprite(spriteName);
|
||||
if (sprite == null)
|
||||
{
|
||||
Debug.LogError($"<color=red>来自 '{packName}' 定义的 TileMappingTableDef 键值 '{spriteName}' 中存在未定义的图片名称。</color>");
|
||||
|
Reference in New Issue
Block a user