diff --git a/Assets/PerfectWorld/Scripts/Managers/A3DSkillGfxMan.cs b/Assets/PerfectWorld/Scripts/Managers/A3DSkillGfxMan.cs index 9c9322d62f..6611d201e1 100644 --- a/Assets/PerfectWorld/Scripts/Managers/A3DSkillGfxMan.cs +++ b/Assets/PerfectWorld/Scripts/Managers/A3DSkillGfxMan.cs @@ -147,11 +147,11 @@ namespace BrewMonster pEvent.SetTargetID(nTargetID); pEvent.SetFlyTimeSpan(dwFlyTimeSpan); pEvent.SetDelay(dwDelayTime); - //pEvent.SetReverse(bReverse); - //if (param.value.fVal != float.MinValue) pEvent.SetParam(param); + pEvent.SetReverse(bReverse); + pEvent.SetParam(param); pEvent.SetTraceTarget(bTraceTarget); pEvent.SetModifier(dwModifier); - //pEvent.SetIsCluster(bCluster); + pEvent.SetIsCluster(bCluster); pEvent.SetFadeOut(bFadeOut); pEvent.SetGoblinSkill(bIsGoblinSkill); @@ -190,7 +190,6 @@ namespace BrewMonster #if !_SKILLGFXCOMPOSER pEvent.Tick(0); #endif - pComposer.SpawnGFX(nTargetID); PushEvent(pEvent); return true; } @@ -202,7 +201,7 @@ namespace BrewMonster public class A3DSkillGfxEvent { protected A3DSkillGfxComposer m_pComposer; - //protected CGfxMoveBase m_pMoveMethod; + protected CGfxMoveBase m_pMoveMethod; //protected A3DGFXEx m_pFlyGfx; // 飞行特效 / Fly effect //protected A3DGFXEx m_pHitGfx; // 命中特效 / Hit effect protected uint m_dwFlyTimeSpan; // 飞行时间 / Flight time @@ -248,7 +247,7 @@ namespace BrewMonster m_bGfxDisableCamShake = false; m_bHostECMCreatedByGfx = false; - //m_pMoveMethod = CGfxMoveBase.CreateMoveMethod(mode); + m_pMoveMethod = CGfxMoveBase.CreateMoveMethod(mode); } ~A3DSkillGfxEvent() @@ -333,16 +332,16 @@ namespace BrewMonster 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 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 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; } @@ -393,32 +392,17 @@ namespace BrewMonster if (m_enumState == GfxSkillEventState.enumFinished) return; // 结束 / Finished else if (m_enumState == GfxSkillEventState.enumHit) // 命中 / Hit { - /* if (m_pHitGfx == null || m_pHitGfx.GetState() == ST_STOP) - m_enumState = GfxSkillEventState.enumFinished; - else - { - if (!m_bTargetExist || (m_bHitGfxInfinite && m_pHitGfx.GetTimeElapse() > 5000)) // HIT_GFX_MAX_TIMESPAN = 5000 - m_enumState = GfxSkillEventState.enumFinished; - else - { - if (m_bTraceTarget) - { - Matrix4x4 matTran = Matrix4x4.identity; - Vector3 targetCenter = GetTargetCenter(); - matTran.SetColumn(3, new Vector4(targetCenter.x, targetCenter.y, targetCenter.z, 1)); - //m_pHitGfx.SetParentTM(matTran); - } - - //m_pHitGfx.TickAnimation(dwDeltaTime); - } - }*/ + // 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; } else if (m_dwCurSpan > m_dwFlyTimeSpan) // 飞行超时 / Flight timeout { if (!m_bTargetExist) m_enumState = GfxSkillEventState.enumFinished; - /* else - HitTarget(GetTargetCenter());*/ + else + HitTarget(GetTargetCenter()); } else if (!m_bTargetExist) m_enumState = GfxSkillEventState.enumFinished; @@ -431,40 +415,20 @@ namespace BrewMonster else { m_enumState = GfxSkillEventState.enumFlying; - /* m_pMoveMethod.SetMaxFlyTime(m_dwFlyTimeSpan); - m_pMoveMethod.StartMove(m_vHostPos, m_vTargetPos);*/ + m_pMoveMethod.SetMaxFlyTime(m_dwFlyTimeSpan); + m_pMoveMethod.StartMove(m_vHostPos, m_vTargetPos); - /* if (m_pFlyGfx != null) - { - Vector3 vDir, vUp; - - if (m_pMoveMethod.GetMode() == GfxMoveMode.enumOnTarget && m_pMoveMethod.IsReverse() && GetTargetDirAndUp(out vDir, out vUp)) - m_pFlyGfx.SetParentTM(a3d_TransformMatrix(vDir, vUp, m_pMoveMethod.GetPos())); - else - m_pFlyGfx.SetParentTM(_build_matrix(m_pMoveMethod.GetMoveDir(), m_pMoveMethod.GetPos())); - - m_pFlyGfx.Start(true); - m_pMoveMethod.UpdateGfxParam(m_pFlyGfx, m_vHostPos, m_vTargetPos); - m_pFlyGfx.TickAnimation(0); - }*/ + // Fly GFX spawning is handled by CECSkillGfxEvent.Tick() when it detects Wait→Flying transition + // 飞行特效的生成由CECSkillGfxEvent.Tick()在检测到Wait→Flying转换时处理 } } - else + else // enumFlying state / 飞行状态 { - /* if (m_pMoveMethod.TickMove(dwDeltaTime, m_vHostPos, m_vTargetPos)) // 目标被命中 / Target hit - HitTarget(GetTargetCenter());*/ - /*else if (m_pFlyGfx != null) - { - Vector3 vDir, vUp; + if (m_pMoveMethod.TickMove(dwDeltaTime, m_vHostPos, m_vTargetPos)) // 目标被命中 / Target hit + HitTarget(GetTargetCenter()); - if (m_pMoveMethod.GetMode() == GfxMoveMode.enumOnTarget && m_pMoveMethod.IsReverse() && GetTargetDirAndUp(out vDir, out vUp)) - m_pFlyGfx.SetParentTM(a3d_TransformMatrix(vDir, vUp, m_pMoveMethod.GetPos())); - else - m_pFlyGfx.SetParentTM(_build_matrix(m_pMoveMethod.GetMoveDir(), m_pMoveMethod.GetPos())); - - m_pMoveMethod.UpdateGfxParam(m_pFlyGfx, m_vHostPos, m_vTargetPos); - m_pFlyGfx.TickAnimation(dwDeltaTime); - }*/ + // Fly GFX transform update is handled by CECSkillGfxEvent.Tick() + // 飞行特效的变换更新由CECSkillGfxEvent.Tick()处理 } } diff --git a/Assets/PerfectWorld/Scripts/Managers/CECAttacksMan.cs b/Assets/PerfectWorld/Scripts/Managers/CECAttacksMan.cs index 31aba54c2f..bf07c3e77c 100644 --- a/Assets/PerfectWorld/Scripts/Managers/CECAttacksMan.cs +++ b/Assets/PerfectWorld/Scripts/Managers/CECAttacksMan.cs @@ -141,6 +141,8 @@ namespace BrewMonster private void Update() { + uint dwDeltaTime = (uint)(Time.deltaTime * 1000); + #if UNITY_EDITOR if (m_AttackList.Count == 0) m_AttackList = m_targets.ToList(); @@ -152,9 +154,13 @@ namespace BrewMonster //BMLogger.LogError("HoangDev: Update CECAttackEvent node.Value.m_bFinished: " + node.Value.m_bFinished); if (node.Value.m_bFinished) m_targets.Remove(node); - else node.Value.Tick((uint)(Time.deltaTime * 1000)); + else node.Value.Tick(dwDeltaTime); node = next; } + + // Tick skill GFX events (fly/hit GFX state machine) + // 更新技能特效事件(飞行/命中特效状态机) + SkillGfxMan.InstanceSub.Tick(dwDeltaTime); } bool FileExists(string relativePath) { @@ -454,6 +460,36 @@ namespace BrewMonster }; } + /// + /// Load SkillStub GFX parameters onto this composer. + /// 将SkillStub的GFX参数加载到此组合器上。 + /// + public void LoadFromSkillStub(BrewMonster.Scripts.Skills.SkillStub stub) + { + if (stub == null) return; + m_MoveMode = stub.m_MoveMode; + m_TargetMode = stub.m_TargetMode; + m_AttFlyMode = stub.m_AttFlyMode; + m_AttHitMode = stub.m_AttHitMode; + m_dwFlyTime = stub.m_dwFlyTime; + m_bTraceTarget = stub.m_bTraceTarget; + m_FlyCluster = new GfxCluster + { + m_ulCount = stub.m_FlyClusterCount, + m_dwInterv = stub.m_FlyClusterInterval + }; + m_bOneHit = stub.m_bOneHit; + m_bFadeOut = stub.m_bFadeOut; + m_bRelScl = stub.m_bRelScl; + m_fDefTarScl = stub.m_fDefTarScl; + m_param = stub.m_param; + } + + // GFX prefab accessors / GFX预制体访问器 + public GameObject GetFlyGFX() => flyGFX; + public GameObject GetHitGFX() => hitGFX; + public GameObject GetHitGrdGFX() => hitGrdGFX; + /// /// Load composer from file /// 从文件加载组合器 @@ -465,11 +501,6 @@ namespace BrewMonster #endif public async Task Load(SkillStub skillStub, string flyGFXPath, string hitGrdGFXPath, string hitGFXPath) { -#if !UNITY_EDITOR - string flyGfxName = flyGFXPath; - string hitGfxName = hitGFXPath; - string hitGrdGfxName = hitGrdGFXPath; -#else flyGfxName = flyGFXPath; hitGfxName = hitGFXPath; hitGrdGfxName = hitGrdGFXPath; @@ -537,19 +568,8 @@ namespace BrewMonster return true; } - public void SpawnGFX(long IDTarget) - { -#if UNITY_EDITOR - BMLogger.LogError("HoangDev: Load A3DSkillGfxComposer GFX name: " + flyGfxName); - BMLogger.LogError("HoangDev: Load A3DSkillGfxComposer GFX name: " + hitGfxName); - BMLogger.LogError("HoangDev: Load A3DSkillGfxComposer GFX name: " + hitGrdGfxName); -#endif - var obj = EC_ManMessageMono.Instance.GetObject(IDTarget, 0); - if (obj != null && flyGFX != null) - { - GameObject.Instantiate(flyGFX, obj.transform.position, flyGFX.gameObject.transform.rotation, obj.transform); - } - } + // SpawnGFX temp hack REMOVED — GFX spawning now handled by CECSkillGfxEvent state machine + // SpawnGFX临时代码已删除 — GFX生成现在由CECSkillGfxEvent状态机处理 /// /// Initialize composer /// 初始化组合器 @@ -567,45 +587,33 @@ namespace BrewMonster { bool bCastInTargets = false; - /* char szFly = m_szFlyGfx[0] ? m_szFlyGfx : NULL; - char szHit = m_szHitGfx[0] ? m_szHitGfx : NULL;*/ + // Determine GFX names from loaded prefabs / 从已加载的预制体确定GFX名称 + string szFly = flyGFX != null ? flyGfxName : null; + string szHit = hitGFX != null ? hitGfxName : null; - /*# ifndef _SKILLGFXCOMPOSER - if (!CECOptimize::Instance().GetGFX().CanShowFly(nHostID)) - { - a_LogOutput(1, "[SKILL_GFX_FLOW] ====> Fly GFX hidden by optimization"); - szFly = NULL; - } - if (!CECOptimize::Instance().GetGFX().CanShowHit(nHostID)) - { - a_LogOutput(1, "[SKILL_GFX_FLOW] ====> Hit GFX hidden by optimization"); - szHit = NULL; - } - #endif*/ + // TODO Phase 2: Optimization checks / 第二阶段:优化检查 + // if (!CECOptimize.Instance.GetGFX().CanShowFly(nHostID)) szFly = null; + // if (!CECOptimize.Instance.GetGFX().CanShowHit(nHostID)) szHit = null; - // Log target details if (targets != null && targets.Count > 0) { for (int i = 0; i < targets.Count; i++) { var tar = targets[i]; - if (nCastTargetID == tar.idTarget) bCastInTargets = true; - AddOneTarget(nCastTargetID, nHostID, "" /*szFly*/, ""/*szHit*/, tar, i == 0, bIsGoblinSkill); + AddOneTarget(nCastTargetID, nHostID, szFly, szHit, tar, i == 0, bIsGoblinSkill); } } - else - { - } + if (nCastTargetID != 0 && !bCastInTargets) { TARGET_DATA tar = default; tar.idTarget = nCastTargetID; tar.dwModifier = 0; - AddOneTarget(nCastTargetID, nHostID, ""/*szFly*/, ""/*szHit*/, tar, false, bIsGoblinSkill); + AddOneTarget(nCastTargetID, nHostID, szFly, szHit, tar, false, bIsGoblinSkill); } } @@ -642,10 +650,10 @@ namespace BrewMonster } // 计算缩放 / Calculate scale - /* if (m_bRelScl) - fScale = m_pSkillGfxMan.GetTargetScale(_Target) / m_fDefTarScl * m_fHitGfxScale; - else - fScale = m_fHitGfxScale;*/ + if (m_bRelScl) + fScale = SkillGfxMan.InstanceSub.GetTargetScale(_Target) / m_fDefTarScl * m_fHitGfxScale; + else + fScale = m_fHitGfxScale; // 根据目标类型决定是否显示特效 / Determine whether to show effects based on target type if ((nCastTargetID != 0 && tar.idTarget != nCastTargetID) @@ -675,8 +683,8 @@ namespace BrewMonster (int)m_FlyCluster.m_ulCount, m_FlyCluster.m_dwInterv, m_param, - 0/*m_fFlyGfxScale*/, - 0/*fScale*/, + m_fFlyGfxScale, + fScale, tar.dwModifier, m_bOneHit, m_bFadeOut, @@ -981,9 +989,13 @@ public class CECAttackEvent } else { - // TODO: Implement SkillGfxComposerMan - // m_pManager.GetSkillGfxComposerMan().Play(m_idSkill, m_idHost, m_idCastTarget, m_targets); - // pComposer = m_pManager.GetSkillGfxComposerMan().GetSkillGfxComposer(m_idSkill); + // NPC regular skill GFX / NPC常规技能特效 + var composerMan = m_pManager?.GetSkillGfxComposerMan(); + if (composerMan != null) + { + composerMan.Play(m_idSkill, m_idHost, m_idCastTarget, m_targets); + pComposer = composerMan.GetSkillGfxComposer(m_idSkill); + } } if (pComposer != null && pComposer.m_dwFlyTime == 0) // 技能没有飞行时间,则直接头顶冒字 / Skill has no fly time, show damage immediately diff --git a/Assets/PerfectWorld/Scripts/Managers/CECSkillGfxMan.cs b/Assets/PerfectWorld/Scripts/Managers/CECSkillGfxMan.cs index 706e00bf3c..37db22d9c6 100644 --- a/Assets/PerfectWorld/Scripts/Managers/CECSkillGfxMan.cs +++ b/Assets/PerfectWorld/Scripts/Managers/CECSkillGfxMan.cs @@ -15,6 +15,9 @@ namespace BrewMonster protected EC_ManPlayer m_pPlayerMan; // 玩家管理器 / Player manager protected CECNPCMan m_pNPCMan; // NPC管理器 / NPC manager + private GameObject m_flyGfxInstance; // 飞行特效实例 / Fly GFX instance + private GameObject m_hitGfxInstance; // 命中特效实例 / Hit GFX instance + public CECSkillGfxEvent(GfxMoveMode mode) : base(mode) { m_pPlayerMan = EC_ManMessageMono.Instance?.GetECManPlayer; @@ -34,7 +37,7 @@ namespace BrewMonster /// Get the original host ID (considering reverse mode) /// 获取原始施法者ID(考虑反向模式) /// - /* public long GetOriginalHost() + public long GetOriginalHost() { // GFX 特效显示,技能的原始的攻击者和目标被位置,提供了方法,查询原始攻击者 // GFX effects display, original attacker and target positions swapped, provides method to query original attacker @@ -50,7 +53,7 @@ namespace BrewMonster // GFX 特效显示,技能的原始的攻击者和目标被位置,提供了方法,查询原始目标 // GFX effects display, original attacker and target positions swapped, provides method to query original target return m_pMoveMethod.IsReverse() ? m_nHostID : m_nTargetID; - }*/ + } /// /// Get target direction and up vector @@ -122,7 +125,8 @@ namespace BrewMonster /// public override void Tick(uint dwDeltaTime) { - /*if (GetComposer() != null) + // Update host and target positions / 更新施法者和目标位置 + if (GetComposer() != null) { SGC_POS_INFO pHostPos, pTargetPos; @@ -192,51 +196,115 @@ namespace BrewMonster Vector3.zero, null, false); - }*/ + } + // Track state before base.Tick() to detect transitions / 在base.Tick()前记录状态以检测转换 + GfxSkillEventState prevState = m_enumState; base.Tick(dwDeltaTime); + + // Spawn fly GFX when entering Flying state / 进入飞行状态时生成飞行特效 + if (prevState == GfxSkillEventState.enumWait && m_enumState == GfxSkillEventState.enumFlying) + SpawnFlyGfx(); + + // Update fly GFX transform during Flying / 飞行期间更新飞行特效变换 + if (m_enumState == GfxSkillEventState.enumFlying) + UpdateFlyGfxTransform(); } /// - /// Handle target hit event - /// 处理命中目标事件 + /// Handle target hit event - destroy fly GFX and spawn hit GFX + /// 处理命中目标事件 - 销毁飞行特效并生成命中特效 /// protected override void HitTarget(Vector3 vTarget) { base.HitTarget(vTarget); + DestroyFlyGfx(); + SpawnHitGfx(vTarget); - // now show some special hit gfx - // 现在显示一些特殊的命中特效 - /* if (CECOptimize.Instance.GetGFX().CanShowHit(GetOriginalHost())) + // TODO Phase 2: Special hit effects (rune, critical, nullity) + // TODO 第二阶段:特殊命中效果(符石、暴击、无效) + } + + // ===== GFX Instance Management ===== + // GFX实例管理 + + /// + /// Spawn fly GFX at movement position + /// 在移动位置生成飞行特效 + /// + private void SpawnFlyGfx() + { + if (m_pComposer == null) return; + GameObject prefab = m_pComposer.GetFlyGFX(); + if (prefab == null) return; + + Vector3 pos = m_pMoveMethod.GetPos(); + Vector3 dir = m_pMoveMethod.GetMoveDir(); + Quaternion rot = dir.sqrMagnitude > 1e-4f ? Quaternion.LookRotation(dir) : Quaternion.identity; + m_flyGfxInstance = GameObject.Instantiate(prefab, pos, rot); + BMLogger.Log($"[GFX_FLOW] SpawnFlyGfx at {pos}, prefab={prefab.name}"); + } + + /// + /// Update fly GFX transform to follow movement + /// 更新飞行特效变换以跟随移动 + /// + private void UpdateFlyGfxTransform() + { + if (m_flyGfxInstance == null) return; + m_flyGfxInstance.transform.position = m_pMoveMethod.GetPos(); + Vector3 dir = m_pMoveMethod.GetMoveDir(); + if (dir.sqrMagnitude > 1e-4f) + m_flyGfxInstance.transform.rotation = Quaternion.LookRotation(dir); + } + + /// + /// Destroy fly GFX instance + /// 销毁飞行特效实例 + /// + private void DestroyFlyGfx() + { + if (m_flyGfxInstance != null) { - if ((m_dwModifier & CECAttackEvent.MOD_PHYSIC_ATTACK_RUNE) != 0) - { - // 程序联入\\符石\\物攻符石特效.gfx - // Program integration\\Rune\\Physical attack rune effect.gfx - // TODO: Play GFX - g_pGame->GetGFXCaster()->PlayAutoGFXEx(...) - } + GameObject.Destroy(m_flyGfxInstance); + m_flyGfxInstance = null; + } + } - if ((m_dwModifier & CECAttackEvent.MOD_MAGIC_ATTACK_RUNE) != 0) - { - // 程序联入\\符石\\法攻符石特效.gfx - // Program integration\\Rune\\Magic attack rune effect.gfx - // TODO: Play GFX - } + /// + /// Spawn hit GFX at target position + /// 在目标位置生成命中特效 + /// + private void SpawnHitGfx(Vector3 vTarget) + { + if (m_pComposer == null) return; + GameObject prefab = m_pComposer.GetHitGFX(); + if (prefab == null) return; - if ((m_dwModifier & CECAttackEvent.MOD_PHYSIC_DEFENCE_RUNE) != 0) - { - // 程序联入\\符石\\物防符石特效.gfx - // Program integration\\Rune\\Physical defense rune effect.gfx - // TODO: Play GFX - } + Quaternion rot = Quaternion.identity; + if (m_bHostExist) + { + Vector3 dir = vTarget - m_vHostPos; + dir.y = 0; + if (dir.sqrMagnitude > 1e-6f) rot = Quaternion.LookRotation(dir); + } - if ((m_dwModifier & CECAttackEvent.MOD_MAGIC_DEFENCE_RUNE) != 0) - { - // 程序联入\\符石\\法防符石特效.gfx - // Program integration\\Rune\\Magic defense rune effect.gfx - // TODO: Play GFX - } - }*/ + m_hitGfxInstance = GameObject.Instantiate(prefab, vTarget, rot); + GameObject.Destroy(m_hitGfxInstance, 3.0f); // auto-cleanup / 自动清理 + BMLogger.Log($"[GFX_FLOW] SpawnHitGfx at {vTarget}, prefab={prefab.name}"); + } + + /// + /// Clean up GFX instances on Resume (return to pool) + /// 在Resume时清理GFX实例(返回池) + /// + public new void Resume() + { + DestroyFlyGfx(); + // Hit GFX is auto-destroyed by Unity's Destroy timer, don't null it here + // 命中特效由Unity的Destroy计时器自动销毁,不在此处置null + m_hitGfxInstance = null; + base.Resume(); } /// @@ -672,7 +740,7 @@ namespace BrewMonster { m_EventLst.Remove(node); pEvent.Resume(); - //m_FreeLst[(int)pEvent.GetMode()].AddLast(pEvent); + m_FreeLst[(int)pEvent.GetMode()].AddLast(pEvent); } else { diff --git a/Assets/PerfectWorld/Scripts/Vfx/A3DSkillGfxComposerMan.cs b/Assets/PerfectWorld/Scripts/Vfx/A3DSkillGfxComposerMan.cs index 6238acea12..139847efde 100644 --- a/Assets/PerfectWorld/Scripts/Vfx/A3DSkillGfxComposerMan.cs +++ b/Assets/PerfectWorld/Scripts/Vfx/A3DSkillGfxComposerMan.cs @@ -50,8 +50,15 @@ namespace BrewMonster return false; } + // Load SkillStub GFX parameters (MoveMode, FlyTime, TargetMode, clusters, etc.) + // 从SkillStub加载GFX参数(移动模式、飞行时间、目标模式、群集等) + SkillStub stub = SkillStub.GetStub((uint)nSkillID); + if (stub != null) + { + composer.LoadFromSkillStub(stub); + } + // In original C++: composer->Init(g_pGame->GetGameRun()->GetWorld()->GetSkillGfxMan()->GetPtr()); - // Not wired in Unity yet; keep placeholder init to allow future hookup. composer.Init(A3DSkillGfxMan.Instance); m_ComposerMap[nSkillID] = composer;