Add channing gfx delay base on SO file
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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,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();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user