Files
test/Assets/PerfectWorld/Scripts/Managers/CECSkillGfxMan.cs
T
vuong dinh hoang a2bbdcb48e adjust gfx
2026-05-20 18:14:01 +07:00

1396 lines
52 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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)
{
#region agent log
#if UNITY_EDITOR || DEVELOPMENT_BUILD
DebugSessionLog.Write("CECSkillGfxEvent.Tick", "wait_to_flying", "D",
new DebugSessionPayload
{
eventId = m_debugEventId,
prevState = prevState.ToString(),
newState = m_enumState.ToString(),
frame = Time.frameCount
});
#endif
#endregion
#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;*/
BMLogger.LogError($"[SKILL_GFX_DEBUG] SpawnFlyGfx: Fly GFX "+ m_pComposer.flyGfxName);
#region agent log
#if UNITY_EDITOR || DEVELOPMENT_BUILD
DebugSessionLog.Write("CECSkillGfxEvent.SpawnFlyGfx", "spawn_fly_gfx", "D",
new DebugSessionPayload
{
eventId = m_debugEventId,
hostId = (int)m_nHostID,
targetId = (int)m_nTargetID,
flyGfx = m_pComposer?.flyGfxName ?? "",
frame = Time.frameCount
});
#endif
#endregion
m_flyGfxInstance = GameObject.Instantiate(prefab, pos, prefab.transform.rotation);
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
}
}