diff --git a/Assets/ModelRenderer/Scripts/A3DModelReader/Common/A3DCombinedAction.cs b/Assets/ModelRenderer/Scripts/A3DModelReader/Common/A3DCombinedAction.cs index 929612ae24..fbfe5c536f 100755 --- a/Assets/ModelRenderer/Scripts/A3DModelReader/Common/A3DCombinedAction.cs +++ b/Assets/ModelRenderer/Scripts/A3DModelReader/Common/A3DCombinedAction.cs @@ -174,6 +174,12 @@ namespace ModelViewer.Common return true; } + public uint GetTimeSpan() { return m_dwSpan; } + public void SetTimeSpan(uint dwTimeSpan) + { + m_dwSpan = dwTimeSpan; + m_dwEndTime = m_dwStartTime + m_dwSpan; + } } @@ -188,7 +194,7 @@ namespace ModelViewer.Common } public int GetLoopNum() { return m_nLoopNum; } public ACTION_INFO GetActInfo() { return m_pInfo; } - public int GetTimeSpan() { return 1/*m_pInfo.GetTimeSpan()*/; } + public int GetTimeSpan() { return (int)m_pInfo.GetTimeSpan(); } public int GetTotalTime() { return GetLoopNum() * GetTimeSpan(); } } [System.Serializable] diff --git a/Assets/PerfectWorld/Scripts/Move/CECPlayer.cs b/Assets/PerfectWorld/Scripts/Move/CECPlayer.cs index df5cb83890..04246fe85f 100644 --- a/Assets/PerfectWorld/Scripts/Move/CECPlayer.cs +++ b/Assets/PerfectWorld/Scripts/Move/CECPlayer.cs @@ -21,7 +21,7 @@ using Unity.VisualScripting; using DG.Tweening.Plugins; using PerfectWorld.Scripts.Managers; using BrewMonster.Scripts.ECModel; - +using Animancer; namespace BrewMonster { public abstract partial class CECPlayer : CECObject, ITickable @@ -767,6 +767,7 @@ namespace BrewMonster // Set references on CECModel // 在CECModel上设置引用 m_pPlayerCECModel.SetSkeletonBuilder(skeletonBuilder); + m_pPlayerCECModel.SetNamedAnimancerComponent(m_pPlayerModel.GetComponentInChildren()); m_pPlayerCECModel.SetTransform(m_pPlayerModel.transform); if(combinedAction != null) { @@ -787,6 +788,7 @@ namespace BrewMonster return; } m_pPlayerCECModel.SetSkeletonBuilder(changeModel.GetComponentInChildren()); + m_pPlayerCECModel.SetNamedAnimancerComponent(changeModel.GetComponentInChildren()); m_pPlayerCECModel.SetTransform(changeModel.transform); // Keep action map in sync with the currently active model (major/dummy). // 让动作映射与当前激活模型(主模型/变身模型)保持一致。 @@ -836,6 +838,7 @@ namespace BrewMonster m_pPlayerCECModel = new CECModel(); } m_pPlayerCECModel.SetSkeletonBuilder(skeletonBuilder); + m_pPlayerCECModel.SetNamedAnimancerComponent(m_pPlayerModel.GetComponentInChildren()); m_pPlayerCECModel.SetTransform(m_pPlayerModel.transform); // Initialize skeleton builder (ensures hooks are available) diff --git a/Assets/PerfectWorld/Scripts/NPC/CECModel.cs b/Assets/PerfectWorld/Scripts/NPC/CECModel.cs index 382cb177c5..3c9a85c75c 100644 --- a/Assets/PerfectWorld/Scripts/NPC/CECModel.cs +++ b/Assets/PerfectWorld/Scripts/NPC/CECModel.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Threading; using System.IO; using System.Runtime.InteropServices; using BrewMonster.Scripts; @@ -17,6 +18,7 @@ using BrewMonster.Scripts.ECModel; using Cysharp.Threading.Tasks; using Unity.VisualScripting.FullSerializer; using Unity.VisualScripting; +using Animancer; public enum ECMScript { enumECMScriptStartAction = 0, @@ -55,10 +57,133 @@ public enum ActionChannel ACTCHA_WOUND = 1, ACTCHA_MAX = 16, // Keep this value same with A3DSkinModelActionCore::ACTCHA_MAX }; +public class FX_BINDING_BASE +{ + protected EVENT_INFO m_pInfo; + protected A3DCombActDynData m_pDynData; + public FX_BINDING_BASE(A3DCombActDynData pDynData) + { + m_pInfo = null; + m_pDynData = pDynData; + } + public virtual bool IsStop() { return false; } + public virtual void Stop() {} + public virtual void Render() {} + public virtual void UpdateParam(CECModel pECModel, int nDeltaTime) {} + public void SetInfo(EVENT_INFO pInfo) { m_pInfo = pInfo; } + public EVENT_INFO GetInfo() { return m_pInfo; } + public A3DCombActDynData GetDynData() { return m_pDynData; } +}; + +public class GFX_BINDING : FX_BINDING_BASE +{ + public GameObject m_pGfx; + public bool m_bNeedSetTM; + public bool m_bInitTMUpdated; // is the first time a delay update gfx to be set to place + public int m_dwTimeSpan; + public bool isStartPlaying = false; + private ParticleSystem _ps; + bool _destroyScheduled; + public GFX_BINDING(A3DCombActDynData pDynData) : base(pDynData) + { + + } + + public override void Stop() + { + if (m_pGfx == null || _destroyScheduled) + return; + _destroyScheduled = true; + DestroyAfterParticlesFinish().Forget(); + } + + async UniTaskVoid DestroyAfterParticlesFinish() + { + GameObject go = m_pGfx; + CancellationToken ct = go != null ? go.transform.GetCancellationTokenOnDestroy() : default; + try + { + if (go == null) + return; + if (_ps != null) + { + var main = _ps.main; + var delayType = main.useUnscaledTime ? DelayType.Realtime : DelayType.DeltaTime; + if (main.loop) + { + // Let the effect run one full loop cycle, then stop emitting. / 再播完一整圈循环后停发粒子 + //Magic number for trailing effect, I dunno what it mean. + float oneLoop = .75f; + await UniTask.Delay(TimeSpan.FromSeconds(oneLoop), delayType, cancellationToken: ct); + if (_ps != null && _ps.IsAlive(true)) + _ps.Stop(true, ParticleSystemStopBehavior.StopEmitting); + } + else + { + if (_ps.IsAlive(true)) + _ps.Stop(true, ParticleSystemStopBehavior.StopEmitting); + } + //await UniTask.WaitUntil(() => _ps == null || !_ps.IsAlive(true), cancellationToken: ct); + } + } + catch (OperationCanceledException) + { + Debug.Log("GFX_BINDING DestroyAfterParticlesFinish: OperationCanceledException"); + // go was destroyed elsewhere; skip / 对象在其他地方被销毁 + } + finally + { + if (m_pGfx != null) + GameObject.Destroy(m_pGfx); + m_pGfx = null; + _ps = null; + _destroyScheduled = false; + } + } + + /// True when this GFX has finished and may be removed from . / 特效已结束,可从绑定列表移除时为 true + public override bool IsStop() + { + if (_destroyScheduled) + return true; + if (!isStartPlaying) + return false; + return _ps == null || !_ps.isPlaying; + } + public override void Render() { + m_pGfx.SetActive(true); + m_pGfx.transform.localPosition = Vector3.zero; + m_pGfx.transform.localScale = Vector3.one; + _ps = m_pGfx.GetComponent(); + if (_ps != null) + _ps.Play(); + isStartPlaying = true; + } + public override void UpdateParam(CECModel pECModel, int nDeltaTime) + { + if(m_pGfx == null) + return; + if (m_pInfo != null && m_pInfo.m_dwStartTime <= nDeltaTime && !isStartPlaying) + { + Render(); + if (_ps != null) + m_pInfo.m_dwTimeSpan = (int)(_ps.main.duration * 1000); + } + } + public void SetGfx(GameObject gfx, Quaternion prefabRotation) { + m_pGfx = gfx; + m_pGfx.transform.localRotation = prefabRotation; + } + + //A3DMATRIX4 CalcGfxParentTM(A3DSkeletonHook* pHook, CECModel* pECModel); + //void UpdateGfxParentTM(A3DSkeletonHook* pHook, CECModel* pECModel, int iDeltaTime); +}; public class A3DCombActDynData { protected A3DCombinedAction m_pAct; protected CECModel m_pECModel; + protected int m_nCurLoop; + protected int m_nCurActLoop; protected int m_dwTimeSpan; protected float m_fWeight; protected int m_nTransTime; @@ -75,7 +200,7 @@ public class A3DCombActDynData protected int m_nChannel; protected int m_dwEventMask; - //ActDynArray m_arrActLoopNum; + public List m_arrActLoopNum; protected int m_nCurActIndex; protected int m_dwDynComActSpan; protected bool m_bIsInfiniteComAct; @@ -92,28 +217,20 @@ public class A3DCombActDynData public bool m_bSetSpeedWhenActStart; public float m_fModelScale; + /// Elapsed time (ms) along the combined action timeline; drives FX event triggers. / 组合动作时间轴已播放毫秒数,用于触发特效事件 + public int GetComActElapsedMs() { return m_dwTimeSpan; } + public List m_ActionNames = new List(); public List m_SFXNames = new List(); - public List m_GFXObjects = new List(); - public bool IsActionStopped() { return m_ActionNames.Count == 0; } + public List m_ActFxArray = new List(); /// True when there is no attack or damage has been applied. / 无攻击事件或已造成伤害则为 true - public bool IsAllEventFinished() { return (ActiveAttackEvent == null || ActiveAttackEvent.m_bDoDamaged); } - /// Animations finished and attack-event phase finished. / 动画与攻击事件阶段均结束 - public bool IsAllFinished() - { - if(IsActionStopped() && IsAllEventFinished()) - { - return true; - } - return false; - } public A3DCombActDynData(A3DCombinedAction pAct, CECModel pECModel, CECAttackEvent attackEvent) { m_pAct = pAct; m_pECModel = pECModel; - // m_nCurLoop = 0; - // m_nCurActLoop = 0; + m_nCurLoop = 0; + m_nCurActLoop = 0; m_dwTimeSpan = 0; m_fWeight = 1.0f; m_nTransTime = 200; @@ -140,12 +257,16 @@ public class A3DCombActDynData m_bResetPSWhenActionStop = false; m_bSetSpeedWhenActStart = false; m_fModelScale = 1.0f; + m_arrActLoopNum = new List(); var actInfoList = pAct.m_ActLst; foreach(var actInfo in actInfoList) { int nDynLoopNum = actInfo.CalcLoopNum(); if (!m_bIsInfiniteComAct && nDynLoopNum < 0) m_bIsInfiniteComAct = true; + m_arrActLoopNum.Add(new ACTIONDYN_DATA(nDynLoopNum, actInfo)); } + //Calculate the span of the combined action + RefreshCombinedActionSpansFromClips(); } /// @@ -168,49 +289,24 @@ public class A3DCombActDynData m_fModelScale = 1; //Vm_pECModel.GetAllRootBonesScale(); - PublishVisual(bForceStopPrevious, channelRank); - if (!m_bNoFx) - TriggerSFxFromEventList(); - // Resume(); - // UpdateAct(0); - // #ifdef _ANGELICA21 - // UpdateEvent(0, 0); - // #endif - } + RefreshCombinedActionSpansFromClips(); - /// - /// Notify visual layer and fire combined-action audio using data on this instance. - /// / 通知表现层并根据本实例数据触发组合动作音效。 - /// - void PublishVisual(bool bForceStopPrevious, byte channelRank) - { - if (m_pECModel == null || m_pAct == null) - return; - - var actionInfos = m_pAct.m_ActLst; - if (actionInfos != null && actionInfos.Count > 0 && !string.IsNullOrEmpty(actionInfos[0].m_strName)) + // PublishVisual(bForceStopPrevious, channelRank); + // if (!m_bNoFx) + // TriggerSFxFromEventList(); + Resume(); + UpdateAct(0); + if(true /*!m_bNoFx*/) { - ChannelAct channelAct = m_pECModel.GetChannelAct(m_nChannel); - if (channelAct != null) - { - bool isLoop = m_bIsInfiniteComAct; - int ownerId = m_pECModel.GetId(); - EventBus.PublishChannel(ownerId, new PlayActionEvent(ref channelAct, actionInfos[0].m_strName, m_nTransTime, bForceStopPrevious, ActiveAttackEvent, isLoop, channelRank)); - m_ActionNames.Add(actionInfos[0].m_strName); - for(int i = 1; i < actionInfos.Count; i++) - { - isLoop = m_bIsInfiniteComAct; - EventBus.PublishChannelClass(ownerId, new QueueActionEvent(ref channelAct, actionInfos[i].m_strName, null, false, null, m_nTransTime, false, isLoop, channelRank)); - m_ActionNames.Add(actionInfos[i].m_strName); - } - } + LoadFXFromEventList(); + UpdateEvent(0, 0); } } /// /// Sound events embedded in combined action; skip gfx paths. / 组合动作内嵌音效事件;跳过 gfx 路径。 /// - async void TriggerSFxFromEventList() + async void LoadFXFromEventList() { if (m_bNoFx || m_pAct == null) return; @@ -246,43 +342,29 @@ public class A3DCombActDynData { path = "gfx/" + path; GameObject prefab = await AddressableManager.Instance.LoadPrefabAsync(path); - GameObject fx = null; + GFX_BINDING fx = new GFX_BINDING(this); if(prefab != null && m_pECModel != null) { - // LoadPrefabAsync returns the prefab asset; must Instantiate before modifying transform. - // LoadPrefabAsync 返回的是预制体资源,必须先 Instantiate 再改 Transform。 - fx = GameObject.Instantiate(prefab, m_pECModel.transform); + var gfx = GameObject.Instantiate(prefab, m_pECModel.transform); + gfx.SetActive(false); Quaternion prefabRotation = prefab.transform.rotation; - fx.SetActive(true); - fx.transform.localPosition = Vector3.zero; - fx.transform.localRotation = prefabRotation; - fx.transform.localScale = Vector3.one; - ParticleSystem ps = fx.GetComponent(); - if (ps != null) - ps.Play(); + fx.SetGfx(gfx,prefabRotation); } else if (prefab == null) { - //THis for null gfx prefab. Debug.LogWarning("Missing gfx prefab, using placeholder: " + path); string path2 = "gfx/人物/技能/妖精/宠物召唤吟唱.gfx"; GameObject prefab2 = await AddressableManager.Instance.LoadPrefabAsync(path2); - if(prefab2 != null && m_pECModel != null) - { - fx = GameObject.Instantiate(prefab2, m_pECModel.transform); - Quaternion prefabRotation = prefab2.transform.rotation; - fx.SetActive(true); - fx.transform.localPosition = Vector3.zero; - fx.transform.localRotation = prefabRotation; - fx.transform.localScale = Vector3.one; - ParticleSystem ps2 = fx.GetComponent(); - if (ps2 != null) - ps2.Play(); - } + var gfx = GameObject.Instantiate(prefab2, m_pECModel.transform); + gfx.SetActive(false); + Quaternion prefabRotation = prefab2.transform.rotation; + fx.SetGfx(gfx,prefabRotation); + } if(fx != null) { - m_GFXObjects.Add(fx); + m_ActFxArray.Add(fx); + fx.SetInfo(eventInfo); } // var gfxLogPath = Path.Combine(Application.dataPath, "PerfectWorld", "Scripts", "NPC", "GFXLOG.txt"); // var lines = new List(); @@ -304,6 +386,49 @@ public class A3DCombActDynData } } } + + /// + /// Set each span from loaded duration (milliseconds), then refresh . + /// / 根据已加载 AnimationClip 时长(毫秒)设置各段 span,并更新组合总时长 m_dwDynComActSpan。 + /// + void RefreshCombinedActionSpansFromClips() + { + if (m_pECModel != null && m_pAct?.m_ActLst != null) + { + foreach (var actInfo in m_pAct.m_ActLst) + { + if (actInfo == null || string.IsNullOrEmpty(actInfo.m_strName)) + continue; + AnimationClip clip = m_pECModel.GetAnimationClip(actInfo.m_strName); + if (clip == null) + continue; + uint spanMs = (uint)Mathf.Max(1, Mathf.RoundToInt(clip.length * 1000f)); + actInfo.SetTimeSpan(spanMs); + } + } + + if (m_arrActLoopNum != null) + CalcDynComActSpan(); + } + + public void CalcDynComActSpan() + { + m_dwDynComActSpan = 0; + int nIndex, nNum = m_arrActLoopNum.Count; + for (nIndex = 0 ; nIndex < nNum ; ++nIndex) { + if (m_arrActLoopNum[nIndex].GetLoopNum() > 0) + m_dwDynComActSpan += m_arrActLoopNum[nIndex].GetTotalTime(); + else + m_dwDynComActSpan += m_arrActLoopNum[nIndex].GetTimeSpan(); + } + } + public void Resume() + { + m_dwTimeSpan = 0; + m_nCurActIndex = -1; + m_nCurActLoop = 0; + RemoveAllActiveFx(); + } public void Stop(bool bStopAct, bool bForceStopFx = false ) { RemoveAllActiveFx(); @@ -338,23 +463,133 @@ public class A3DCombActDynData // if (pChannel) // pChannel->StopAction(m_pAct->GetRank(m_nChannel)); } - public void UpdateAct(uint dwUpdateTime) + public void UpdateAct(uint dwDeltaTime) { - if(IsActionStopped()) + m_dwTimeSpan += (int)dwDeltaTime; + if (m_pECModel.IsActionStopped()) + return; + + if(true/*!m_bNoFx*/) + UpdateEvent((uint)m_dwTimeSpan, 0); + ACTION_INFO pCur = null, pNext = null; + if (m_arrActLoopNum.Count == 0) + return; + if (m_nCurActIndex == -1) // first + pNext = m_arrActLoopNum[0].GetActInfo(); + else if (m_nCurActIndex == m_arrActLoopNum.Count - 1) // last + pCur = m_arrActLoopNum[m_nCurActIndex].GetActInfo(); + else // in middle { - RemoveAllActiveFx(); + pCur = m_arrActLoopNum[m_nCurActIndex].GetActInfo(); + pNext = m_arrActLoopNum[m_nCurActIndex + 1].GetActInfo(); + } + + if (pCur != null) + { + if (m_dwTimeSpan >= pCur.m_dwEndTime) // µ±Ç°¶¯×÷³¬Ê± + { + m_nCurActLoop++; + + if (!IsCurActFinished(pCur)) //ûÓнáÊøÔòÖØÐÂÑ­»· + { + LoopCurAct(pCur, dwDeltaTime); + return; + } + else if (pNext == null) // ûÓÐÏÂÒ»¸ö¶¯×÷ + { + LoopNext(dwDeltaTime); + return; + } + // else ÏÂÒ»¸ö¶¯×÷¿ªÊ¼ + } + else // ²¥·Åµ±Ç°¶¯×÷ + { + return; + } + } + + if (pNext != null) + { + if (m_dwTimeSpan >= pNext.m_dwStartTime) // ÏÂÒ»¸ö¶¯×÷ʱ¼äµ½´ï + { + m_nCurActLoop = 0; + m_nCurActIndex++; + EventBus.PublishChannel(m_pECModel.GetId(), new PlayActionEvent( pNext.m_strName, m_nTransTime, false, ActiveAttackEvent, pNext.CalcLoopNum()==-1 && pNext.GetTimeSpan() > 10)); + } } } + + public void UpdateEvent(uint dwActTime, uint dwEventTime) + { + if (m_bNoFx) + return; + ExpireActFx(); + foreach(var fx in m_ActFxArray) + { + fx.UpdateParam(m_pECModel, (int)dwActTime); + } + } + public void ExpireActFx() + { + for(int i = 0; i < m_ActFxArray.Count; i++) + { + FX_BINDING_BASE pFx = m_ActFxArray[i]; + + if (IsFxEnd(pFx, m_dwTimeSpan)) + { + m_ActFxArray.RemoveAt(i); + pFx.Stop(); + i--; + } + } + } + + static bool IsFxEnd(FX_BINDING_BASE pFx, int dwTimeSpan) + { + uint dwMax = 0xFFFFFFFF; + if (pFx.IsStop()) + return true; + + // if (pFx.GetInfo().m_dwTimeSpan == dwMax) + // return false; + return false; + //return dwTimeSpan >= pFx->GetInfo()->GetStartTime() + pFx->GetInfo()->GetTimeSpan(); + } + + bool IsCurActFinished(ACTION_INFO pCur) + { + return !pCur.IsInfinite() || m_nCurActLoop >= m_arrActLoopNum[m_nCurActIndex].GetLoopNum(); + } + public bool IsActionStopped() + { + return m_pAct.m_ActLst.Count == 0 || + (m_pAct.m_nLoops != -1 && m_nCurLoop >= m_pAct.m_nLoops); + } + void LoopCurAct(ACTION_INFO pCur, uint dwDeltaTime) + { + m_dwTimeSpan = (int)(pCur.m_dwStartTime + m_dwTimeSpan - pCur.m_dwEndTime); // °Ñʱ¼äÉè»ØÈ¥ + } + void LoopNext(uint dwDeltaTime) + { + m_nCurLoop++; + + if (!IsActionStopped()) + { + RemoveAllActiveFx(); + m_nCurActIndex = -1; + Play(m_nChannel, m_fWeight, m_nTransTime, m_dwEventMask, false, m_bAbsTrack, m_bNoFx); + } + } public void RemoveAllActiveFx() { - foreach(var fx in m_GFXObjects) + foreach(var fx in m_ActFxArray) { if(fx != null) { - GameObject.Destroy(fx); + fx.Stop(); } } - m_GFXObjects.Clear(); + m_ActFxArray.Clear(); } public void SetUserData(int dwUserData) { m_dwUserData = dwUserData; } public void SetTransTime(int nTransTime) { m_nTransTime = nTransTime; } @@ -797,6 +1032,7 @@ public class CECModel private const uint COMACT_FLAG_MODE_ONCE_IGNOREGFX = 2; private const uint COMACT_FLAG_MODE_ONCE_MULTIIGNOREGFX = 3; protected CECModelStaticData m_pMapModel = new CECModelStaticData(); + protected NamedAnimancerComponent m_pMapAnimancer; public SkeletonBuilder m_skeletonBuilder; private Dictionary m_hookCache = new Dictionary(); private Dictionary m_hangerPositionCache = new Dictionary(); @@ -853,6 +1089,10 @@ public class CECModel m_skeletonBuilder = builder; m_hookCache?.Clear(); // Invalidate cache on model change } + public void SetNamedAnimancerComponent(NamedAnimancerComponent animancer) + { + m_pMapAnimancer = animancer; + } public void SetCombinedAction(CombinedActionSO combinedAction) { //workaround: load data from combined action. C++ load from CMS file aka prefab. we already include those data inside the prefab @@ -868,6 +1108,10 @@ public class CECModel { return m_skeletonBuilder; } + public NamedAnimancerComponent GetNamedAnimancerComponent() + { + return m_pMapAnimancer; + } /// /// Initialize SkeletonBuilder reference - call this after model is loaded @@ -880,19 +1124,25 @@ public class CECModel if (m_transform != null) { m_skeletonBuilder = m_transform.GetComponentInChildren(true); + m_pMapAnimancer = m_transform.GetComponentInChildren(true); } - if (m_skeletonBuilder == null && m_transform != null) + if (m_skeletonBuilder == null && m_pMapAnimancer == null && m_transform != null) { // Fallback: search in parent hierarchy (for NPCs/Players) // 回退:在父层次结构中搜索(用于NPC/玩家) m_skeletonBuilder = m_transform.GetComponentInParent(); + m_pMapAnimancer = m_transform.GetComponentInParent(); } if (m_skeletonBuilder == null) { BMLogger.LogWarning($"[CECModel] SkeletonBuilder not found for {(m_transform != null ? m_transform.name : "null")}. Hooks will not be available."); } + else if (m_pMapAnimancer == null) + { + BMLogger.LogWarning($"[CECModel] NamedAnimancerComponent not found for {(m_transform != null ? m_transform.name : "null")}. Animancer will not be available."); + } else { // Clear cache when skeleton is set @@ -967,6 +1217,14 @@ public class CECModel } return hook; } + public AnimationClip GetAnimationClip(string animationName) + { + if (m_pMapAnimancer != null) + { + return m_pMapAnimancer.States.TryGet(animationName, out AnimancerState state) ? state.Clip : null; + } + return null; + } public float[] GetOuterData() { return m_pMapModel.m_OuterData; } /// @@ -1190,16 +1448,7 @@ public class CECModel pDynData.SetStopPrevAct(bForceStopPrevAct); pDynData.SetNoFxFlag(bNoFx); pDynData.SetSpeedWhenActStart(bResetSpeed); - pNode.m_QueuedActs.Add(pDynData); - - // EventBus.PublishChannelClass(m_nId, new QueueActionEvent(ref m_ChannelActs[nChannel], actionInfos[0].m_strName, null, false, attackEvent, nTransTime, bForceStopPrevAct, isLoop, node.m_Rank)); - // for(int i = 1; i < actionInfos.Count; i++) - // { - // EventBus.PublishChannelClass(m_nId, new QueueActionEvent(ref m_ChannelActs[nChannel], actionInfos[i].m_strName, null, false, attackEvent, nTransTime, false, isLoop, node.m_Rank)); - // actData.m_EventNames.Add(actionInfos[i].m_strName); - // } - //todo: should add sfx and gfx logic to channel act. currently we dont have logic for queue action. only available for play action. - return true; + pNode.m_QueuedActs.Add(pDynData);return true; } public bool ClearComActFlagAllRankNodes(bool bSignalCurrent) { @@ -1217,12 +1466,18 @@ public class CECModel returnValue = nodeCh0.m_pActive.GetUserData(); return returnValue; } + /// + /// ForNPCOnly + /// public bool QueueAction(CECNPC.INFO iNFO, string szActName, ref bool pNewActFlag, int nTransTime = 200, uint dwUserData = 0, bool bForceStopPrevAct = false, bool bCheckTailDup = false, bool bNoFx = false, bool bResetSpeed = false /*joslian*/, bool bResetActFlag = false, uint dwNewFlagMode = COMACT_FLAG_MODE_NONE) { QueueAction(iNFO, 0, szActName, ref bResetActFlag, nTransTime, dwUserData, bForceStopPrevAct, bCheckTailDup, bNoFx, bResetSpeed/*joslian*/, pNewActFlag, dwNewFlagMode); return true; } + /// + /// ForNPCOnly + /// public bool QueueAction(CECNPC.INFO iNFO, int nChannel, string szActName, ref bool pNewActFlag, int nTransTime, uint dwUserData/* 0 */, bool bForceStopPrevAct, bool bCheckTailDup, bool bNoFx, bool bResetSpeed /*joslian*/, bool bResetActFlag, uint dwNewFlagMode) { @@ -1233,6 +1488,16 @@ public class CECModel { //TODO: Implement StopChannelAction } + public bool IsActionStopped(int nChannel =0) + { + ChannelAct ca = m_ChannelActs[nChannel]; + foreach (var node in ca.m_RankNodes) + { + if (!node.m_pActive.IsActionStopped()) + return false; + } + return true; + } public struct QueueNPCActionEvent { public string AnimationName; @@ -1262,7 +1527,7 @@ public class CECModel if (node.m_dwFlagMode == COMACT_FLAG_MODE_ONCE_IGNOREGFX || node.m_dwFlagMode == COMACT_FLAG_MODE_ONCE_MULTIIGNOREGFX) bActFinished = node.m_pActive.IsActionStopped(); else - bActFinished = node.m_pActive.IsAllFinished(); + bActFinished = node.m_pActive.IsActionStopped(); if (bActFinished) { diff --git a/Assets/PerfectWorld/Scripts/NPC/CECNPC.cs b/Assets/PerfectWorld/Scripts/NPC/CECNPC.cs index 3d9c6710ce..8be3a58a10 100644 --- a/Assets/PerfectWorld/Scripts/NPC/CECNPC.cs +++ b/Assets/PerfectWorld/Scripts/NPC/CECNPC.cs @@ -10,6 +10,7 @@ using BrewMonster.Scripts.Chat; using System.Collections.Generic; using UnityEngine; using BrewMonster.Scripts.Skills; +using Animancer; public class CECNPC : CECObject { [SerializeField] protected INFO m_NPCInfo; @@ -1886,6 +1887,15 @@ public class CECNPC : CECObject m_pNPCCECModel.SetSkeletonBuilder(skeletonBuilder); m_pNPCCECModel.InitializeSkeletonBuilder(); } + NamedAnimancerComponent animancer = modelObject.GetComponent(); + if (animancer == null) + { + animancer = modelObject.GetComponentInChildren(true); + } + if (animancer != null) + { + m_pNPCCECModel.SetNamedAnimancerComponent(animancer); + } } } diff --git a/Assets/PerfectWorld/Scripts/Players/CECHostNavigatePlayer.cs b/Assets/PerfectWorld/Scripts/Players/CECHostNavigatePlayer.cs index 0c458f8dcb..09b7c9f058 100644 --- a/Assets/PerfectWorld/Scripts/Players/CECHostNavigatePlayer.cs +++ b/Assets/PerfectWorld/Scripts/Players/CECHostNavigatePlayer.cs @@ -4,6 +4,7 @@ using System.Threading.Tasks; using UnityEngine; using CSNetwork.GPDataType; using Cysharp.Threading.Tasks; +using Animancer; namespace BrewMonster.Scripts { // CECHostNavigatePlayer class - Basic implementation for navigation player // CECHostNavigatePlayer类 - 导航玩家的基本实现 @@ -192,9 +193,12 @@ namespace BrewMonster.Scripts m_pNavigateModel = new CECModel(); m_pNavigateModel.m_pPlayerModel = instance; SkeletonBuilder skeletonBuilder = instance.GetComponentInChildren(true); - if (skeletonBuilder != null) + NamedAnimancerComponent animancer = instance.GetComponent(); + + if (skeletonBuilder != null && animancer != null) { m_pNavigateModel.SetSkeletonBuilder(skeletonBuilder); + m_pNavigateModel.SetNamedAnimancerComponent(animancer); m_pNavigateModel.SetTransform(instance.transform); m_pNavigateModel.InitializeSkeletonBuilder(); } @@ -272,7 +276,6 @@ namespace BrewMonster.Scripts public override bool Tick(uint dwDeltatime) { base.Tick(dwDeltatime); - Debug.Log($"CECHostNavigatePlayer::Tick, m_bNavigateModelApplied: {m_bNavigateModelApplied}"); if (!m_bNavigateModelApplied /*&& IsAllResReady()*/) { ApplyNavigateModel(); @@ -345,7 +348,6 @@ namespace BrewMonster.Scripts } protected override void OnCloneSimpleProperty() { - base.OnCloneSimpleProperty(); // SetId on player model etc. // 调用基类:玩家模型 SetId 等 // m_pNavigateModel needs mount hook on cloned model; duplicate attach/release — do not load riding pet again here // m_pNavigateModel 需要挂接克隆模型挂点,避免重复释放,故不再加载骑宠 m_RidingPet.Reset(); }