using System; using System.Collections.Generic; using System.Linq; using System.Text; using UnityEngine; using static Unity.Cinemachine.CinemachineFreeLookModifier; namespace BrewMonster { public enum GfxHitPos { enumHitCenter, enumHitBottom } public enum GfxSkillEventState { enumWait, enumFlying, enumHit, enumFinished } // TODO: remove singleton later public class A3DSkillGfxMan { static int s_debugNextEventId = 1; public static A3DSkillGfxMan _instance; public static A3DSkillGfxMan Instance { get { if (_instance == null) { _instance = new A3DSkillGfxMan(); } return _instance; } set { _instance = value; } } public bool AddSkillGfxEvent( A3DSkillGfxComposer pComposer, long nHostID, long nTargetID, string szFlyGfx, string szHitGfx, uint dwFlyTimeSpan, bool bTraceTarget, GfxMoveMode FlyMode, int nFlyGfxCount, uint dwInterval, GFX_SKILL_PARAM param, uint dwModifier, bool bOnlyOneHit, bool bFadeOut, bool bIsGoblinSkill, bool bReverse ) { bool bRet = true, bCluster; uint dwDelayTime; if (nFlyGfxCount == 1) { dwDelayTime = dwInterval; bCluster = false; } else { dwDelayTime = 0; bCluster = true; } #region agent log #if UNITY_EDITOR || DEVELOPMENT_BUILD DebugSessionLog.Write("A3DSkillGfxMan.AddSkillGfxEvent", "add_skill_gfx_event", "C", new DebugSessionPayload { hostId = (int)nHostID, targetId = (int)nTargetID, flyClusterCount = nFlyGfxCount }); #endif #endregion for (int i = 0; i < nFlyGfxCount; i++) { string value = bOnlyOneHit && i != nFlyGfxCount - 1 ? "" : szHitGfx; if (!AddOneSkillGfxEvent( pComposer, nHostID, nTargetID, szFlyGfx, FlyMode, dwDelayTime, dwFlyTimeSpan, value, param, bTraceTarget, dwModifier, bCluster, bFadeOut, bIsGoblinSkill, bReverse )) { bRet = false; } dwDelayTime += dwInterval; } return bRet; } public virtual void Dispose() { Instance = null; } public bool AddOneSkillGfxEvent( A3DSkillGfxComposer pComposer, long nHostID, long nTargetID, string szFlyGfx, GfxMoveMode mode, uint dwDelayTime, uint dwFlyTimeSpan, string szHitGfx, GFX_SKILL_PARAM param, bool bTraceTarget, uint dwModifier, bool bCluster, bool bFadeOut, bool bIsGoblinSkill, bool bReverse) { // Validate host ID // 验证施法者ID if (nHostID == 0) { BMLogger.LogError($"[SKILL_GFX_DEBUG] AddOneSkillGfxEvent: WARNING - Invalid host ID (0), skipping event creation"); return false; } // Validate target ID - allow 0 for area skills, but warn about suspiciously large negative values // 验证目标ID - 允许0用于区域技能,但对可疑的大负值发出警告 A3DSkillGfxEvent pEvent = SkillGfxMan.InstanceSub.GetEmptyEvent(mode); pEvent.m_debugEventId = s_debugNextEventId++; #region agent log #if UNITY_EDITOR || DEVELOPMENT_BUILD DebugSessionLog.Write("A3DSkillGfxMan.AddOneSkillGfxEvent", "add_gfx_event", "C", new DebugSessionPayload { eventId = pEvent.m_debugEventId, hostId = (int)nHostID, targetId = (int)nTargetID, flyGfx = szFlyGfx ?? "" }); #endif #endregion pEvent.SetComposer(pComposer); pEvent.SetHostID(nHostID); pEvent.SetTargetID(nTargetID); pEvent.SetFlyTimeSpan(dwFlyTimeSpan); pEvent.SetDelay(dwDelayTime); pEvent.SetReverse(bReverse); pEvent.SetParam(param); pEvent.SetTraceTarget(bTraceTarget); pEvent.SetModifier(dwModifier); pEvent.SetIsCluster(bCluster); pEvent.SetFadeOut(bFadeOut); pEvent.SetGoblinSkill(bIsGoblinSkill); pEvent.SetShowFlyGfx(!string.IsNullOrEmpty(szFlyGfx)); pEvent.SetShowHitGfx(!string.IsNullOrEmpty(szHitGfx)); ECMODEL_GFX_PROPERTY Prop = new ECMODEL_GFX_PROPERTY(); if (GetPropertyById(nHostID, ref Prop)) { pEvent.SetGfxUseLod(Prop.bGfxUseLod); pEvent.SetDisableCamShake(Prop.bGfxDisableCamShake); pEvent.SetHostModelCreatedByGfx(Prop.bHostECMCreatedByGfx); } // NOTE: In Unity, GFX are Particle Systems — scaling is handled by the particle system itself, // not by code. The C++ pGfx.SetScale() calls are not needed. // 注意:在Unity中,GFX是粒子系统 — 缩放由粒子系统自身处理,不需要代码设置。 // Fly GFX instantiation is handled by CECSkillGfxEvent.SpawnFlyGfx() // Hit GFX instantiation is handled by CECSkillGfxEvent.SpawnHitGfx() #if !_SKILLGFXCOMPOSER pEvent.Tick(0); #endif PushEvent(pEvent); return true; } public virtual bool GetPropertyById(long nId, ref ECMODEL_GFX_PROPERTY pProperty) => false; void PushEvent(A3DSkillGfxEvent pEvent) { SkillGfxMan.InstanceSub.m_EventLst.AddLast((CECSkillGfxEvent)pEvent); } } public class A3DSkillGfxEvent { protected A3DSkillGfxComposer m_pComposer; protected CGfxMoveBase m_pMoveMethod; //protected A3DGFXEx m_pFlyGfx; // 飞行特效 / Fly effect //protected A3DGFXEx m_pHitGfx; // 命中特效 / Hit effect protected uint m_dwFlyTimeSpan; // 飞行时间 / Flight time protected uint m_dwCurSpan; protected uint m_dwDelayTime; protected bool m_bTraceTarget; protected bool m_bFadeOut; protected long m_nHostID; protected long m_nTargetID; protected uint m_dwModifier; protected bool m_bIsGoblinSkill; protected bool m_bShowFlyGfx; protected bool m_bShowHitGfx; protected Vector3 m_vHostPos; protected Vector3 m_vTargetPos; protected Vector3 m_vTargetDir; protected Vector3 m_vTargetUp; protected bool m_bHostExist; protected bool m_bTargetExist; protected bool m_bHitGfxInfinite; protected bool m_bTargetDirAndUpExist; protected bool m_bGfxUseLod; protected bool m_bGfxDisableCamShake; protected bool m_bHostECMCreatedByGfx; protected GfxSkillEventState m_enumState; public int m_debugEventId; public A3DSkillGfxEvent(GfxMoveMode mode) { m_pComposer = null; /* m_pFlyGfx = null; m_pHitGfx = null;*/ m_nHostID = 0; m_nTargetID = 0; m_dwModifier = 0; m_dwFlyTimeSpan = 0; m_dwCurSpan = 0; m_enumState = GfxSkillEventState.enumWait; m_bHitGfxInfinite = false; m_bIsGoblinSkill = false; m_bShowFlyGfx = true; m_bShowHitGfx = true; m_bTargetDirAndUpExist = false; m_bGfxUseLod = true; m_bGfxDisableCamShake = false; m_bHostECMCreatedByGfx = false; m_pMoveMethod = CGfxMoveBase.CreateMoveMethod(mode); } ~A3DSkillGfxEvent() { //ReleaseGfx(); // Note: m_pMoveMethod will be garbage collected } // Inline functions /* protected void ReleaseFlyGfx() { if (m_pFlyGfx != null) { if (m_bFadeOut) AfxGetGFXExMan().QueueFadeOutGfx(m_pFlyGfx, 1000); else { m_pFlyGfx.Release(); // In C#, we don't need to manually delete } m_pFlyGfx = null; } }*/ /* protected void ReleaseHitGfx() { if (m_pHitGfx != null) { AfxGetGFXExMan().CacheReleasedGfx(m_pHitGfx); m_pHitGfx = null; } }*/ /* protected void ReleaseGfx() { ReleaseFlyGfx(); ReleaseHitGfx(); }*/ // Virtual functions protected virtual void HitTarget(Vector3 vTarget) { m_enumState = GfxSkillEventState.enumHit; //ReleaseFlyGfx(); if (false /*m_pHitGfx != null*/) { //m_bHitGfxInfinite = m_pHitGfx.IsInfinite(); Matrix4x4 matTran; // now try to make the hit gfx face to the attacker if (m_bHostExist) { Vector3 vDir = vTarget - m_vHostPos; vDir.y = 0; if (vDir.magnitude < 1e-3f) vDir = new Vector3(0, 0, 1.0f); else vDir.Normalize(); matTran = _build_matrix(vDir, vTarget); } else { matTran = Matrix4x4.identity; matTran.SetColumn(3, new Vector4(vTarget.x, vTarget.y, vTarget.z, 1)); } /* m_pHitGfx.SetParentTM(matTran); m_pHitGfx.Start(true); m_pHitGfx.TickAnimation(0);*/ } } // Public inline functions public void SetGfxUseLod(bool b) { m_bGfxUseLod = b; } public bool GetGfxUseLod() { return m_bGfxUseLod; } public void SetDisableCamShake(bool b) { m_bGfxDisableCamShake = b; } public bool GetDisableCamShake() { return m_bGfxDisableCamShake; } public void SetHostModelCreatedByGfx(bool b) { m_bHostECMCreatedByGfx = b; } public bool GetHostModelCreatedByGfx() { return m_bHostECMCreatedByGfx; } public void SetComposer(A3DSkillGfxComposer pComposer) { m_pComposer = pComposer; } public CGfxMoveBase GetMoveMethod() { return m_pMoveMethod; } public GfxMoveMode GetMode() { return m_pMoveMethod.GetMode(); } public GfxHitPos GetHitPos() { return m_pMoveMethod.GetHitPos(); } /* public A3DGFXEx GetFlyGfx() { return m_pFlyGfx; } public A3DGFXEx GetHitGfx() { return m_pHitGfx; }*/ public void SetFlyTimeSpan(uint dwSpan) { m_dwFlyTimeSpan = dwSpan; } public void SetDelay(uint dwDelay) { m_dwDelayTime = dwDelay; } public void SetReverse(bool bReverse) { m_pMoveMethod.SetReverse(bReverse); } public void SetParam(GFX_SKILL_PARAM param) { m_pMoveMethod.SetParam(param); } public void SetIsCluster(bool bCluster) { m_pMoveMethod.SetIsCluster(bCluster); } public void SetTraceTarget(bool bTrace) { m_bTraceTarget = bTrace; } public void SetFadeOut(bool bFadeOut) { m_bFadeOut = bFadeOut; } public bool IsFinished() { return m_enumState == GfxSkillEventState.enumFinished; } public long GetHostID() { return m_nHostID; } public void SetHostID(long nID) { m_nHostID = nID; } public long GetTargetID() { return m_nTargetID; } public void SetTargetID(long nID) { m_nTargetID = nID; } public void SetHostPos(Vector3 vPos) { m_vHostPos = vPos; } public void SetTargetPos(Vector3 vPos) { m_vTargetPos = vPos; } public void SetHostExist(bool bExist) { m_bHostExist = bExist; } public void SetTargetExist(bool bExist) { m_bTargetExist = bExist; } public void SetModifier(uint dwModifier) { m_dwModifier = dwModifier; } public void SetGoblinSkill(bool bGoblinSkill) { m_bIsGoblinSkill = bGoblinSkill; } public bool GetGoblinSkill() { return m_bIsGoblinSkill; } public void SetShowFlyGfx(bool b) { m_bShowFlyGfx = b; } public void SetShowHitGfx(bool b) { m_bShowHitGfx = b; } public void Resume() { //ReleaseGfx(); m_enumState = GfxSkillEventState.enumWait; m_dwCurSpan = 0; m_bShowFlyGfx = true; m_bShowHitGfx = true; } // Virtual functions /* public virtual A3DGFXEx LoadFlyGfx(A3DDevice pDev, string szPath) { return AfxGetGFXExMan().LoadGfx(pDev, szPath); } public virtual A3DGFXEx LoadHitGfx(A3DDevice pDev, string szPath) { return AfxGetGFXExMan().LoadGfx(pDev, szPath); } public virtual void SetFlyGfx(A3DGFXEx pFlyGfx) { m_pFlyGfx = pFlyGfx; } public virtual void SetHitGfx(A3DGFXEx pHitGfx) { m_pHitGfx = pHitGfx; }*/ public virtual void Tick(uint dwDeltaTime) { m_dwCurSpan += dwDeltaTime; if (m_enumState == GfxSkillEventState.enumFinished) return; // 结束 / Finished else if (m_enumState == GfxSkillEventState.enumHit) // 命中 / Hit { // If m_bTraceTarget is true, stay in Hit state to allow derived class to update hit GFX position each frame // This matches C++ logic: hit GFX follows target when m_bTraceTarget is true // 如果m_bTraceTarget为true,保持在Hit状态以允许派生类每帧更新命中特效位置 // 这与C++逻辑匹配:当m_bTraceTarget为true时,命中特效跟随目标 if (!m_bTraceTarget) { // In Unity, hit GFX is auto-destroyed via Destroy(obj, 3f) in CECSkillGfxEvent. // Transition to Finished immediately — the hit GFX cleanup is handled by Unity's timer. // 在Unity中,命中特效通过Destroy(obj, 3f)自动销毁。立即转为Finished状态。 m_enumState = GfxSkillEventState.enumFinished; } // If m_bTraceTarget is true, derived class (CECSkillGfxEvent) will handle position updates // and transition to Finished when hit GFX lifetime expires // 如果m_bTraceTarget为true,派生类(CECSkillGfxEvent)将处理位置更新 // 并在命中特效生命周期到期时转为Finished状态 } else if (m_enumState == GfxSkillEventState.enumWait) { if (m_dwCurSpan < m_dwDelayTime) return; // Check host existence before transitioning to Flying // 在转换到Flying之前检查施法者是否存在 if (!m_bHostExist) { m_enumState = GfxSkillEventState.enumFinished; return; } // For skills that require a target, check target existence before starting flight // For area skills or skills without specific targets, allow flight even if target doesn't exist // 对于需要目标的技能,在开始飞行前检查目标是否存在 // 对于区域技能或没有特定目标的技能,即使目标不存在也允许飞行 if (!m_bTargetExist && m_nTargetID != 0) { // Target is required but doesn't exist - finish the event // 需要目标但目标不存在 - 结束事件 m_enumState = GfxSkillEventState.enumFinished; return; } // Transition to Flying state // 转换到飞行状态 m_enumState = GfxSkillEventState.enumFlying; m_pMoveMethod.SetMaxFlyTime(m_dwFlyTimeSpan); // Use target position if available, otherwise use host position (for area skills) // 如果目标位置可用则使用,否则使用施法者位置(用于区域技能) Vector3 targetPos = m_bTargetExist ? m_vTargetPos : m_vHostPos; m_pMoveMethod.StartMove(m_vHostPos, targetPos); // Fly GFX spawning is handled by CECSkillGfxEvent.Tick() when it detects Wait→Flying transition // 飞行特效的生成由CECSkillGfxEvent.Tick()在检测到Wait→Flying转换时处理 } else if (m_dwCurSpan > m_dwFlyTimeSpan) // 飞行超时 / Flight timeout { if (!m_bTargetExist && m_nTargetID != 0) m_enumState = GfxSkillEventState.enumFinished; else { Vector3 hitPos = m_bTargetExist ? GetTargetCenter() : m_pMoveMethod.GetPos(); HitTarget(hitPos); } } else // enumFlying state / 飞行状态 { if (m_pMoveMethod.TickMove(dwDeltaTime, m_vHostPos, m_vTargetPos)) // 目标被命中 / Target hit { // Only call GetTargetCenter if target exists and is not destroyed // 仅在目标存在且未销毁时调用GetTargetCenter if (m_bTargetExist && m_nTargetID != 0) { HitTarget(GetTargetCenter()); } else { // Target destroyed, hit at last known position or current position // 目标已销毁,在最后已知位置或当前位置命中 HitTarget(m_bTargetExist ? m_vTargetPos : m_pMoveMethod.GetPos()); } } // Fly GFX transform update is handled by CECSkillGfxEvent.Tick() // 飞行特效的变换更新由CECSkillGfxEvent.Tick()处理 } } /* public virtual void Render() { if (m_pFlyGfx != null) AfxGetGFXExMan().RegisterGfx(m_pFlyGfx); if (m_pHitGfx != null) AfxGetGFXExMan().RegisterGfx(m_pHitGfx); } */ public virtual Vector3 GetTargetCenter() { // Abstract method - must be implemented by derived class throw new NotImplementedException("GetTargetCenter must be implemented by derived class"); } public virtual bool GetTargetDirAndUp(out Vector3 vDir, out Vector3 vUp) { vDir = Vector3.zero; vUp = Vector3.zero; return false; } // Helper methods that need to be implemented elsewhere or provided by utility class /* protected A3DGFXExMan AfxGetGFXExMan() { // This should return the GFX manager instance throw new NotImplementedException("AfxGetGFXExMan needs to be implemented"); } */ protected Matrix4x4 _build_matrix(Vector3 dir, Vector3 pos) { // This should build a transformation matrix from direction and position throw new NotImplementedException("_build_matrix needs to be implemented"); } protected Matrix4x4 a3d_TransformMatrix(Vector3 dir, Vector3 up, Vector3 pos) { // This should build a transformation matrix from direction, up vector and position throw new NotImplementedException("a3d_TransformMatrix needs to be implemented"); } } public struct ECMODEL_GFX_PROPERTY { public bool bGfxUseLod; public bool bGfxDisableCamShake; public bool bHostECMCreatedByGfx; }; }