Add channing gfx delay base on SO file

This commit is contained in:
Tran Hai Nam
2026-05-13 14:25:45 +07:00
parent b4b8ba31da
commit d5ba997382
5 changed files with 384 additions and 98 deletions
@@ -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]
@@ -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<NamedAnimancerComponent>());
m_pPlayerCECModel.SetTransform(m_pPlayerModel.transform);
if(combinedAction != null)
{
@@ -787,6 +788,7 @@ namespace BrewMonster
return;
}
m_pPlayerCECModel.SetSkeletonBuilder(changeModel.GetComponentInChildren<SkeletonBuilder>());
m_pPlayerCECModel.SetNamedAnimancerComponent(changeModel.GetComponentInChildren<NamedAnimancerComponent>());
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<NamedAnimancerComponent>());
m_pPlayerCECModel.SetTransform(m_pPlayerModel.transform);
// Initialize skeleton builder (ensures hooks are available)
+358 -93
View File
@@ -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;
}
}
/// <summary>True when this GFX has finished and may be removed from <see cref="A3DCombActDynData.m_ActFxArray"/>. / 特效已结束,可从绑定列表移除时为 true</summary>
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<ParticleSystem>();
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<ACTIONDYN_DATA> 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;
/// <summary>Elapsed time (ms) along the combined action timeline; drives FX event triggers. / 组合动作时间轴已播放毫秒数,用于触发特效事件</summary>
public int GetComActElapsedMs() { return m_dwTimeSpan; }
public List<string> m_ActionNames = new List<string>();
public List<string> m_SFXNames = new List<string>();
public List<GameObject> m_GFXObjects = new List<GameObject>();
public bool IsActionStopped() { return m_ActionNames.Count == 0; }
public List<FX_BINDING_BASE> m_ActFxArray = new List<FX_BINDING_BASE>();
/// <summary>True when there is no attack or damage has been applied. / 无攻击事件或已造成伤害则为 true</summary>
public bool IsAllEventFinished() { return (ActiveAttackEvent == null || ActiveAttackEvent.m_bDoDamaged); }
/// <summary>Animations finished and attack-event phase finished. / 动画与攻击事件阶段均结束</summary>
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<ACTIONDYN_DATA>();
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();
}
/// <param name="triggerVisualAndFx">
@@ -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();
/// <summary>
/// Notify visual layer and fire combined-action audio using data on this instance.
/// / 通知表现层并根据本实例数据触发组合动作音效。
/// </summary>
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);
}
}
/// <summary>
/// Sound events embedded in combined action; skip gfx paths. / 组合动作内嵌音效事件;跳过 gfx 路径。
/// </summary>
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<ParticleSystem>();
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<ParticleSystem>();
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<string>();
@@ -304,6 +386,49 @@ public class A3DCombActDynData
}
}
}
/// <summary>
/// Set each <see cref="ACTION_INFO"/> span from loaded <see cref="AnimationClip"/> duration (milliseconds), then refresh <see cref="m_dwDynComActSpan"/>.
/// / 根据已加载 AnimationClip 时长(毫秒)设置各段 span,并更新组合总时长 m_dwDynComActSpan。
/// </summary>
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<string, Transform> m_hookCache = new Dictionary<string, Transform>();
private Dictionary<string, Transform> m_hangerPositionCache = new Dictionary<string, Transform>();
@@ -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;
}
/// <summary>
/// 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<SkeletonBuilder>(true);
m_pMapAnimancer = m_transform.GetComponentInChildren<NamedAnimancerComponent>(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<SkeletonBuilder>();
m_pMapAnimancer = m_transform.GetComponentInParent<NamedAnimancerComponent>();
}
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; }
/// <summary>
@@ -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;
}
/// <summary>
/// ForNPCOnly
/// </summary>
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;
}
/// <summary>
/// ForNPCOnly
/// </summary>
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)
{
+10
View File
@@ -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<NamedAnimancerComponent>();
if (animancer == null)
{
animancer = modelObject.GetComponentInChildren<NamedAnimancerComponent>(true);
}
if (animancer != null)
{
m_pNPCCECModel.SetNamedAnimancerComponent(animancer);
}
}
}
@@ -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<SkeletonBuilder>(true);
if (skeletonBuilder != null)
NamedAnimancerComponent animancer = instance.GetComponent<NamedAnimancerComponent>();
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();
}