From f5dba378135e4a83cb580dd57f8ed643359841aa Mon Sep 17 00:00:00 2001 From: Tran Hai Nam Date: Wed, 22 Apr 2026 15:58:28 +0700 Subject: [PATCH] Add loop behavior via code to player animation --- .../Scripts/Managers/EC_EPWork.cs | 20 +++- .../PerfectWorld/Scripts/Move/CECCounter.cs | 2 +- Assets/PerfectWorld/Scripts/Move/CECPlayer.cs | 28 ++--- Assets/PerfectWorld/Scripts/NPC/CECModel.cs | 10 +- .../Scripts/Players/EC_ElsePlayer.cs | 21 ++-- Assets/Scripts/PlayerVisual.cs | 101 ++++++++++++++---- 6 files changed, 128 insertions(+), 54 deletions(-) diff --git a/Assets/PerfectWorld/Scripts/Managers/EC_EPWork.cs b/Assets/PerfectWorld/Scripts/Managers/EC_EPWork.cs index ba62982bad..91c3fd84f9 100644 --- a/Assets/PerfectWorld/Scripts/Managers/EC_EPWork.cs +++ b/Assets/PerfectWorld/Scripts/Managers/EC_EPWork.cs @@ -158,7 +158,15 @@ namespace BrewMonster.Scripts return m_iType; } } - + public class CECEPWorkMove : CECEPWork + { + public CECEPWorkMove(CECEPWorkMan pWorkMan) : base(CECEPWork.EP_work_ID.WORK_MOVE, pWorkMan) + { + } + public override void Start() + { + } + } public class CECEPIdleWorkMatcher : CECEPWorkMatcher { int m_iType; @@ -354,7 +362,10 @@ namespace BrewMonster.Scripts new List() }; private int m_iCurWorkType; - + public int GetCurrentWorkType() + { + return m_iCurWorkType; + } public CECEPWorkMan(EC_ElsePlayer pElsePlayer) { m_pElsePlayer = pElsePlayer; @@ -792,12 +803,11 @@ namespace BrewMonster.Scripts if (!GetPlayer().m_FightCnt.IsFull()){ GetPlayer().m_FightCnt.IncCounter(dwDeltaTime); } - //todo: get model and check - // if (GetPlayer().GetPlayerModel() != null){ + if (GetPlayer().GetPlayerModel() != null){ if (CECPlayer.IsMoveStandAction(GetPlayer().GetLowerBodyAction())){ GetPlayer().PlayAction(GetStandAction(), false); } - // } + } } int GetStandAction() diff --git a/Assets/PerfectWorld/Scripts/Move/CECCounter.cs b/Assets/PerfectWorld/Scripts/Move/CECCounter.cs index 2821df2977..2dfd24dfcd 100644 --- a/Assets/PerfectWorld/Scripts/Move/CECCounter.cs +++ b/Assets/PerfectWorld/Scripts/Move/CECCounter.cs @@ -38,7 +38,7 @@ public class CECCounter public bool IncCounter(float dwCounter) { m_dwCounter += dwCounter; - return (m_dwCounter >= m_dwPeriod); + return (m_dwCounter >= m_dwPeriod) ? true : false; } // Decrease counter diff --git a/Assets/PerfectWorld/Scripts/Move/CECPlayer.cs b/Assets/PerfectWorld/Scripts/Move/CECPlayer.cs index 2a411697ab..434e3f2787 100644 --- a/Assets/PerfectWorld/Scripts/Move/CECPlayer.cs +++ b/Assets/PerfectWorld/Scripts/Move/CECPlayer.cs @@ -294,7 +294,7 @@ namespace BrewMonster protected GameObject GetDummyModel(int i) => (i!=(int)PLAYERMODEL_TYPE.PLAYERMODEL_MAJOR&&i<(int)PLAYERMODEL_TYPE.PLAYERMODEL_MAX) ? m_pModels[i]:null; protected GameObject GetMajorModel() => m_pModels[(int)PLAYERMODEL_TYPE.PLAYERMODEL_MAJOR]; protected GameObject GetPetModel() => m_pPetModel; - protected CECModel GetPlayerModel() => m_pPlayerCECModel; + public CECModel GetPlayerModel() => m_pPlayerCECModel; protected const int OBJECT_EXT_STATE_COUNT = 6; protected bool IsMajorModel(GameObject pModel) => GetMajorModel() != null && pModel == GetMajorModel(); public enum WeaponHangerPosition @@ -1176,11 +1176,11 @@ namespace BrewMonster } } - public bool PlayAction(int iAction, bool bRestart = true, int iTransTime = 200, bool bQueue = false) + public bool PlayAction(int iAction, bool bRestart = true, int iTransTime = 200, bool bQueue = false, bool isElsePlayer = false) { return PlayActionWithConfig(iAction, 0, bRestart, iTransTime, bQueue); } - public bool PlayAction(int iAction, int actionConfigID, bool bRestart = true, int iTransTime = 200, bool bQueue = false) + public bool PlayAction(int iAction, int actionConfigID, bool bRestart = true, int iTransTime = 200, bool bQueue = false, bool isElsePlayer = false) { return PlayActionWithConfig(iAction, actionConfigID, bRestart, iTransTime, bQueue); } @@ -1634,7 +1634,6 @@ namespace BrewMonster string szShapeName = string.Empty; GetShapeName(ref szShapeName); int weapon_type = GetShowingWeaponType(); - Debug.Log($"[THN]: PlayAttackAction weapon_type: {weapon_type}"); int nTime1 = 0, nTime2 = 0; int iAction = (int)PLAYER_ACTION_TYPE.ACT_ATTACK_1 + nRand; bool bHideFX = false;//!CECOptimize::Instance().GetGFX().CanShowAttack(GetCharacterID(), GetClassID()); @@ -1653,7 +1652,6 @@ namespace BrewMonster { // “起�? 动作(挥起) - Debug.Log($"[THN]: PlayAttackAction action with weapon type: {weapon_type} and weapon attached: {m_bWeaponAttached}"); szAct = EC_Utility.BuildActionName(action, weapon_type, "起"); int iTransTime = 200; //EventBus.PublishChannel(m_PlayerInfo.cid, new PlayActionEvent(szShapeName, szAct, iTransTime, true)); @@ -1723,8 +1721,7 @@ namespace BrewMonster PLAYER_ACTION stand_action = m_PlayerActions[(int)PLAYER_ACTION_TYPE.ACT_FIGHTSTAND]; szAct = EC_Utility.BuildActionName(stand_action, 0); int iTranstime = 300; - queueActionEvent.SetData(szShapeName, szAct, SetApplyDamage, false, attackEvent, iTranstime, false); - //EventBus.PublishChannelClass(m_PlayerInfo.cid, queueActionEvent); + BMLogger.LogError($"[THN]: PlayAttackAction QueueNonSkillActionWithName: {szAct}"); m_pActionController.QueueNonSkillActionWithName(iAction, szAct, iTranstime, false, false, true, false); /* QueueNonSkillActionWithName(ACT_FIGHTSTAND, szAct, 300, false, bHideFX, true); @@ -3883,20 +3880,23 @@ namespace BrewMonster public string AnimationName; public int ITransTime; public bool IsForceStopPrevious; + public bool IsLoop; public CECAttackEvent AttackEvent; - public PlayActionEvent(string shapeName, string animationName, int iTransTime, bool isForceStopPrevious = false, CECAttackEvent attackEvent = null) + public PlayActionEvent(string shapeName, string animationName, int iTransTime, bool isForceStopPrevious = false, CECAttackEvent attackEvent = null, bool isLoop = false) { this.AnimationName = shapeName + animationName; ITransTime = iTransTime; IsForceStopPrevious = isForceStopPrevious; AttackEvent = attackEvent; + IsLoop = isLoop; } - public PlayActionEvent(string animationName, int iTransTime, bool isForceStopPrevious = false, CECAttackEvent attackEvent = null) + public PlayActionEvent(string animationName, int iTransTime, bool isForceStopPrevious = false, CECAttackEvent attackEvent = null, bool isLoop = false) { this.AnimationName = animationName; ITransTime = iTransTime; IsForceStopPrevious = isForceStopPrevious; AttackEvent = attackEvent; + IsLoop = isLoop; } } public struct PLAYER_ACTION @@ -3912,8 +3912,9 @@ namespace BrewMonster public CECAttackEvent AttackEvent; public bool IsHitAnim; public bool IsForceStopPrevious; + public bool IsLoop; public QueueActionEvent(string animationName, Action setFlag, bool isHitAnim, - CECAttackEvent attackEvent, int iTransTime, bool isForceStopPrevious = false) + CECAttackEvent attackEvent, int iTransTime, bool isForceStopPrevious = false, bool isLoop = false) { this.AnimationName = animationName; SetFlag = setFlag; @@ -3921,10 +3922,11 @@ namespace BrewMonster AttackEvent = attackEvent; ITransTime = iTransTime; IsForceStopPrevious = isForceStopPrevious; + IsLoop = isLoop; } public void SetData(string shapeName, string animationName, Action setFlag, bool isHitAnim, - CECAttackEvent attackEvent, int iTransTime, bool isForceStopPrevious = false) + CECAttackEvent attackEvent, int iTransTime, bool isForceStopPrevious = false, bool isLoop = false) { this.AnimationName = shapeName + animationName; SetFlag = setFlag; @@ -3932,9 +3934,10 @@ namespace BrewMonster AttackEvent = attackEvent; ITransTime = iTransTime; IsForceStopPrevious = isForceStopPrevious; + IsLoop = isLoop; } public void SetData(string animationName, Action setFlag, bool isHitAnim, - CECAttackEvent attackEvent, int iTransTime, bool isForceStopPrevious = false) + CECAttackEvent attackEvent, int iTransTime, bool isForceStopPrevious = false, bool isLoop = false) { this.AnimationName = animationName; SetFlag = setFlag; @@ -3942,6 +3945,7 @@ namespace BrewMonster AttackEvent = attackEvent; ITransTime = iTransTime; IsForceStopPrevious = isForceStopPrevious; + IsLoop = isLoop; } } public enum PLAYER_ACTION_TYPE diff --git a/Assets/PerfectWorld/Scripts/NPC/CECModel.cs b/Assets/PerfectWorld/Scripts/NPC/CECModel.cs index a968392d5e..307e0f1225 100644 --- a/Assets/PerfectWorld/Scripts/NPC/CECModel.cs +++ b/Assets/PerfectWorld/Scripts/NPC/CECModel.cs @@ -671,10 +671,10 @@ public class CECModel } var actionInfos = combinedAction.m_ActLst; var isLoop = combinedAction.m_nLoops == 1; - EventBus.PublishChannel(m_nId, new PlayActionEvent(actionInfos[0].m_strName, nTransTime, bForceStop, attackEvent)); + EventBus.PublishChannel(m_nId, new PlayActionEvent(actionInfos[0].m_strName, nTransTime, bForceStop, attackEvent, isLoop)); for(int i = 1; i < actionInfos.Count; i++) { - EventBus.PublishChannelClass(m_nId, new QueueActionEvent(actionInfos[i].m_strName, null, false, attackEvent, nTransTime, false)); + EventBus.PublishChannelClass(m_nId, new QueueActionEvent(actionInfos[i].m_strName, null, false, attackEvent, nTransTime, false, isLoop)); } var eventInfoList = combinedAction.m_EventInfoLst; if(eventInfoList != null && eventInfoList.Count > 0) @@ -688,7 +688,7 @@ public class CECModel { string soundpath = AFile.NormalizePath(sfx.m_strFilePaths[0]); soundpath = soundpath.ToLower(); - SFXManager.Instance.PlaySkillSfxAtPointAsync(soundpath, Vector3.zero).Forget(); + SFXManager.Instance.PlaySkillSfxAtPointAsync(soundpath, Vector3.zero).Forget(); } } @@ -717,10 +717,10 @@ public class CECModel } var actionInfos = combinedAction.m_ActLst; var isLoop = combinedAction.m_nLoops == 1; - EventBus.PublishChannelClass(m_nId, new QueueActionEvent(actionInfos[0].m_strName, null, false, attackEvent, nTransTime, bForceStopPrevAct)); + EventBus.PublishChannelClass(m_nId, new QueueActionEvent(actionInfos[0].m_strName, null, false, attackEvent, nTransTime, bForceStopPrevAct, isLoop)); for(int i = 1; i < actionInfos.Count; i++) { - EventBus.PublishChannelClass(m_nId, new QueueActionEvent(actionInfos[i].m_strName, null, false, attackEvent, nTransTime, false)); + EventBus.PublishChannelClass(m_nId, new QueueActionEvent(actionInfos[i].m_strName, null, false, attackEvent, nTransTime, false, isLoop)); } return true; } diff --git a/Assets/PerfectWorld/Scripts/Players/EC_ElsePlayer.cs b/Assets/PerfectWorld/Scripts/Players/EC_ElsePlayer.cs index 6b697df049..abe06892b0 100644 --- a/Assets/PerfectWorld/Scripts/Players/EC_ElsePlayer.cs +++ b/Assets/PerfectWorld/Scripts/Players/EC_ElsePlayer.cs @@ -154,20 +154,23 @@ namespace BrewMonster //if (!m_pPlayerModel) return; //if (!IsValidAction(iCurAction)) return; // PlayAction(GetMoveStandAction(true), true, 1, false); - + if (m_pEPWorkMan.GetCurrentWorkType() < CECEPWorkMan.Work_type.WT_NORMAL || + !m_pEPWorkMan.FindWork(CECEPWorkMan.Work_type.WT_NORMAL, CECEPWork.EP_work_ID.WORK_MOVE)){ + m_pEPWorkMan.StartNormalWork(new CECEPWorkMove(m_pEPWorkMan)); + } // Play action if (IsValidAction(m_iCurAction)) { if (!IsPlayingAction((int)PLAYER_ACTION_TYPE.ACT_TRICK_JUMP) && !IsPlayingAction((int)PLAYER_ACTION_TYPE.ACT_TRICK_RUN)) { if (m_iMoveMode == Move_Mode.MOVE_JUMP || m_iMoveMode == Move_Mode.MOVE_SLIDE) - PlayAction((int)PLAYER_ACTION_TYPE.ACT_JUMP_LOOP, false); + PlayAction((int)PLAYER_ACTION_TYPE.ACT_JUMP_LOOP, false, 200, false, true); else - PlayAction(GetMoveStandAction(true), false); + PlayAction(GetMoveStandAction(true), false, 200, false, true); } - } + } else - PlayAction(GetMoveStandAction(true), true, 1, false); + PlayAction(GetMoveStandAction(true), true, 200, false, true); } public bool MovingTo(float dwDeltaTime) @@ -204,7 +207,7 @@ namespace BrewMonster if (Math.Abs(fMoveDelta - 0f) <= float.Epsilon || fMoveDelta >= fDist) //!fMoveDelta <=> (Math.Abs(fMoveDelta - 0f) <= float.Epsilon) Compare with 0 { SetPos(m_vServerPos); - PlayAction(GetMoveStandAction(false), true, 1, false); + PlayAction(GetMoveStandAction(false), true, 200, false); bRet = true; } else @@ -264,7 +267,7 @@ namespace BrewMonster { m_bStopMove = false; SetPos(Cmd.dest); - PlayAction(GetMoveStandAction(true), true, 1, false); + PlayAction(GetMoveStandAction(true), true, 200, false); return; } int iMoveMode = Cmd.move_mode; @@ -293,7 +296,7 @@ namespace BrewMonster case GPMoveMode.GP_MOVE_JUMP: m_iMoveMode = (int)MoveMode.MOVE_JUMP; m_cdr.bTraceGround = false; break; } - PlayAction(GetMoveStandAction(true), true, 1, false); + PlayAction(GetMoveStandAction(true), true, 200, false); } public float GetDistToHost() { return m_fDistToHost; } @@ -419,7 +422,7 @@ namespace BrewMonster m_fDistToHost = CalcDist(pHost.GetPos(), true); m_fDistToHostH = CalcDist(pHost.GetPos(), false); } - m_pEPWorkMan?.Tick(Time.deltaTime); + m_pEPWorkMan?.Tick(Time.deltaTime * 1000); } public void SetPos(A3DVECTOR3 vPos) diff --git a/Assets/Scripts/PlayerVisual.cs b/Assets/Scripts/PlayerVisual.cs index 90a48e69c2..57940f9f58 100644 --- a/Assets/Scripts/PlayerVisual.cs +++ b/Assets/Scripts/PlayerVisual.cs @@ -11,7 +11,9 @@ namespace BrewMonster { public string AnimationName; public bool IsForceStopPrevious; + public int ITransTime; public CECAttackEvent AttackEvent; + public bool IsLoop; } public class PlayerVisual : MonoBehaviour { @@ -31,10 +33,15 @@ namespace BrewMonster private const float FadeTime = 100; private const FadeMode FadeMode = Animancer.FadeMode.FixedDuration; QueueActionEvent queueActionEvent; - + private string previousAnimationName; private void PlayActionEventHandler(PlayActionEvent @event) { - //when this trigger, clear all the animation in the queue which in the same layer of animancer + //prevent enqueue the same loop animation + bool loopcheck = @event.IsLoop == true && previousAnimationName == @event.AnimationName; + if(loopcheck) + { + return; + } if (_animationQueue.Count > 0) { _animationQueue.Enqueue(new AnimationQueue @@ -46,7 +53,8 @@ namespace BrewMonster _animationList = _animationQueue.Select(q => q.AnimationName).ToList(); return; } - InternalPlayAnimation(@event.AnimationName, @event.ITransTime); + previousAnimationName = @event.AnimationName; + InternalPlayAnimation(@event.AnimationName, @event.ITransTime, FadeMode, @event.IsLoop); ApplyAttackSignalOnAnimationEnd(@event.AttackEvent); } public void InitPlayerEventDoneHandler() @@ -133,15 +141,24 @@ namespace BrewMonster } public bool EnqueueAnimation(QueueActionEvent @event) { - if (namedAnimancer == null) return false; + if (namedAnimancer == null) + { + return false; + } + if(previousAnimationName == @event.AnimationName) + { + return false; + } + previousAnimationName = @event.AnimationName; _animationQueue.Enqueue(new AnimationQueue { AnimationName = @event.AnimationName, IsForceStopPrevious = @event.IsForceStopPrevious, - AttackEvent = null + ITransTime = @event.ITransTime, + AttackEvent = @event.AttackEvent, + IsLoop = @event.IsLoop }); _animationList = _animationQueue.Select(q => q.AnimationName).ToList(); - if (!isHit) { queueActionEvent = @event; @@ -149,23 +166,51 @@ namespace BrewMonster } return true; } + /// + /// This function is used to enqueue an animation for looping when the animancer is not set to looping + /// + /// + /// + private bool EnqueueAnimationForLooping(string animationName) + { + if (namedAnimancer == null) + { + return false; + } + //prevent call if these is a animation already in the queue + if(_animationQueue.Count > 0) + { + return false; + } + _animationQueue.Enqueue(new AnimationQueue + { + AnimationName = animationName, + IsForceStopPrevious = false, + AttackEvent = null, + IsLoop = true + }); + _animationList = _animationQueue.Select(q => q.AnimationName).ToList(); + return true; + } private void PlayNext() { + if (_animationQueue.Count == 0) { return; } + else + { + string animationQueueString = ""; + foreach(var animation in _animationQueue) + { + animationQueueString += animation.AnimationName + ", "; + } + } - // if (_currentState == null) - // { - // _animationQueue.Dequeue(); - // return; - // } - //peek next if IsForceStopPrevious is true, force end if (_animationQueue.Peek().IsForceStopPrevious) { - Debug.Log($" InternalPlayAnimation PlayNext: Force Stop Previous"); - _currentState.Stop(); + _currentState?.Stop(); _currentState = null; } if (_currentState != null && _currentState.NormalizedTime < 1f) return; @@ -175,7 +220,8 @@ namespace BrewMonster } var animationQueue = _animationQueue.Dequeue(); _animationList = _animationQueue.Select(q => q.AnimationName).ToList(); - InternalPlayAnimation(animationQueue.AnimationName); + previousAnimationName = animationQueue.AnimationName; + InternalPlayAnimation(animationQueue.AnimationName, animationQueue.ITransTime, FadeMode, animationQueue.IsLoop); ApplyAttackSignalOnAnimationEnd(animationQueue.AttackEvent); } private void ApplyAttackSignalOnAnimationEnd(CECAttackEvent attackEvent) @@ -192,7 +238,10 @@ namespace BrewMonster } void ApplyDamage() { - if (queueActionEvent == null) return; + if (queueActionEvent == null) + { + return; + } isHit = false; queueActionEvent.SetFlag(true, queueActionEvent.AttackEvent); queueActionEvent = null; @@ -203,7 +252,8 @@ namespace BrewMonster } public bool IsAnimationExist(string animationName) { - return namedAnimancer.States.TryGet("ActionName", out var existingState) ? true : false; + var exists = namedAnimancer.States.TryGet("ActionName", out var existingState) ? true : false; + return exists; } private string _currentAnimationName; @@ -213,19 +263,26 @@ namespace BrewMonster /// /// /// - private void InternalPlayAnimation(string animationName, float duration = FadeTime, FadeMode fadeMode = FadeMode) + private void InternalPlayAnimation(string animationName, float duration = FadeTime, FadeMode fadeMode = FadeMode, bool isLoop = false) { - + if (namedAnimancer == null) + { + return; + } bool isState = namedAnimancer.States.TryGet(animationName, out var existingState) ? true : false; if (isState) { _currentState = namedAnimancer.TryPlay(animationName, duration / 1000, fadeMode); _currentAnimationName = animationName; - //Debug.Log($"InternalPlayAnimation: removeShapeName 1 TriggerName={removeShapeName}"); + //if the animation is looping and the current state is not looping, play the animation again + if(isLoop == true && _currentState.IsLooping == false) + { + _currentState.Time = 0; + _currentState.Events.OnEnd = () => EnqueueAnimationForLooping(animationName); + } return; } - - BMLogger.LogError($"Null name animation: {animationName}"); + //BMLogger.LogError($"Null name animation: {animationName}"); } ///