1370 lines
51 KiB
C#
1370 lines
51 KiB
C#
using BrewMonster;
|
||
using BrewMonster.Managers;
|
||
using BrewMonster.Network;
|
||
using BrewMonster.Scripts;
|
||
using CSNetwork.GPDataType;
|
||
using Cysharp.Threading.Tasks;
|
||
using PerfectWorld.Scripts.Managers.BrewMonster.Managers;
|
||
using System;
|
||
using System.Collections.Generic;
|
||
using UnityEngine;
|
||
|
||
namespace BrewMonster
|
||
{
|
||
|
||
public class CECSkillGfxEvent : A3DSkillGfxEvent
|
||
{
|
||
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;
|
||
m_pNPCMan = EC_ManMessageMono.Instance?.CECNPCMan;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Get the skill GFX composer
|
||
/// 获取技能特效组合器
|
||
/// </summary>
|
||
public A3DSkillGfxComposer GetComposer()
|
||
{
|
||
return m_pComposer;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Get the original host ID (considering reverse mode)
|
||
/// 获取原始施法者ID(考虑反向模式)
|
||
/// </summary>
|
||
public long GetOriginalHost()
|
||
{
|
||
// GFX 特效显示,技能的原始的攻击者和目标被位置,提供了方法,查询原始攻击者
|
||
// GFX effects display, original attacker and target positions swapped, provides method to query original attacker
|
||
return m_pMoveMethod.IsReverse() ? m_nTargetID : m_nHostID;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Get the original target ID (considering reverse mode)
|
||
/// 获取原始目标ID(考虑反向模式)
|
||
/// </summary>
|
||
public long GetOriginalTarget()
|
||
{
|
||
// GFX 特效显示,技能的原始的攻击者和目标被位置,提供了方法,查询原始目标
|
||
// GFX effects display, original attacker and target positions swapped, provides method to query original target
|
||
return m_pMoveMethod.IsReverse() ? m_nHostID : m_nTargetID;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Get target direction and up vector
|
||
/// 获取目标方向和上向量
|
||
/// </summary>
|
||
public override bool GetTargetDirAndUp(out Vector3 vDir, out Vector3 vUp)
|
||
{
|
||
if (!m_bTargetDirAndUpExist)
|
||
{
|
||
vDir = Vector3.zero;
|
||
vUp = Vector3.zero;
|
||
return false;
|
||
}
|
||
|
||
vDir = m_vTargetDir;
|
||
vUp = m_vTargetUp;
|
||
return true;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Get target center position
|
||
/// 获取目标中心位置
|
||
/// </summary>
|
||
public override Vector3 GetTargetCenter()
|
||
{
|
||
Vector3 vTargetCenter = Vector3.zero;
|
||
|
||
try
|
||
{
|
||
// if composer has been set
|
||
// use the composer's parameter to make the hook information affect.
|
||
// 如果已设置组合器,使用组合器的参数来影响挂点信息
|
||
if (GetComposer() != null)
|
||
{
|
||
A3DSkillGfxComposer pComposer = GetComposer();
|
||
bool success = get_pos_by_id(
|
||
m_pPlayerMan,
|
||
m_pNPCMan,
|
||
(int)m_nTargetID,
|
||
out vTargetCenter,
|
||
pComposer.m_HitPos.HitPos,
|
||
false,
|
||
pComposer.m_HitPos.szHook,
|
||
pComposer.m_HitPos.bRelHook,
|
||
pComposer.m_HitPos.vOffset,
|
||
pComposer.m_HitPos.szHanger,
|
||
pComposer.m_HitPos.bChildHook);
|
||
|
||
if (!success)
|
||
{
|
||
// Return last known position or zero if target is destroyed
|
||
// 如果目标已销毁,返回最后已知位置或零
|
||
return m_vTargetPos != Vector3.zero ? m_vTargetPos : Vector3.zero;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
bool success = get_pos_by_id(
|
||
m_pPlayerMan,
|
||
m_pNPCMan,
|
||
(int)m_nTargetID,
|
||
out vTargetCenter,
|
||
GfxHitPos.enumHitCenter,
|
||
false,
|
||
null,
|
||
false,
|
||
Vector3.zero,
|
||
null,
|
||
false);
|
||
|
||
if (!success)
|
||
{
|
||
// Return last known position or zero if target is destroyed
|
||
// 如果目标已销毁,返回最后已知位置或零
|
||
return m_vTargetPos != Vector3.zero ? m_vTargetPos : Vector3.zero;
|
||
}
|
||
}
|
||
}
|
||
catch (System.Exception ex)
|
||
{
|
||
BMLogger.LogError($"[SKILL_GFX_DEBUG] GetTargetCenter: Exception accessing target {m_nTargetID} - {ex.Message}");
|
||
// Return last known position or zero
|
||
// 返回最后已知位置或零
|
||
return m_vTargetPos != Vector3.zero ? m_vTargetPos : Vector3.zero;
|
||
}
|
||
|
||
return vTargetCenter;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Tick update
|
||
/// 更新
|
||
/// </summary>
|
||
public override void Tick(uint dwDeltaTime)
|
||
{
|
||
// Track state before base.Tick() to detect transitions / 在base.Tick()前记录状态以检测转换
|
||
GfxSkillEventState prevState = m_enumState;
|
||
|
||
// Update host and target positions / 更新施法者和目标位置
|
||
if (GetComposer() != null)
|
||
{
|
||
SGC_POS_INFO pHostPos, pTargetPos;
|
||
|
||
if (m_pMoveMethod.IsReverse())
|
||
{
|
||
pHostPos = m_pComposer.m_FlyEndPos;
|
||
pTargetPos = m_pComposer.m_FlyPos;
|
||
}
|
||
else
|
||
{
|
||
pHostPos = m_pComposer.m_FlyPos;
|
||
pTargetPos = m_pComposer.m_FlyEndPos;
|
||
}
|
||
|
||
m_bHostExist = get_pos_by_id(
|
||
m_pPlayerMan,
|
||
m_pNPCMan,
|
||
(int)m_nHostID,
|
||
out m_vHostPos,
|
||
pHostPos.HitPos,
|
||
m_bIsGoblinSkill,
|
||
pHostPos.szHook,
|
||
pHostPos.bRelHook,
|
||
pHostPos.vOffset,
|
||
pHostPos.szHanger,
|
||
pHostPos.bChildHook);
|
||
|
||
m_bTargetExist = get_pos_by_id(
|
||
m_pPlayerMan,
|
||
m_pNPCMan,
|
||
(int)m_nTargetID,
|
||
out m_vTargetPos,
|
||
pTargetPos.HitPos,
|
||
false,
|
||
pTargetPos.szHook,
|
||
pTargetPos.bRelHook,
|
||
pTargetPos.vOffset,
|
||
pTargetPos.szHanger,
|
||
pTargetPos.bChildHook);
|
||
|
||
m_bTargetDirAndUpExist = _get_dir_and_up_by_id(m_pPlayerMan, m_pNPCMan, (int)m_nTargetID, out m_vTargetDir, out m_vTargetUp);
|
||
}
|
||
else
|
||
{
|
||
m_bHostExist = get_pos_by_id(
|
||
m_pPlayerMan,
|
||
m_pNPCMan,
|
||
(int)m_nHostID,
|
||
out m_vHostPos,
|
||
m_pMoveMethod.GetHitPos(),
|
||
m_bIsGoblinSkill,
|
||
null,
|
||
false,
|
||
Vector3.zero,
|
||
null,
|
||
false);
|
||
|
||
m_bTargetExist = get_pos_by_id(
|
||
m_pPlayerMan,
|
||
m_pNPCMan,
|
||
(int)m_nTargetID,
|
||
out m_vTargetPos,
|
||
m_pMoveMethod.GetHitPos(),
|
||
false,
|
||
null,
|
||
false,
|
||
Vector3.zero,
|
||
null,
|
||
false);
|
||
}
|
||
|
||
// Log target existence issues with more detail
|
||
// 记录目标存在问题的更多详细信息
|
||
|
||
base.Tick(dwDeltaTime);
|
||
|
||
// Spawn fly GFX when entering Flying state / 进入飞行状态时生成飞行特效
|
||
if (prevState == GfxSkillEventState.enumWait && m_enumState == GfxSkillEventState.enumFlying)
|
||
{
|
||
|
||
#if UNITY_EDITOR
|
||
Vector3 currentPos = m_pMoveMethod.GetPos();
|
||
//BMLogger.LogError($"[SKILL_GFX_DEBUG] Event.Tick: Registering gizmo - hostPos={m_vHostPos}, targetPos={m_vTargetPos}, currentPos={currentPos}, hostExist={m_bHostExist}, targetExist={m_bTargetExist}");
|
||
|
||
if (m_vHostPos.sqrMagnitude > 0.01f && m_vTargetPos.sqrMagnitude > 0.01f)
|
||
{
|
||
SkillGfxGizmoDrawer.RegisterProjectile(m_nHostID, m_nTargetID, m_vHostPos, m_vTargetPos, m_pMoveMethod.GetMode());
|
||
}
|
||
#endif
|
||
|
||
SpawnFlyGfx();
|
||
}
|
||
|
||
// Update fly GFX transform during Flying / 飞行期间更新飞行特效变换
|
||
if (m_enumState == GfxSkillEventState.enumFlying)
|
||
{
|
||
UpdateFlyGfxTransform();
|
||
|
||
// Update gizmo position / 更新辅助线位置
|
||
#if UNITY_EDITOR
|
||
Vector3 currentPos = m_pMoveMethod.GetPos();
|
||
// Only update if position is valid
|
||
// 仅在位置有效时更新
|
||
if (currentPos.sqrMagnitude > 0.01f)
|
||
{
|
||
SkillGfxGizmoDrawer.UpdateProjectile(m_nHostID, m_nTargetID, currentPos, m_vTargetPos);
|
||
}
|
||
#endif
|
||
}
|
||
|
||
// Update hit GFX position when m_bTraceTarget is true (follow target) / 当m_bTraceTarget为true时更新命中特效位置(跟随目标)
|
||
// This matches C++ logic: hit GFX follows target position each frame when m_bTraceTarget is true
|
||
// 这与C++逻辑匹配:当m_bTraceTarget为true时,命中特效每帧跟随目标位置
|
||
if (m_enumState == GfxSkillEventState.enumHit && m_bTraceTarget)
|
||
{
|
||
UpdateHitGfxTransform();
|
||
// Check if hit GFX has been destroyed (3 second lifetime) or target is destroyed
|
||
// 检查命中特效是否已被销毁(3秒生命周期)或目标是否已销毁
|
||
if (m_hitGfxInstance == null || (!m_bTargetExist && m_nTargetID != 0))
|
||
{
|
||
m_enumState = GfxSkillEventState.enumFinished;
|
||
}
|
||
}
|
||
|
||
// Remove gizmo when hit or finished / 命中或完成时移除辅助线
|
||
if (m_enumState == GfxSkillEventState.enumHit || m_enumState == GfxSkillEventState.enumFinished)
|
||
{
|
||
#if UNITY_EDITOR
|
||
SkillGfxGizmoDrawer.RemoveProjectile(m_nHostID, m_nTargetID);
|
||
#endif
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Handle target hit event - destroy fly GFX and spawn hit GFX
|
||
/// 处理命中目标事件 - 销毁飞行特效并生成命中特效
|
||
/// </summary>
|
||
protected override void HitTarget(Vector3 vTarget)
|
||
{
|
||
base.HitTarget(vTarget);
|
||
DestroyFlyGfx();
|
||
// Only destroy fly GFX if NOT tracing target
|
||
// If tracing target, fly GFX will be cleaned up when buff expires
|
||
// 只有在不跟踪目标时才销毁飞行特效
|
||
// 如果跟踪目标,飞行特效将在buff过期时清理
|
||
|
||
// if (!m_bTraceTarget)
|
||
// {
|
||
// DestroyFlyGfx();
|
||
// }
|
||
// else
|
||
// {
|
||
// // If fly GFX exists and m_bTraceTarget is true, add to tracking list
|
||
// // 如果飞行特效存在且m_bTraceTarget为true,添加到跟踪列表
|
||
// if (m_flyGfxInstance != null)
|
||
// {
|
||
// SkillGfxMan.InstanceSub?.AddTraceTargetGfx(m_flyGfxInstance, 0); // Skill ID not available, use 0
|
||
// }
|
||
// }
|
||
|
||
SpawnHitGfx(vTarget);
|
||
|
||
// TODO Phase 2: Special hit effects (rune, critical, nullity)
|
||
// TODO 第二阶段:特殊命中效果(符石、暴击、无效)
|
||
}
|
||
|
||
// ===== GFX Instance Management =====
|
||
// GFX实例管理
|
||
|
||
/// <summary>
|
||
/// Spawn fly GFX at movement position
|
||
/// 在移动位置生成飞行特效
|
||
/// </summary>
|
||
private void SpawnFlyGfx()
|
||
{
|
||
if (!m_bShowFlyGfx) return;
|
||
|
||
if (m_pComposer == null)
|
||
{
|
||
BMLogger.LogError($"[SKILL_GFX_DEBUG] SpawnFlyGfx: m_pComposer is NULL - cannot spawn fly GFX!");
|
||
return;
|
||
}
|
||
|
||
GameObject prefab = m_pComposer.GetFlyGFX();
|
||
//BMLogger.LogError("FlyGfx : " + m_pComposer.flyGfxName);
|
||
if (prefab == null)
|
||
{
|
||
BMLogger.LogError($"[SKILL_GFX_DEBUG] SpawnFlyGfx: Fly GFX prefab is NULL - cannot spawn!");
|
||
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, prefab.transform.rotation);
|
||
// BMLogger.LogError("HoangDev: m_pComposer fly "+m_pComposer.flyGfxName);
|
||
SFXManager.Instance?.PlaySkillSfxAtPointAsync(m_pComposer.GetFlySfxPath(), pos).Forget();
|
||
|
||
// If m_bTraceTarget is true, add to tracking list when spawned
|
||
// 如果m_bTraceTarget为true,在生成时添加到跟踪列表
|
||
if (m_bTraceTarget)
|
||
{
|
||
SkillGfxMan.InstanceSub?.AddTraceTargetGfx(m_flyGfxInstance, 0); // Skill ID not available, use 0
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Update fly GFX transform to follow movement
|
||
/// 更新飞行特效变换以跟随移动
|
||
/// </summary>
|
||
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);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Update hit GFX transform to follow target when m_bTraceTarget is true
|
||
/// 当m_bTraceTarget为true时更新命中特效变换以跟随目标
|
||
/// This matches C++ logic: hit GFX position is updated each frame to follow target
|
||
/// 这与C++逻辑匹配:命中特效位置每帧更新以跟随目标
|
||
/// </summary>
|
||
private void UpdateHitGfxTransform()
|
||
{
|
||
if (m_hitGfxInstance == null) return;
|
||
|
||
// Update target position first / 首先更新目标位置
|
||
if (m_bTargetExist && m_nTargetID != 0)
|
||
{
|
||
// Get current target position using GetTargetCenter() / 使用GetTargetCenter()获取当前目标位置
|
||
Vector3 targetPos = GetTargetCenter();
|
||
m_hitGfxInstance.transform.position = targetPos;
|
||
}
|
||
// If target doesn't exist, hit GFX stays at last known position
|
||
// 如果目标不存在,命中特效保持在最后已知位置
|
||
}
|
||
|
||
/// <summary>
|
||
/// Destroy fly GFX instance
|
||
/// 销毁飞行特效实例
|
||
/// </summary>
|
||
private void DestroyFlyGfx()
|
||
{
|
||
if (m_flyGfxInstance != null)
|
||
{
|
||
GameObject.Destroy(m_flyGfxInstance);
|
||
m_flyGfxInstance = null;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Spawn hit GFX at target position
|
||
/// 在目标位置生成命中特效
|
||
/// </summary>
|
||
private void SpawnHitGfx(Vector3 vTarget)
|
||
{
|
||
if (!m_bShowHitGfx) return;
|
||
|
||
if (m_pComposer == null)
|
||
{
|
||
BMLogger.LogError($"[SKILL_GFX_DEBUG] SpawnHitGfx: m_pComposer is NULL - cannot spawn hit GFX!");
|
||
return;
|
||
}
|
||
|
||
// Check if target exists - if not, use ground hit GFX instead of regular hit GFX
|
||
// 检查目标是否存在 - 如果不存在,使用地面命中特效而不是常规命中特效
|
||
// This matches C++ logic: m_szHitGrndGfx is used when projectile hits ground (no target)
|
||
// 这与C++逻辑匹配:当投射物击中地面(无目标)时使用m_szHitGrndGfx
|
||
bool bTargetExists = m_bTargetExist && m_nTargetID != 0;
|
||
GameObject prefab = bTargetExists ? m_pComposer.GetHitGFX() : m_pComposer.GetHitGrdGFX();
|
||
//BMLogger.LogError("bTargetExists : " + bTargetExists);
|
||
BMLogger.LogError("HitGfx : " + m_pComposer.hitGfxName);
|
||
|
||
if (prefab == null)
|
||
{
|
||
// Fallback: if ground hit GFX is null but target doesn't exist, try regular hit GFX
|
||
// 回退:如果地面命中特效为空但目标不存在,尝试使用常规命中特效
|
||
if (!bTargetExists)
|
||
{
|
||
prefab = m_pComposer.GetHitGFX();
|
||
}
|
||
|
||
if (prefab == null)
|
||
{
|
||
BMLogger.LogWarning($"[SKILL_GFX_DEBUG] SpawnHitGfx: {(bTargetExists ? "Hit" : "Ground Hit")} GFX prefab is NULL - cannot spawn!");
|
||
return;
|
||
}
|
||
}
|
||
|
||
/* Quaternion rot = Quaternion.identity;
|
||
if (m_bHostExist)
|
||
{
|
||
Vector3 dir = vTarget - m_vHostPos;
|
||
dir.y = 0;
|
||
if (dir.sqrMagnitude > 1e-6f) rot = Quaternion.LookRotation(dir);
|
||
}*/
|
||
|
||
m_hitGfxInstance = GameObject.Instantiate(prefab, vTarget, prefab.transform.rotation);
|
||
|
||
string hitSfxPath;
|
||
if (bTargetExists)
|
||
hitSfxPath = m_pComposer.GetHitSfxPath();
|
||
else
|
||
{
|
||
hitSfxPath = m_pComposer.GetHitGrndSfxPath();
|
||
if (string.IsNullOrEmpty(hitSfxPath))
|
||
hitSfxPath = m_pComposer.GetHitSfxPath();
|
||
}
|
||
SFXManager.Instance?.PlaySkillSfxAtPointAsync(hitSfxPath, vTarget).Forget();
|
||
|
||
// If m_bTraceTarget is true, add to tracking list (don't auto-destroy)
|
||
// 如果m_bTraceTarget为true,添加到跟踪列表(不自动销毁)
|
||
if (m_bTraceTarget)
|
||
{
|
||
SkillGfxMan.InstanceSub?.AddTraceTargetGfx(m_hitGfxInstance, 0); // Skill ID not available, use 0
|
||
//BMLogger.Log($"[TRACE_TARGET_GFX] SpawnHitGfx: Added hit GFX to trace target list (m_bTraceTarget=true)");
|
||
}
|
||
else
|
||
{
|
||
// Destroy hit GFX after 5 seconds (unless m_bTraceTarget is true, then it follows target until destroyed)
|
||
// 5秒后销毁命中特效(除非m_bTraceTarget为true,否则它会跟随目标直到被销毁)
|
||
//HIT_GFX_MAX_TIMESPAN 5000
|
||
//BMLogger.Log($"[TRACE_TARGET_GFX] SpawnHitGfx: GameObject.Destroy(m_hitGfxInstance, 5.0f);");
|
||
GameObject.Destroy(m_hitGfxInstance, 5.0f);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Clean up GFX instances on Resume (return to pool)
|
||
/// 在Resume时清理GFX实例(返回池)
|
||
/// </summary>
|
||
public new void Resume()
|
||
{
|
||
// Don't destroy GFX if it's in trace target list
|
||
// It will be cleaned up when buff expires
|
||
// 如果GFX在跟踪目标列表中,不要销毁它
|
||
// 它将在buff过期时清理
|
||
if (m_flyGfxInstance != null)
|
||
{
|
||
if (SkillGfxMan.InstanceSub != null && !SkillGfxMan.InstanceSub.IsTraceTargetGfx(m_flyGfxInstance))
|
||
{
|
||
DestroyFlyGfx();
|
||
}
|
||
}
|
||
|
||
if (m_hitGfxInstance != null)
|
||
{
|
||
if (SkillGfxMan.InstanceSub != null && !SkillGfxMan.InstanceSub.IsTraceTargetGfx(m_hitGfxInstance))
|
||
{
|
||
// Hit GFX is auto-destroyed by Unity's Destroy timer, don't null it here
|
||
// 命中特效由Unity的Destroy计时器自动销毁,不在此处置null
|
||
m_hitGfxInstance = null;
|
||
}
|
||
}
|
||
|
||
base.Resume();
|
||
}
|
||
|
||
/// <summary>
|
||
/// Load hit GFX with modifier checks
|
||
/// 加载命中特效(带修饰符检查)
|
||
/// </summary>
|
||
/*public A3DGFXEx LoadHitGfx(A3DDevice pDev, string szPath)
|
||
{
|
||
if ((m_dwModifier & CECAttackEvent.MOD_NULLITY) != 0)
|
||
{
|
||
// 程序联入\\击中\\无效攻击击中.gfx
|
||
// Program integration\\Hit\\Invalid attack hit.gfx
|
||
szPath = "程序联入\\击中\\无效攻击击中.gfx";
|
||
}
|
||
|
||
// TODO: return AfxGetGFXExMan()->LoadGfx(pDev, szPath);
|
||
return null;
|
||
}*/
|
||
|
||
/// <summary>
|
||
/// Set hit GFX
|
||
/// 设置命中特效
|
||
/// </summary>
|
||
/*public void SetHitGfx(A3DGFXEx pHitGfx)
|
||
{
|
||
// if (m_dwModifier & CECAttackEvent::MOD_CRITICAL_STRIKE)
|
||
// pHitGfx->SetActualScale(pHitGfx->GetScale() * 2.0f);
|
||
m_pHitGfx = pHitGfx;
|
||
}*/
|
||
|
||
private static bool get_pos_by_id(
|
||
EC_ManPlayer pPlayerMan,
|
||
CECNPCMan pNPCMan,
|
||
int nID,
|
||
out Vector3 vPos,
|
||
GfxHitPos HitPos,
|
||
bool bIsGoblinSkill = false,
|
||
string szHook = null,
|
||
bool bRelHook = false,
|
||
Vector3 pOffset = default,
|
||
string szHanger = null,
|
||
bool bChildHook = false)
|
||
{
|
||
vPos = Vector3.zero;
|
||
|
||
|
||
if (GPDataTypeHelper.ISPLAYERID(nID))
|
||
{
|
||
CECPlayer pPlayer = pPlayerMan?.GetPlayer(nID);
|
||
|
||
// Check if player exists AND GameObject is not destroyed (Unity's "fake null" handling)
|
||
if (pPlayer != null && pPlayer.gameObject != null)
|
||
{
|
||
{
|
||
if (bIsGoblinSkill)
|
||
{
|
||
// Get goblin model from player
|
||
// 从玩家获取小精灵模型
|
||
// Note: GetGoblin() method may need to be implemented in CECPlayer
|
||
// 注意:GetGoblin()方法可能需要在CECPlayer中实现
|
||
// For now, we'll try to get it via a common pattern
|
||
// 目前,我们将尝试通过通用模式获取它
|
||
|
||
// Try to find goblin model - this is a placeholder until GetGoblin() is implemented
|
||
// 尝试查找小精灵模型 - 这是占位符,直到实现GetGoblin()
|
||
// TODO: Implement GetGoblin() in CECPlayer when goblin system is available
|
||
// TODO: 当小精灵系统可用时,在CECPlayer中实现GetGoblin()
|
||
|
||
// For Phase 3, we'll search for a child model named "goblin" or similar
|
||
// 对于第3阶段,我们将搜索名为"goblin"或类似的子模型
|
||
CECModel pModel = pPlayer.GetPlayerCECModel();
|
||
if (pModel != null)
|
||
{
|
||
// Try common goblin hanger names
|
||
// 尝试常见的小精灵挂载者名称
|
||
CECModel goblinModel = pModel.GetChildModel("goblin") ??
|
||
pModel.GetChildModel("pet") ??
|
||
pModel.GetChildModel("_goblin");
|
||
|
||
if (goblinModel != null)
|
||
{
|
||
// Use hook if specified, otherwise use model center
|
||
// 如果指定了挂点则使用挂点,否则使用模型中心
|
||
if (!string.IsNullOrEmpty(szHook))
|
||
{
|
||
Transform pHook = goblinModel.GetHook(szHook, true);
|
||
if (pHook != null)
|
||
{
|
||
Transform modelTransform = goblinModel.transform;
|
||
vPos = HookUtils.GetHookWorldPosition(pHook, pOffset, bRelHook, modelTransform);
|
||
|
||
#if UNITY_EDITOR
|
||
BMLogger.Log($"[HOOK_DEBUG] Found goblin hook '{szHook}' for player ID {nID}, position={vPos}");
|
||
#endif
|
||
return true;
|
||
}
|
||
}
|
||
|
||
// Fallback to goblin position
|
||
// 回退到小精灵位置
|
||
if (goblinModel.transform != null)
|
||
{
|
||
vPos = goblinModel.transform.position;
|
||
vPos.y += 0.5f;
|
||
|
||
#if UNITY_EDITOR
|
||
BMLogger.Log($"[HOOK_DEBUG] Using goblin center position for player ID {nID}, position={vPos}");
|
||
#endif
|
||
return true;
|
||
}
|
||
}
|
||
}
|
||
|
||
#if UNITY_EDITOR
|
||
BMLogger.LogWarning($"[HOOK_DEBUG] Goblin model not found for player ID {nID}");
|
||
#endif
|
||
return false;
|
||
}
|
||
else
|
||
{
|
||
// currently hook does not affect the Goblin Skill
|
||
// 目前挂点不影响小精灵技能
|
||
while (true)
|
||
{
|
||
if (string.IsNullOrEmpty(szHook))
|
||
break;
|
||
|
||
// Get player model and hook position
|
||
// 获取玩家模型和挂点位置
|
||
CECModel pModel = pPlayer.GetPlayerCECModel();
|
||
if (pModel == null)
|
||
{
|
||
#if UNITY_EDITOR
|
||
BMLogger.LogWarning($"[HOOK_DEBUG] Player model not found for ID {nID}, hook '{szHook}'");
|
||
#endif
|
||
break;
|
||
}
|
||
|
||
// Handle child model (hanger) if specified
|
||
// 如果指定了子模型(挂载者),则处理
|
||
if (!string.IsNullOrEmpty(szHanger) && bChildHook)
|
||
{
|
||
pModel = pModel.GetChildModel(szHanger);
|
||
if (pModel == null)
|
||
{
|
||
#if UNITY_EDITOR
|
||
BMLogger.LogWarning($"[HOOK_DEBUG] Child model '{szHanger}' not found for player ID {nID}, hook '{szHook}'");
|
||
#endif
|
||
break;
|
||
}
|
||
}
|
||
|
||
// Get hook Transform (non-recursive search as per C++: GetSkeletonHook(szHook, true))
|
||
// 获取挂点变换(非递归搜索,对应C++:GetSkeletonHook(szHook, true))
|
||
Transform pHook = pModel.GetHook(szHook, false);
|
||
if (pHook == null)
|
||
{
|
||
#if UNITY_EDITOR
|
||
BMLogger.LogWarning($"[HOOK_DEBUG] Hook '{szHook}' not found for player ID {nID}, falling back to center position");
|
||
#endif
|
||
break;
|
||
}
|
||
|
||
// Get model transform for absolute offset calculation
|
||
// 获取模型变换用于绝对偏移计算
|
||
Transform modelTransform = pModel.transform;
|
||
|
||
// Calculate position based on relative/absolute offset
|
||
// 根据相对/绝对偏移计算位置
|
||
vPos = HookUtils.GetHookWorldPosition(pHook, pOffset, bRelHook, modelTransform);
|
||
|
||
#if UNITY_EDITOR
|
||
BMLogger.Log($"[HOOK_DEBUG] Found hook '{szHook}' for player ID {nID}, position={vPos}, relative={bRelHook}, offset={pOffset}");
|
||
#endif
|
||
|
||
return true;
|
||
}
|
||
|
||
if (HitPos == GfxHitPos.enumHitBottom)
|
||
{
|
||
vPos = pPlayer.GetPosVector3();
|
||
}
|
||
else
|
||
{
|
||
// TODO: Get player AABB
|
||
// const A3DAABB& aabb = pPlayer->GetPlayerAABB();
|
||
// vPos = aabb.Center;
|
||
// vPos.y += aabb.Extents.y * .5f;
|
||
vPos = pPlayer.GetPosVector3();
|
||
vPos.y += 1.0f; // Default height offset / 默认高度偏移
|
||
}
|
||
}
|
||
|
||
return true;
|
||
}
|
||
}
|
||
}
|
||
else if (GPDataTypeHelper.ISNPCID(nID))
|
||
{
|
||
CECNPC pNPC = pNPCMan?.GetNPCFromAll(nID);
|
||
|
||
// Check if NPC exists AND GameObject is not destroyed (Unity's "fake null" handling)
|
||
if (pNPC != null && pNPC.gameObject != null)
|
||
{
|
||
{
|
||
while (true)
|
||
{
|
||
if (string.IsNullOrEmpty(szHook))
|
||
break;
|
||
|
||
// Get NPC model
|
||
// 获取NPC模型
|
||
CECModel pModel = pNPC.GetModel();
|
||
if (pModel == null)
|
||
{
|
||
#if UNITY_EDITOR
|
||
BMLogger.LogWarning($"[HOOK_DEBUG] NPC model not found for ID {nID}, hook '{szHook}'");
|
||
#endif
|
||
break;
|
||
}
|
||
|
||
// Handle child model (hanger) if specified
|
||
// 如果指定了子模型(挂载者),则处理
|
||
if (!string.IsNullOrEmpty(szHanger) && bChildHook)
|
||
{
|
||
pModel = pModel.GetChildModel(szHanger);
|
||
if (pModel == null)
|
||
{
|
||
#if UNITY_EDITOR
|
||
BMLogger.LogWarning($"[HOOK_DEBUG] Child model '{szHanger}' not found for NPC ID {nID}, hook '{szHook}'");
|
||
#endif
|
||
break;
|
||
}
|
||
}
|
||
|
||
// Get hook Transform (non-recursive search as per C++: GetSkeletonHook(szHook, true))
|
||
// 获取挂点变换(非递归搜索,对应C++:GetSkeletonHook(szHook, true))
|
||
Transform pHook = pModel.GetHook(szHook, false);
|
||
if (pHook == null)
|
||
{
|
||
#if UNITY_EDITOR
|
||
BMLogger.LogWarning($"[HOOK_DEBUG] Hook '{szHook}' not found for NPC ID {nID}, falling back to center position");
|
||
#endif
|
||
break;
|
||
}
|
||
|
||
// Get model transform for absolute offset calculation
|
||
// 获取模型变换用于绝对偏移计算
|
||
Transform modelTransform = pModel.transform;
|
||
|
||
// Calculate position based on relative/absolute offset
|
||
// 根据相对/绝对偏移计算位置
|
||
vPos = HookUtils.GetHookWorldPosition(pHook, pOffset, bRelHook, modelTransform);
|
||
|
||
#if UNITY_EDITOR
|
||
BMLogger.Log($"[HOOK_DEBUG] Found hook '{szHook}' for NPC ID {nID}, position={vPos}, relative={bRelHook}, offset={pOffset}");
|
||
#endif
|
||
|
||
return true;
|
||
}
|
||
|
||
if (HitPos == GfxHitPos.enumHitBottom)
|
||
{
|
||
vPos = pNPC.GetPosVector3();
|
||
}
|
||
else
|
||
{
|
||
// TODO: Get NPC AABB
|
||
// const A3DAABB& aabb = pNPC->GetPickAABB();
|
||
// vPos = aabb.Center;
|
||
// vPos.y += aabb.Extents.y * .5f;
|
||
vPos = pNPC.GetPosVector3();
|
||
vPos.y += 1.0f; // Default height offset / 默认高度偏移
|
||
}
|
||
|
||
return true;
|
||
}
|
||
}
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Get direction and up vector by ID
|
||
/// 根据ID获取方向和上向量
|
||
/// </summary>
|
||
private static bool _get_dir_and_up_by_id(
|
||
EC_ManPlayer pPlayerMan,
|
||
CECNPCMan pNPCMan,
|
||
int nId,
|
||
out Vector3 vDir,
|
||
out Vector3 vUp)
|
||
{
|
||
vDir = Vector3.zero;
|
||
vUp = Vector3.zero;
|
||
|
||
if (GPDataTypeHelper.ISPLAYERID(nId))
|
||
{
|
||
CECPlayer pPlayer = pPlayerMan?.GetPlayer(nId);
|
||
|
||
// Check if player exists AND GameObject is not destroyed (Unity's "fake null" handling)
|
||
if (pPlayer != null && pPlayer.gameObject != null)
|
||
{
|
||
// TODO: Get player direction and up
|
||
// vDir = pPlayer->GetDir();
|
||
// vUp = pPlayer->GetUp();
|
||
vDir = pPlayer.transform.forward;
|
||
vUp = pPlayer.transform.up;
|
||
return true;
|
||
}
|
||
}
|
||
else if (GPDataTypeHelper.ISNPCID(nId))
|
||
{
|
||
CECNPC pNPC = pNPCMan?.GetNPCFromAll(nId);
|
||
|
||
// Check if NPC exists AND GameObject is not destroyed (Unity's "fake null" handling)
|
||
if (pNPC != null && pNPC.gameObject != null)
|
||
{
|
||
// TODO: Get NPC direction and up
|
||
// vDir = pNPC->GetDir();
|
||
// vUp = pNPC->GetUp();
|
||
vDir = pNPC.transform.forward;
|
||
vUp = pNPC.transform.up;
|
||
return true;
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
|
||
public static float _get_scale_by_id(
|
||
EC_ManPlayer pPlayerMan,
|
||
CECNPCMan pNPCMan,
|
||
int nID)
|
||
{
|
||
if (GPDataTypeHelper.ISPLAYERID(nID))
|
||
{
|
||
CECPlayer pPlayer = pPlayerMan?.GetPlayer(nID);
|
||
|
||
if (pPlayer != null)
|
||
{
|
||
// TODO: Get player AABB
|
||
// const A3DAABB& aabb = pPlayer->GetPlayerAABB();
|
||
// return aabb.Maxs.y - aabb.Mins.y;
|
||
return 1.0f;
|
||
}
|
||
}
|
||
else if (GPDataTypeHelper.ISNPCID(nID))
|
||
{
|
||
CECNPC pNPC = pNPCMan?.GetNPCFromAll(nID);
|
||
|
||
if (pNPC != null)
|
||
{
|
||
// TODO: Get NPC AABB
|
||
// const A3DAABB& aabb = pNPC->GetPickAABB();
|
||
// return aabb.Maxs.y - aabb.Mins.y;
|
||
return 1.0f;
|
||
}
|
||
}
|
||
|
||
return 1.0f;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Get ECModel GFX property by ID
|
||
/// 根据ID获取ECModel GFX属性
|
||
/// </summary>
|
||
public static bool _get_ecm_property_by_id(long nID, out ECMODEL_GFX_PROPERTY pProperty)
|
||
{
|
||
pProperty = new ECMODEL_GFX_PROPERTY();
|
||
int _32bitID = (int)nID;
|
||
|
||
if (GPDataTypeHelper.ISPLAYERID(_32bitID))
|
||
{
|
||
// TODO: Get player model properties
|
||
/*CECPlayer* pPlayer = g_pGame->GetGameRun()->GetWorld()->GetPlayerMan()->GetPlayer(_32bitID);
|
||
if (!pPlayer)
|
||
return false;
|
||
|
||
CECModel* pModel = pPlayer->GetPlayerModel();
|
||
if (!pModel)
|
||
return false;
|
||
|
||
pProperty->bGfxUseLod = pModel->IsGfxUseLOD();
|
||
pProperty->bGfxDisableCamShake = pModel->GetDisableCamShake();
|
||
pProperty->bHostECMCreatedByGfx = pModel->IsCreatedByGfx();*/
|
||
return true;
|
||
}
|
||
else if (GPDataTypeHelper.ISNPCID(_32bitID))
|
||
{
|
||
// TODO: Get NPC properties
|
||
/*CECNPC* pNPC = g_pGame->GetGameRun()->GetWorld()->GetNPCMan()->GetNPC(_32bitID);
|
||
if (!pNPC || !pNPC->HasModel())
|
||
return false;
|
||
pNPC->GetEcmProperty(&pProperty);*/
|
||
return true;
|
||
}
|
||
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Skill GFX Manager
|
||
/// 技能特效管理器
|
||
/// </summary>
|
||
public class SkillGfxMan : A3DSkillGfxMan
|
||
{
|
||
public static SkillGfxMan _instancesub;
|
||
|
||
public static SkillGfxMan InstanceSub
|
||
{
|
||
get
|
||
{
|
||
if (_instancesub == null)
|
||
{
|
||
_instancesub = new SkillGfxMan(EC_Game.GetGameRun());
|
||
}
|
||
return _instancesub;
|
||
}
|
||
set
|
||
{
|
||
_instancesub = value;
|
||
}
|
||
}
|
||
|
||
private const int DEFAULT_EVENT_BUF_SIZE = 10; // 默认事件缓冲区大小 / Default event buffer size
|
||
|
||
public LinkedList<CECSkillGfxEvent> m_EventLst; // 活动事件列表 / Active event list
|
||
protected LinkedList<CECSkillGfxEvent>[] m_FreeLst;
|
||
protected EC_ManPlayer m_pPlayerMan;
|
||
protected CECNPCMan m_pNPCMan;
|
||
|
||
// Track GFX instances that have m_bTraceTarget = true
|
||
// These are typically buff-related trail effects that persist until buff expires
|
||
// 跟踪具有m_bTraceTarget = true的GFX实例
|
||
// 这些通常是持续到buff结束的buff相关轨迹效果
|
||
private List<GameObject> m_TraceTargetGfxList = new List<GameObject>();
|
||
private Dictionary<GameObject, int> m_TraceTargetGfxSkillMap = new Dictionary<GameObject, int>();
|
||
|
||
public SkillGfxMan(CECGameRun pGameRun)
|
||
{
|
||
m_EventLst = new LinkedList<CECSkillGfxEvent>();
|
||
m_FreeLst = new LinkedList<CECSkillGfxEvent>[(int)GfxMoveMode.enumMoveModeNum];
|
||
|
||
// Initialize free lists for each move mode
|
||
// 为每种移动模式初始化空闲列表
|
||
for (int i = 0; i < (int)GfxMoveMode.enumMoveModeNum; i++)
|
||
{
|
||
m_FreeLst[i] = new LinkedList<CECSkillGfxEvent>();
|
||
|
||
for (int j = 0; j < DEFAULT_EVENT_BUF_SIZE; j++)
|
||
{
|
||
CECSkillGfxEvent pEvent = new CECSkillGfxEvent((GfxMoveMode)i);
|
||
m_FreeLst[i].AddLast(pEvent);
|
||
}
|
||
}
|
||
|
||
// TODO: Get managers from game run
|
||
// m_pPlayerMan = pGameRun?.GetWorld()?.GetPlayerMan();
|
||
// m_pNPCMan = pGameRun?.GetWorld()?.GetNPCMan();
|
||
m_pPlayerMan = EC_ManMessageMono.Instance?.GetECManPlayer;
|
||
m_pNPCMan = EC_ManMessageMono.Instance?.CECNPCMan;
|
||
}
|
||
|
||
public override void Dispose()
|
||
{
|
||
base.Dispose();
|
||
InstanceSub = null;
|
||
}
|
||
public A3DSkillGfxEvent GetEmptyEvent(GfxMoveMode mode)
|
||
{
|
||
int modeIndex = (int)mode;
|
||
|
||
if (m_FreeLst[modeIndex].Count == 0)
|
||
{
|
||
return new CECSkillGfxEvent(mode);
|
||
}
|
||
else
|
||
{
|
||
CECSkillGfxEvent pEvent = m_FreeLst[modeIndex].Last.Value;
|
||
m_FreeLst[modeIndex].RemoveLast();
|
||
pEvent.Resume();
|
||
return pEvent;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Push event to active list
|
||
/// 将事件推送到活动列表
|
||
/// </summary>
|
||
public void PushEvent(A3DSkillGfxEvent pEvent)
|
||
{
|
||
if (pEvent is CECSkillGfxEvent cecEvent)
|
||
{
|
||
m_EventLst.AddLast(cecEvent);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Get target scale by ID
|
||
/// 根据ID获取目标缩放
|
||
/// </summary>
|
||
public float GetTargetScale(long nTargetId)
|
||
{
|
||
return CECSkillGfxEvent._get_scale_by_id(m_pPlayerMan, m_pNPCMan, (int)nTargetId);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Get ECModel GFX property by ID
|
||
/// 根据ID获取ECModel GFX属性
|
||
/// </summary>
|
||
public override bool GetPropertyById(long nId, ref ECMODEL_GFX_PROPERTY pProperty)
|
||
{
|
||
return CECSkillGfxEvent._get_ecm_property_by_id(nId, out pProperty);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Release all events
|
||
/// 释放所有事件
|
||
/// </summary>
|
||
public void Release()
|
||
{
|
||
// Release active events
|
||
// 释放活动事件
|
||
m_EventLst.Clear();
|
||
|
||
// Release free lists
|
||
// 释放空闲列表
|
||
for (int i = 0; i < (int)GfxMoveMode.enumMoveModeNum; i++)
|
||
{
|
||
m_FreeLst[i].Clear();
|
||
}
|
||
|
||
// Clean up trace target GFX
|
||
// 清理跟踪目标GFX
|
||
RemoveAllTraceTargetGfx();
|
||
}
|
||
|
||
/// <summary>
|
||
/// Add a GFX instance to trace target tracking list
|
||
/// 将GFX实例添加到跟踪目标跟踪列表
|
||
/// </summary>
|
||
public void AddTraceTargetGfx(GameObject gfxInstance, int skillId)
|
||
{
|
||
if (gfxInstance == null) return;
|
||
|
||
if (!m_TraceTargetGfxList.Contains(gfxInstance))
|
||
{
|
||
m_TraceTargetGfxList.Add(gfxInstance);
|
||
m_TraceTargetGfxSkillMap[gfxInstance] = skillId;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Remove all trace target GFX (called when buff states update)
|
||
/// 移除所有跟踪目标GFX(在buff状态更新时调用)
|
||
/// </summary>
|
||
public void RemoveAllTraceTargetGfx()
|
||
{
|
||
foreach (GameObject gfx in m_TraceTargetGfxList)
|
||
{
|
||
if (gfx != null)
|
||
{
|
||
GameObject.Destroy(gfx, 1);
|
||
}
|
||
}
|
||
|
||
m_TraceTargetGfxList.Clear();
|
||
m_TraceTargetGfxSkillMap.Clear();
|
||
}
|
||
|
||
/// <summary>
|
||
/// Remove trace target GFX for specific skill
|
||
/// 移除特定技能的跟踪目标GFX
|
||
/// </summary>
|
||
public void RemoveTraceTargetGfxForSkill(int skillId)
|
||
{
|
||
List<GameObject> toRemove = new List<GameObject>();
|
||
|
||
foreach (var kvp in m_TraceTargetGfxSkillMap)
|
||
{
|
||
if (kvp.Value == skillId)
|
||
{
|
||
toRemove.Add(kvp.Key);
|
||
}
|
||
}
|
||
|
||
foreach (GameObject gfx in toRemove)
|
||
{
|
||
if (gfx != null)
|
||
{
|
||
GameObject.Destroy(gfx);
|
||
}
|
||
m_TraceTargetGfxList.Remove(gfx);
|
||
m_TraceTargetGfxSkillMap.Remove(gfx);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Check if a GFX instance is tracked as trace target GFX
|
||
/// 检查GFX实例是否被跟踪为跟踪目标GFX
|
||
/// </summary>
|
||
public bool IsTraceTargetGfx(GameObject gfx)
|
||
{
|
||
return m_TraceTargetGfxList.Contains(gfx);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Clean up null references (GFX destroyed elsewhere)
|
||
/// 清理空引用(在其他地方销毁的GFX)
|
||
/// </summary>
|
||
public void CleanupTraceTargetGfx()
|
||
{
|
||
m_TraceTargetGfxList.RemoveAll(gfx => gfx == null);
|
||
|
||
List<GameObject> nullKeys = new List<GameObject>();
|
||
foreach (var kvp in m_TraceTargetGfxSkillMap)
|
||
{
|
||
if (kvp.Key == null)
|
||
{
|
||
nullKeys.Add(kvp.Key);
|
||
}
|
||
}
|
||
foreach (var key in nullKeys)
|
||
{
|
||
m_TraceTargetGfxSkillMap.Remove(key);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Tick update all events
|
||
/// 更新所有事件
|
||
/// </summary>
|
||
public bool Tick(uint dwDeltaTime)
|
||
{
|
||
var node = m_EventLst.First;
|
||
|
||
while (node != null)
|
||
{
|
||
var nextNode = node.Next;
|
||
CECSkillGfxEvent pEvent = node.Value;
|
||
|
||
if (pEvent.IsFinished())
|
||
{
|
||
m_EventLst.Remove(node);
|
||
pEvent.Resume();
|
||
m_FreeLst[(int)pEvent.GetMode()].AddLast(pEvent);
|
||
}
|
||
else
|
||
{
|
||
pEvent.Tick(dwDeltaTime);
|
||
}
|
||
|
||
node = nextNode;
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Render all events
|
||
/// 渲染所有事件
|
||
/// </summary>
|
||
public bool Render(CECViewport pViewport)
|
||
{
|
||
/*var node = m_EventLst.First;
|
||
|
||
while (node != null)
|
||
{
|
||
node.Value.Render();
|
||
node = node.Next;
|
||
}*/
|
||
|
||
return true;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Manager wrapper for SkillGfxMan
|
||
/// SkillGfxMan的管理器包装类
|
||
/// </summary>
|
||
public class CECSkillGfxMan : CECManager
|
||
{
|
||
protected SkillGfxMan m_GfxMan; // 技能特效管理器 / Skill GFX manager
|
||
|
||
public CECSkillGfxMan(CECGameRun pGameRun) : base(pGameRun)
|
||
{
|
||
m_GfxMan = new SkillGfxMan(pGameRun);
|
||
// TODO: m_GfxMan.Init(g_pGame->GetA3DDevice());
|
||
}
|
||
|
||
/// <summary>
|
||
/// Get the internal SkillGfxMan pointer
|
||
/// 获取内部SkillGfxMan指针
|
||
/// </summary>
|
||
public A3DSkillGfxMan GetPtr()
|
||
{
|
||
return m_GfxMan;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Release resources
|
||
/// 释放资源
|
||
/// </summary>
|
||
public virtual void Release()
|
||
{
|
||
m_GfxMan.Release();
|
||
}
|
||
|
||
/// <summary>
|
||
/// Tick update
|
||
/// 更新
|
||
/// </summary>
|
||
public virtual bool Tick(uint dwDeltaTime)
|
||
{
|
||
return m_GfxMan.Tick(dwDeltaTime);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Render
|
||
/// 渲染
|
||
/// </summary>
|
||
public bool Render(CECViewport pViewport)
|
||
{
|
||
return m_GfxMan.Render(pViewport);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Add skill GFX event
|
||
/// 添加技能特效事件
|
||
/// </summary>
|
||
/// <summary>
|
||
/// Convenience overload for weapon/melee attacks (no composer).
|
||
/// Scale is not needed — Unity Particle Systems handle their own scale.
|
||
/// 武器/近战攻击的便捷重载(无组合器)。不需要缩放 — Unity粒子系统自己处理缩放。
|
||
/// </summary>
|
||
public bool AddSkillGfxEvent(
|
||
int nHostID,
|
||
int nTargetID,
|
||
string szFlyGfx,
|
||
string szHitGfx,
|
||
uint dwFlyTimeSpan,
|
||
bool bTraceTarget = true,
|
||
GfxMoveMode FlyMode = GfxMoveMode.enumLinearMove,
|
||
int nFlyGfxCount = 1,
|
||
uint dwInterval = 0,
|
||
GFX_SKILL_PARAM? param = null,
|
||
uint dwModifier = 0)
|
||
{
|
||
return m_GfxMan.AddSkillGfxEvent(
|
||
null,
|
||
nHostID,
|
||
nTargetID,
|
||
szFlyGfx,
|
||
szHitGfx,
|
||
dwFlyTimeSpan,
|
||
bTraceTarget,
|
||
FlyMode,
|
||
nFlyGfxCount,
|
||
dwInterval,
|
||
param ?? default,
|
||
dwModifier,
|
||
false, // bOnlyOneHit
|
||
false, // bFadeOut
|
||
false, // bIsGoblinSkill
|
||
false // bReverse
|
||
);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Viewport placeholder for CECViewport
|
||
/// CECViewport的占位符类
|
||
/// </summary>
|
||
public class CECViewport
|
||
{
|
||
// TODO: Implement viewport functionality
|
||
// 待实现:视口功能
|
||
}
|
||
|
||
/// <summary>
|
||
/// Optimize settings placeholder
|
||
/// 优化设置占位符
|
||
/// </summary>
|
||
public class CECOptimize
|
||
{
|
||
private static CECOptimize _instance;
|
||
public static CECOptimize Instance => _instance ?? (_instance = new CECOptimize());
|
||
|
||
private GFXOptimize _gfxOptimize = new GFXOptimize();
|
||
|
||
public GFXOptimize GetGFX()
|
||
{
|
||
return _gfxOptimize;
|
||
}
|
||
|
||
public class GFXOptimize
|
||
{
|
||
public bool CanShowHit(long hostId)
|
||
{
|
||
// TODO: Implement optimization check
|
||
// 待实现:优化检查
|
||
return true;
|
||
}
|
||
|
||
public bool CanShowFly(long hostId)
|
||
{
|
||
// TODO: Implement optimization check
|
||
// 待实现:优化检查
|
||
return true;
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Skill GFX Composer position info
|
||
/// 技能特效组合器位置信息
|
||
/// </summary>
|
||
public class SGC_POS_INFO
|
||
{
|
||
public GfxHitPos HitPos; // 命中位置类型 / Hit position type
|
||
public string szHook; // 挂点名称 / Hook name
|
||
public bool bRelHook; // 是否相对挂点 / Is relative to hook
|
||
public Vector3 vOffset; // 偏移量 / Offset
|
||
public string szHanger; // 挂载者名称 / Hanger name
|
||
public bool bChildHook; // 是否子挂点 / Is child hook
|
||
|
||
public SGC_POS_INFO()
|
||
{
|
||
HitPos = GfxHitPos.enumHitCenter;
|
||
szHook = null;
|
||
bRelHook = false;
|
||
vOffset = Vector3.zero;
|
||
szHanger = null;
|
||
bChildHook = false;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Extended A3DSkillGfxComposer with position info
|
||
/// 扩展的A3DSkillGfxComposer(带位置信息)
|
||
/// </summary>
|
||
public partial class A3DSkillGfxComposer
|
||
{
|
||
public SGC_POS_INFO m_FlyPos = new SGC_POS_INFO(); // 飞行起始位置信息 / Fly start position info
|
||
public SGC_POS_INFO m_FlyEndPos = new SGC_POS_INFO(); // 飞行结束位置信息 / Fly end position info
|
||
public SGC_POS_INFO m_HitPos = new SGC_POS_INFO(); // 命中位置信息 / Hit position info
|
||
}
|
||
}
|
||
|