Files
test/Assets/PerfectWorld/Scripts/Managers/A3DSkillGfxMan.cs
T
2026-03-05 17:39:11 +07:00

506 lines
19 KiB
C#

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
{
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;
}
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.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);
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 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 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_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 Resume()
{
//ReleaseGfx();
m_enumState = GfxSkillEventState.enumWait;
m_dwCurSpan = 0;
}
// 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;
};
}