517 lines
20 KiB
C#
517 lines
20 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);
|
|
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 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;
|
|
};
|
|
}
|