Add NPC animation behavior
This commit is contained in:
@@ -307,7 +307,7 @@ public class CombineActHolderFolderAssigner : EditorWindow
|
||||
if (AssetDatabase.GetMainAssetTypeAtPath(prefabPath) != typeof(GameObject))
|
||||
return false;
|
||||
|
||||
GameObject prefabAsset = AssetDatabase.LoadMainAssetAtPath<GameObject>(prefabPath);
|
||||
GameObject prefabAsset = (GameObject)AssetDatabase.LoadMainAssetAtPath(prefabPath);
|
||||
if (prefabAsset == null)
|
||||
return false;
|
||||
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:d6a8ecae27ef44213a7965bfcb0e1cfcf30a013734f9c6fd14095d6440259005
|
||||
size 145407
|
||||
oid sha256:ea74bc9aef8c1251035c339a2a463e2d026cc0af379759abf687dcf13c9bda99
|
||||
size 146707
|
||||
|
||||
@@ -1271,43 +1271,55 @@ public class CECModel
|
||||
/// <returns>Hook Transform or null / 挂点变换,未找到返回null</returns>
|
||||
public Transform GetHook(string hookName, bool recursive = true)
|
||||
{
|
||||
// Auto-initialize if not set (lazy initialization)
|
||||
// 如果未设置则自动初始化(延迟初始化)
|
||||
if (m_skeletonBuilder == null)
|
||||
{
|
||||
InitializeSkeletonBuilder();
|
||||
if (m_skeletonBuilder == null)
|
||||
{
|
||||
return null; // Still no skeleton found
|
||||
}
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(hookName))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// Check cache first
|
||||
// 首先检查缓存
|
||||
if (m_hookCache.TryGetValue(hookName, out Transform cachedHook))
|
||||
{
|
||||
if (cachedHook != null) // Unity "fake null" check
|
||||
{
|
||||
if (cachedHook != null)
|
||||
return cachedHook;
|
||||
}
|
||||
m_hookCache.Remove(hookName); // Remove invalid entry
|
||||
m_hookCache.Remove(hookName);
|
||||
}
|
||||
|
||||
// Lookup from skeleton
|
||||
// 从骨架查找
|
||||
Transform hook = m_skeletonBuilder.GetHook(hookName, recursive);
|
||||
Transform hook = null;
|
||||
|
||||
if (m_skeletonBuilder == null)
|
||||
InitializeSkeletonBuilder();
|
||||
|
||||
if (m_skeletonBuilder != null)
|
||||
{
|
||||
hook = m_skeletonBuilder.GetHook(hookName, recursive);
|
||||
}
|
||||
else if (m_transform != null)
|
||||
{
|
||||
// [中文] NPC 无 SkeletonBuilder:按子节点名称查找挂点
|
||||
// [English] NPCs without SkeletonBuilder: resolve hooks by child transform name
|
||||
hook = FindHookByChildName(m_transform, hookName, recursive);
|
||||
}
|
||||
|
||||
if (hook != null)
|
||||
{
|
||||
m_hookCache[hookName] = hook; // Cache for performance
|
||||
}
|
||||
m_hookCache[hookName] = hook;
|
||||
return hook;
|
||||
}
|
||||
|
||||
static Transform FindHookByChildName(Transform root, string hookName, bool recursive)
|
||||
{
|
||||
if (root == null || string.IsNullOrEmpty(hookName))
|
||||
return null;
|
||||
if (root.name == hookName)
|
||||
return root;
|
||||
if (!recursive)
|
||||
return null;
|
||||
foreach (Transform child in root)
|
||||
{
|
||||
Transform found = FindHookByChildName(child, hookName, true);
|
||||
if (found != null)
|
||||
return found;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
public AnimationClip GetAnimationClip(string animationName)
|
||||
{
|
||||
if (m_pMapAnimancer != null)
|
||||
|
||||
@@ -12,6 +12,7 @@ using UnityEngine;
|
||||
using BrewMonster.Scripts.Skills;
|
||||
using BrewMonster.Network;
|
||||
using Animancer;
|
||||
using BrewMonster.Scripts.ECModel;
|
||||
|
||||
public class CECNPC : CECObject
|
||||
{
|
||||
@@ -801,7 +802,9 @@ public class CECNPC : CECObject
|
||||
Array.Clear(m_aExtStates, 0, m_aExtStates.Length);
|
||||
|
||||
m_pNPCModelPolicy = null;
|
||||
m_pNPCCECModel = null;
|
||||
PoolManager.Instance.Despawn(m_modelVisual);
|
||||
m_modelVisual = null;
|
||||
/*if (m_pPateName)
|
||||
{
|
||||
delete m_pPateName;
|
||||
@@ -1145,28 +1148,56 @@ public class CECNPC : CECObject
|
||||
return;
|
||||
}
|
||||
|
||||
uint bornStampAtRequest = GetBornStamp();
|
||||
int nidAtRequest = m_NPCInfo.nid;
|
||||
|
||||
if (m_modelVisual != null)
|
||||
{
|
||||
PoolManager.Instance.Despawn(m_modelVisual);
|
||||
m_modelVisual = null;
|
||||
m_pNPCCECModel?.InvalidateHookCache();
|
||||
}
|
||||
|
||||
GameObject loadedVisual = null;
|
||||
try
|
||||
{
|
||||
szModelFile = AFile.NormalizePath(szModelFile.ToLower(), true);
|
||||
m_modelVisual = await NPCBuilder.Instance.GetModelByPath(szModelFile);
|
||||
if (m_modelVisual == null)
|
||||
loadedVisual = await NPCBuilder.Instance.GetModelByPath(szModelFile);
|
||||
if (loadedVisual == null)
|
||||
{
|
||||
m_modelVisual = GameObject.CreatePrimitive(PrimitiveType.Capsule);
|
||||
m_modelVisual.name = szModelFile;
|
||||
loadedVisual = GameObject.CreatePrimitive(PrimitiveType.Capsule);
|
||||
loadedVisual.name = szModelFile;
|
||||
BMLogger.LogWarning($" CECNPC.QueueLoadNPCModel model == null szModelFile= {szModelFile} ");
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
m_modelVisual = GameObject.CreatePrimitive(PrimitiveType.Capsule);
|
||||
loadedVisual = GameObject.CreatePrimitive(PrimitiveType.Capsule);
|
||||
BMLogger.LogWarning($" CECNPC.QueueLoadNPCModel model == null szModelFile= {szModelFile} ");
|
||||
}
|
||||
|
||||
//var monsterModel = Instantiate(model, transform);
|
||||
if (this == null || !gameObject)
|
||||
return;
|
||||
|
||||
if (GetBornStamp() != bornStampAtRequest || m_NPCInfo.nid != nidAtRequest)
|
||||
{
|
||||
if (loadedVisual != null)
|
||||
PoolManager.Instance.Despawn(loadedVisual);
|
||||
return;
|
||||
}
|
||||
|
||||
m_modelVisual = loadedVisual;
|
||||
m_modelVisual.transform.SetParent(transform, false);
|
||||
m_modelVisual.SetActive(true);
|
||||
|
||||
InitializeNPCCECModel(m_modelVisual);
|
||||
|
||||
var npcVisual = GetComponent<NPCVisual>();
|
||||
npcVisual?.InitNPCEventDoneHandler(m_NPCInfo);
|
||||
if (npcVisual != null)
|
||||
{
|
||||
npcVisual.RefreshNamedAnimancer(m_modelVisual);
|
||||
npcVisual.InitNPCEventDoneHandler(m_NPCInfo);
|
||||
}
|
||||
|
||||
// UINPC.Start can run before async model instantiate; refresh anchor once SMR hierarchy exists.
|
||||
// UINPC.Start可能在异步模型实例化之前执行;SMR层次就绪后刷新名牌锚点。
|
||||
@@ -1177,6 +1208,48 @@ public class CECNPC : CECObject
|
||||
|
||||
//QueueECModelForLoad(MTL_ECM_NPC, GetNPCInfo().nid, GetBornStamp(), GetServerPos(), szModelFile, tid);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Bind CECModel to the loaded NPC visual (prefab holds NamedAnimancer + CombineActHolder; no SkeletonBuilder).
|
||||
/// 将CECModel绑定到已加载的NPC视觉对象(预制体含NamedAnimancer与CombineActHolder;NPC无SkeletonBuilder)。
|
||||
/// </summary>
|
||||
void InitializeNPCCECModel(GameObject modelRoot)
|
||||
{
|
||||
if (modelRoot == null)
|
||||
return;
|
||||
|
||||
if (m_pNPCCECModel == null)
|
||||
m_pNPCCECModel = new CECModel();
|
||||
|
||||
m_pNPCCECModel.SetId(m_NPCInfo.nid);
|
||||
m_pNPCCECModel.SetTransform(modelRoot.transform);
|
||||
m_pNPCCECModel.InvalidateHookCache();
|
||||
|
||||
NamedAnimancerComponent animancer = modelRoot.GetComponentInChildren<NamedAnimancerComponent>(true);
|
||||
if (animancer != null)
|
||||
m_pNPCCECModel.SetNamedAnimancerComponent(animancer);
|
||||
|
||||
CombineActHolder combineActHolder = modelRoot.GetComponentInChildren<CombineActHolder>(true);
|
||||
if (combineActHolder != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (combineActHolder.ActionSO != null)
|
||||
m_pNPCCECModel.SetCombinedAction(combineActHolder.ActionSO);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
BMLogger.LogWarning($"[CECNPC] Failed to read CombineActHolder.ActionSO on '{modelRoot.name}': {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
SkeletonBuilder skeletonBuilder = modelRoot.GetComponentInChildren<SkeletonBuilder>(true);
|
||||
if (skeletonBuilder != null)
|
||||
{
|
||||
m_pNPCCECModel.SetSkeletonBuilder(skeletonBuilder);
|
||||
m_pNPCCECModel.InitializeSkeletonBuilder();
|
||||
}
|
||||
}
|
||||
public ROLEBASICPROP GetBasicProps() { return m_BasicProps; }
|
||||
public ROLEEXTPROP GetExtendProps() { return m_ExtProps; }
|
||||
public void SetSelectedTarget(int id) { m_idSelTarget = id; }
|
||||
@@ -1641,13 +1714,10 @@ public class CECNPC : CECObject
|
||||
|
||||
public bool ShouldPlayNewActionFor(int iMoveMode)
|
||||
{
|
||||
if (m_pNPCModelPolicy.IsPlayingAction())
|
||||
{
|
||||
int iAction = GetMoveAction(iMoveMode);
|
||||
return !m_pNPCModelPolicy.IsPlayingAction(iAction)
|
||||
&& m_pNPCModelPolicy.HasAction(iAction);
|
||||
}
|
||||
return false;
|
||||
int iAction = GetMoveAction(iMoveMode);
|
||||
if (!m_pNPCModelPolicy.HasAction(iAction))
|
||||
return false;
|
||||
return !m_pNPCModelPolicy.IsPlayingAction(iAction);
|
||||
}
|
||||
public int GetMoveAction(int iMoveMode)
|
||||
{
|
||||
@@ -1833,77 +1903,14 @@ public class CECNPC : CECObject
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get NPC's CECModel instance
|
||||
/// 获取NPC的CECModel实例
|
||||
/// Get NPC's CECModel instance (bound after QueueLoadNPCModel, or lazily from m_modelVisual).
|
||||
/// 获取NPC的CECModel实例(QueueLoadNPCModel后绑定,或从m_modelVisual延迟绑定)。
|
||||
/// </summary>
|
||||
/// <returns>CECModel instance or null / CECModel实例,未找到返回null</returns>
|
||||
public CECModel GetModel()
|
||||
{
|
||||
// Initialize CECModel if not already created
|
||||
// 如果尚未创建,则初始化CECModel
|
||||
if (m_pNPCCECModel == null)
|
||||
{
|
||||
// Find the model GameObject (typically a child of this NPC)
|
||||
// 查找模型GameObject(通常是此NPC的子对象)
|
||||
GameObject modelObject = null;
|
||||
|
||||
// Try to find model in children (where NPC models are typically placed)
|
||||
// 尝试在子对象中查找模型(NPC模型通常放置在那里)
|
||||
NPCVisual npcVisual = GetComponent<NPCVisual>();
|
||||
if (npcVisual != null)
|
||||
{
|
||||
// Find SkeletonBuilder which is typically on the model GameObject
|
||||
// 查找通常在模型GameObject上的SkeletonBuilder
|
||||
SkeletonBuilder skeleton = GetComponentInChildren<SkeletonBuilder>(true);
|
||||
if (skeleton != null)
|
||||
{
|
||||
modelObject = skeleton.gameObject;
|
||||
}
|
||||
}
|
||||
|
||||
// If no model found, try to find any child with SkeletonBuilder
|
||||
// 如果未找到模型,尝试查找任何带有SkeletonBuilder的子对象
|
||||
if (modelObject == null)
|
||||
{
|
||||
SkeletonBuilder skeleton = GetComponentInChildren<SkeletonBuilder>(true);
|
||||
if (skeleton != null)
|
||||
{
|
||||
modelObject = skeleton.gameObject;
|
||||
}
|
||||
}
|
||||
|
||||
if (modelObject != null)
|
||||
{
|
||||
// Create and initialize CECModel instance
|
||||
// 创建并初始化CECModel实例
|
||||
m_pNPCCECModel = new CECModel();
|
||||
m_pNPCCECModel.SetTransform(modelObject.transform);
|
||||
|
||||
// Find and set SkeletonBuilder
|
||||
// 查找并设置SkeletonBuilder
|
||||
SkeletonBuilder skeletonBuilder = modelObject.GetComponent<SkeletonBuilder>();
|
||||
if (skeletonBuilder == null)
|
||||
{
|
||||
skeletonBuilder = modelObject.GetComponentInChildren<SkeletonBuilder>(true);
|
||||
}
|
||||
|
||||
if (skeletonBuilder != null)
|
||||
{
|
||||
m_pNPCCECModel.SetSkeletonBuilder(skeletonBuilder);
|
||||
m_pNPCCECModel.InitializeSkeletonBuilder();
|
||||
}
|
||||
NamedAnimancerComponent animancer = modelObject.GetComponent<NamedAnimancerComponent>();
|
||||
if (animancer == null)
|
||||
{
|
||||
animancer = modelObject.GetComponentInChildren<NamedAnimancerComponent>(true);
|
||||
}
|
||||
if (animancer != null)
|
||||
{
|
||||
m_pNPCCECModel.SetNamedAnimancerComponent(animancer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (m_pNPCCECModel == null && m_modelVisual != null)
|
||||
InitializeNPCCECModel(m_modelVisual);
|
||||
return m_pNPCCECModel;
|
||||
}
|
||||
|
||||
|
||||
@@ -10,8 +10,8 @@ using UnityEngine;
|
||||
public class CECNPCModelDefaultPolicy
|
||||
: CECNPCModelPolicy
|
||||
{
|
||||
CECModel m_pNPCModel;
|
||||
CECNPC m_pNPC;
|
||||
CECModel NPCModel => m_pNPC?.GetModel();
|
||||
int m_nBrushes;
|
||||
A3DAABB m_CHAABB; // AABB Updated with m_ppBrushes
|
||||
// number of brush object used in collision
|
||||
@@ -26,10 +26,17 @@ public class CECNPCModelDefaultPolicy
|
||||
|
||||
public CECNPCModelDefaultPolicy(CECNPC pNPC)
|
||||
{
|
||||
m_pNPCModel = new CECModel();
|
||||
m_pNPC = pNPC;
|
||||
}
|
||||
|
||||
static bool IsMoveAction(int iAction)
|
||||
{
|
||||
return iAction == (int)NPCActionIndex.ACT_WALK
|
||||
|| iAction == (int)NPCActionIndex.ACT_RUN
|
||||
|| iAction == (int)NPCActionIndex.ACT_NPC_WALK
|
||||
|| iAction == (int)NPCActionIndex.ACT_NPC_RUN;
|
||||
}
|
||||
|
||||
public string GetActionName(int iAct, bool bAttackStart = false)
|
||||
{
|
||||
// Tạo builder thay cho static char[128]
|
||||
@@ -52,10 +59,8 @@ public class CECNPCModelDefaultPolicy
|
||||
}
|
||||
public override void ClearComActFlag(bool bSignalCurrent)
|
||||
{
|
||||
if (m_pNPCModel != null)
|
||||
{
|
||||
m_pNPCModel.ClearComActFlag(bSignalCurrent);
|
||||
}
|
||||
if (NPCModel != null)
|
||||
NPCModel.ClearComActFlag(bSignalCurrent);
|
||||
}
|
||||
public override bool PlayAttackAction(int nAttackSpeed, CECAttackEvent attackEvent)
|
||||
{
|
||||
@@ -70,10 +75,8 @@ public class CECNPCModelDefaultPolicy
|
||||
}
|
||||
public override void StopChannelAction()
|
||||
{
|
||||
if (m_pNPCModel != null)
|
||||
{
|
||||
m_pNPCModel.StopChannelAction(0, true);
|
||||
}
|
||||
if (NPCModel != null)
|
||||
NPCModel.StopChannelAction(0, true);
|
||||
}
|
||||
public override bool GetCHAABB(ref A3DAABB aabb)
|
||||
{
|
||||
@@ -92,8 +95,8 @@ public class CECNPCModelDefaultPolicy
|
||||
}
|
||||
bool HasCHAABB()
|
||||
{
|
||||
return m_pNPCModel != null
|
||||
&& m_pNPCModel.HasCHAABB()
|
||||
return NPCModel != null
|
||||
&& NPCModel.HasCHAABB()
|
||||
&& m_nBrushes > 0;
|
||||
}
|
||||
public override bool PlayModelAction(int iAction, bool bRestart, CECAttackEvent cECAttackEvent)
|
||||
@@ -125,11 +128,11 @@ public class CECNPCModelDefaultPolicy
|
||||
|
||||
if (_npcVisual.IsAnimationExist(szAct))
|
||||
{
|
||||
m_pNPCModel.QueueAction(_npcVisual.GetNPCINFO, szAct, ref ignoreRef, 0, 0, false, false, false);
|
||||
NPCModel?.QueueAction(_npcVisual.GetNPCINFO, szAct, ref ignoreRef, 0, 0, false, false, false);
|
||||
}
|
||||
if (_npcVisual.IsAnimationExist(szAct2))
|
||||
{
|
||||
m_pNPCModel.QueueAction(_npcVisual.GetNPCINFO, szAct2, ref ignoreRef, 300);
|
||||
NPCModel?.QueueAction(_npcVisual.GetNPCINFO, szAct2, ref ignoreRef, 300);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -141,7 +144,7 @@ public class CECNPCModelDefaultPolicy
|
||||
string szAct2 = GetActionName((int)NPCActionIndex.ACT_STAND);
|
||||
if (_npcVisual.IsAnimationExist(szAct2))
|
||||
{
|
||||
m_pNPCModel.QueueAction(_npcVisual.GetNPCINFO, szAct2, ref ignoreRef, 300);
|
||||
NPCModel?.QueueAction(_npcVisual.GetNPCINFO, szAct2, ref ignoreRef, 300);
|
||||
}
|
||||
//m_pNPCModel->QueueAction(GetActionName((int)NPCActionIndex.ACT_STAND));
|
||||
}
|
||||
@@ -154,7 +157,7 @@ public class CECNPCModelDefaultPolicy
|
||||
string szAct2 = GetActionName((int)NPCActionIndex.ACT_NPC_STAND);
|
||||
if (_npcVisual.IsAnimationExist(szAct2))
|
||||
{
|
||||
m_pNPCModel.QueueAction(_npcVisual.GetNPCINFO, szAct2, ref ignoreRef, 300);
|
||||
NPCModel?.QueueAction(_npcVisual.GetNPCINFO, szAct2, ref ignoreRef, 300);
|
||||
}
|
||||
//m_pNPCModel->QueueAction(GetActionName((int)NPCActionIndex.ACT_NPC_STAND));
|
||||
}
|
||||
@@ -169,11 +172,11 @@ public class CECNPCModelDefaultPolicy
|
||||
string szAct2 = GetActionName((int)NPCActionIndex.ACT_NPC_STAND);
|
||||
if (_npcVisual.IsAnimationExist(szAct))
|
||||
{
|
||||
m_pNPCModel.QueueAction(_npcVisual.GetNPCINFO, szAct, ref ignoreRef, 0, 0, false, false, false);
|
||||
NPCModel?.QueueAction(_npcVisual.GetNPCINFO, szAct, ref ignoreRef, 0, 0, false, false, false);
|
||||
}
|
||||
if (_npcVisual.IsAnimationExist(szAct2))
|
||||
{
|
||||
m_pNPCModel.QueueAction(_npcVisual.GetNPCINFO, szAct2, ref ignoreRef, 300);
|
||||
NPCModel?.QueueAction(_npcVisual.GetNPCINFO, szAct2, ref ignoreRef, 300);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -185,14 +188,19 @@ public class CECNPCModelDefaultPolicy
|
||||
string szAct2 = GetActionName((int)NPCActionIndex.ACT_STAND);
|
||||
if (_npcVisual.IsAnimationExist(szAct2))
|
||||
{
|
||||
m_pNPCModel.QueueAction(_npcVisual.GetNPCINFO, szAct2, ref ignoreRef, 300);
|
||||
NPCModel?.QueueAction(_npcVisual.GetNPCINFO, szAct2, ref ignoreRef, 300);
|
||||
}
|
||||
//m_pNPCModel->QueueAction(GetActionName((int)NPCActionIndex.ACT_STAND));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
result = _npcVisual.TryPlayAction(GetActionName(iAction), cECAttackEvent);
|
||||
result = _npcVisual.TryPlayAction(
|
||||
GetActionName(iAction),
|
||||
cECAttackEvent,
|
||||
isHit: false,
|
||||
bRestart: bRestart,
|
||||
bLoop: true);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -229,7 +237,10 @@ public class CECNPCModelDefaultPolicy
|
||||
// [English] Returns true when the NPC model is present and the game object is active
|
||||
public override bool IsModelLoaded()
|
||||
{
|
||||
return m_pNPCModel != null && m_pNPC != null && m_pNPC.gameObject.activeInHierarchy;
|
||||
return m_pNPC != null
|
||||
&& m_pNPC.gameObject.activeInHierarchy
|
||||
&& NPCModel != null
|
||||
&& NPCModel.transform != null;
|
||||
}
|
||||
|
||||
// [中文] 异步加载并挂载状态效果 GFX 到指定挂点;以 (路径+挂点) 为键去重
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Animancer;
|
||||
using BrewMonster;
|
||||
using BrewMonster.Scripts.ECModel;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
@@ -9,29 +10,82 @@ public class NPCVisual : MonoBehaviour
|
||||
{
|
||||
[SerializeField] NamedAnimancerComponent namedAnimancer;
|
||||
protected CECNPC.INFO m_NPCInfo;
|
||||
[SerializeField] private Queue<string> _animationQueue = new Queue<string>();
|
||||
[SerializeField] private Queue<AnimationQueue> _animationQueue = new Queue<AnimationQueue>();
|
||||
[SerializeField] private AnimancerState _currentState;
|
||||
private string _previousAnimationName;
|
||||
private string _currentAnimationName;
|
||||
private bool debugNamePlateBounds = false;
|
||||
private const float fadeTime = .2f;
|
||||
private const float FadeTime = 100f;
|
||||
private const FadeMode FadeMode = Animancer.FadeMode.FixedDuration;
|
||||
|
||||
public CECNPC.INFO GetNPCINFO => m_NPCInfo;
|
||||
|
||||
public bool TryPlayAction(string animationName, CECAttackEvent cECAttackEvent, bool isHit = false, bool bRestart = true)
|
||||
public bool TryPlayAction(string animationName, CECAttackEvent cECAttackEvent, bool isHit = false, bool bRestart = true, bool bLoop = false)
|
||||
{
|
||||
if (namedAnimancer == null)
|
||||
return false;
|
||||
|
||||
if (namedAnimancer == null) return false;
|
||||
|
||||
if (namedAnimancer.IsPlaying(animationName)) return false;
|
||||
_currentState = namedAnimancer.TryPlay(animationName, fadeTime);
|
||||
if (isHit)
|
||||
if (namedAnimancer.IsPlaying(animationName))
|
||||
{
|
||||
if(_currentState != null)
|
||||
{
|
||||
_currentState.Events.OnEnd = () => SetHitOnEnd(cECAttackEvent);
|
||||
}
|
||||
if (!bRestart)
|
||||
return true;
|
||||
if (!bLoop)
|
||||
return false;
|
||||
}
|
||||
return _currentState != null;
|
||||
|
||||
if (!InternalPlayAnimation(animationName))
|
||||
return false;
|
||||
|
||||
_previousAnimationName = animationName;
|
||||
if (isHit)
|
||||
_currentState.Events.OnEnd = () => SetHitOnEnd(cECAttackEvent);
|
||||
else
|
||||
ApplyAnimationEndCallbacks(animationName, bLoop);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool InternalPlayAnimation(string animationName, float duration = FadeTime, FadeMode fadeMode = FadeMode, float playSpeed = 1.0f)
|
||||
{
|
||||
if (namedAnimancer == null)
|
||||
return false;
|
||||
|
||||
if (!namedAnimancer.States.TryGet(animationName, out _))
|
||||
return false;
|
||||
|
||||
_currentState = namedAnimancer.TryPlay(animationName, duration / 1000f, fadeMode);
|
||||
if (_currentState == null)
|
||||
return false;
|
||||
|
||||
_currentState.Time = 0;
|
||||
_currentState.Speed = playSpeed > 0f ? playSpeed : 1.0f;
|
||||
_currentAnimationName = animationName;
|
||||
return true;
|
||||
}
|
||||
|
||||
void ApplyAnimationEndCallbacks(string animationName, bool isLoop)
|
||||
{
|
||||
if (_currentState == null)
|
||||
return;
|
||||
_currentState.Events.OnEnd = () =>
|
||||
{
|
||||
if (isLoop)
|
||||
EnqueueAnimationForLooping(animationName);
|
||||
};
|
||||
}
|
||||
|
||||
bool EnqueueAnimationForLooping(string animationName)
|
||||
{
|
||||
if (namedAnimancer == null || _animationQueue.Count > 0)
|
||||
return false;
|
||||
|
||||
_animationQueue.Enqueue(new AnimationQueue
|
||||
{
|
||||
AnimationName = animationName,
|
||||
IsForceStopPrevious = false,
|
||||
IsLoop = true,
|
||||
});
|
||||
return true;
|
||||
}
|
||||
private void SetHitOnEnd(CECAttackEvent cECAttackEvent)
|
||||
{
|
||||
@@ -69,23 +123,35 @@ public class NPCVisual : MonoBehaviour
|
||||
public bool EnqueueAnimation(QueueNPCActionEvent @event)
|
||||
{
|
||||
if (namedAnimancer == null) return false;
|
||||
_animationQueue.Enqueue(@event.AnimationName);
|
||||
|
||||
_animationQueue.Enqueue(new AnimationQueue
|
||||
{
|
||||
AnimationName = @event.AnimationName,
|
||||
IsForceStopPrevious = false,
|
||||
IsLoop = false,
|
||||
});
|
||||
return true;
|
||||
}
|
||||
public string animName1;
|
||||
|
||||
private void PlayNext()
|
||||
{
|
||||
if (_animationQueue.Count == 0)
|
||||
{
|
||||
return;
|
||||
|
||||
if (_animationQueue.Peek().IsForceStopPrevious)
|
||||
{
|
||||
_currentState?.Stop();
|
||||
_currentState = null;
|
||||
}
|
||||
|
||||
if (_currentState == null) return;
|
||||
animName1 = _animationQueue.Peek();
|
||||
if (_currentState.NormalizedTime < 1f) return;
|
||||
string animName = _animationQueue.Dequeue();
|
||||
_currentState = namedAnimancer.TryPlay(animName);
|
||||
if (_currentState != null && _currentState.NormalizedTime < 1f)
|
||||
return;
|
||||
|
||||
var animationQueue = _animationQueue.Dequeue();
|
||||
_previousAnimationName = animationQueue.AnimationName;
|
||||
float duration = animationQueue.ITransTime > 0 ? animationQueue.ITransTime : FadeTime;
|
||||
if (!InternalPlayAnimation(animationQueue.AnimationName, duration, FadeMode, animationQueue.PlaySpeed))
|
||||
return;
|
||||
ApplyAnimationEndCallbacks(animationQueue.AnimationName, animationQueue.IsLoop);
|
||||
}
|
||||
private void OnDestroy()
|
||||
{
|
||||
@@ -114,27 +180,18 @@ public class NPCVisual : MonoBehaviour
|
||||
/// <param name="modelRoot">The root GameObject of the model to search for NamedAnimancerComponent / 要搜索NamedAnimancerComponent的模型根GameObject</param>
|
||||
public void RefreshNamedAnimancer(GameObject modelRoot = null)
|
||||
{
|
||||
_currentState = null;
|
||||
_previousAnimationName = null;
|
||||
_currentAnimationName = null;
|
||||
_animationQueue.Clear();
|
||||
|
||||
if (modelRoot != null)
|
||||
{
|
||||
// Search specifically within the model GameObject's hierarchy
|
||||
// 在模型GameObject的层次结构中搜索
|
||||
namedAnimancer = modelRoot.GetComponentInChildren<NamedAnimancerComponent>();
|
||||
}
|
||||
namedAnimancer = modelRoot.GetComponentInChildren<NamedAnimancerComponent>(true);
|
||||
else
|
||||
{
|
||||
// Fallback to searching from this component's hierarchy
|
||||
// 回退到从此组件的层次结构搜索
|
||||
namedAnimancer = GetComponentInChildren<NamedAnimancerComponent>();
|
||||
}
|
||||
namedAnimancer = GetComponentInChildren<NamedAnimancerComponent>(true);
|
||||
|
||||
if (namedAnimancer == null)
|
||||
{
|
||||
BMLogger.LogWarning($"NPCVisual: RefreshNamedAnimancer - namedAnimancer == null after refresh (modelRoot: {modelRoot?.name ?? "null"})");
|
||||
}
|
||||
else
|
||||
{
|
||||
BMLogger.LogMono(this, $"NPCVisual: RefreshNamedAnimancer - Successfully refreshed namedAnimancer from model: {modelRoot?.name ?? "default"}");
|
||||
}
|
||||
BMLogger.LogWarning($"NPCVisual: RefreshNamedAnimancer - namedAnimancer == null (modelRoot: {modelRoot?.name ?? "null"})");
|
||||
}
|
||||
|
||||
// Cached reference to the bone with the highest world-Y position at model-ready time.
|
||||
|
||||
Reference in New Issue
Block a user