This commit is contained in:
VDH
2026-02-24 09:50:08 +07:00
parent af44545093
commit e9522193c6
13 changed files with 1961 additions and 452 deletions
@@ -1,7 +0,0 @@
fileFormatVersion: 2
guid: d0c06c588e2a6442488a3542551fb243
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -5,6 +5,11 @@ namespace BrewMonster
{
public static partial class SkillStubs
{
public static void Init()
{
int i = 0;
}
// Skill stub declarations
public static Skill1Stub __stub_Skill1Stub = new Skill1Stub();
public static Skill2Stub __stub_Skill2Stub = new Skill2Stub();
@@ -166,9 +166,6 @@ namespace BrewMonster
public float GetAttackdistance(Skill skill) => 0f;
public float GetAngle(Skill skill) => (float)(1 - 0.0111111 * 0);
public override float GetPraydistance(Skill skill) => (float)(skill.GetPlayer().GetRange());
public override int GetRequiredLevel(Skill skill) => RequiredLevelArray[skill.GetLevel() - 1];
public override int GetRequiredSp(Skill skill) => RequiredSpArray[skill.GetLevel() - 1];
public override int GetRequiredMoney(Skill skill) => RequiredMoneyArray[skill.GetLevel() - 1];
#if SKILL_CLIENT
public override int GetIntroduction(Skill skill, StringBuilder buffer, string format)
@@ -1,193 +0,0 @@
#define SKILL_CLIENT
using BrewMonster.Scripts.Skills;
using CSNetwork.GPDataType;
using System.Collections.Generic;
using System.Text;
using UnityEngine;
using static BrewMonster.PET_EVOLVE_CONFIG;
namespace BrewMonster
{
#if SKILL_SERVER
public class Skill1 : Skill
{
public const int SKILL_ID = 1;
public Skill1() : base(SKILL_ID)
{
}
}
#endif
public class Skill1Stub : SkillStub
{
private static readonly int[] RequiredLevelArray = { 0, 5, 10, 15, 20, 25, 30, 35, 40, 45 };
private static readonly int[] RequiredSpArray = { 0, 300, 1200, 2800, 5200, 8400, 12800, 18600, 26300, 36500 };
private static readonly int[] RequiredMoneyArray = { 0, 30, 130, 280, 480, 730, 1180, 1630, 2080, 2580 };
#if SKILL_SERVER
public class State1 : SkillStub.State
{
public int GetTime(Skill skill) => 400;
public bool Quit(Skill skill) => false;
public bool Loop(Skill skill) => false;
public bool Bypass(Skill skill) => false;
public void Calculate(Skill skill)
{
skill.GetPlayer().SetDecmp(0.2f *(-5 + 7 * skill.GetLevel()));
skill.GetPlayer().SetPray(1);
}
public bool Interrupt(Skill skill) => false;
public bool Cancel(Skill skill) => true;
public bool Skip(Skill skill) => false;
}
#endif
#if SKILL_SERVER
public class State2 : SkillStub.State
{
public int GetTime(Skill skill) => 700;
public bool Quit(Skill skill) => false;
public bool Loop(Skill skill) => false;
public bool Bypass(Skill skill) => false;
public void Calculate(Skill skill)
{
skill.GetPlayer().SetDecmp(0.8f *(-5 + 7 * skill.GetLevel()));
skill.SetPlus(1.9f * skill.GetLevel() * skill.GetLevel() + 64 * skill.GetLevel() + 36.7f);
skill.SetRatio(0);
skill.SetDamage(skill.GetAttack());
skill.GetPlayer().SetPerform(1);
}
public bool Interrupt(Skill skill) => false;
public bool Cancel(Skill skill) => false;
public bool Skip(Skill skill) => false;
}
#endif
#if SKILL_SERVER
public class State3 : SkillStub.State
{
public int GetTime(Skill skill) => 0;
public bool Quit(Skill skill) => false;
public bool Loop(Skill skill) => false;
public bool Bypass(Skill skill) => false;
public void Calculate(Skill skill)
{
}
public bool Interrupt(Skill skill) => false;
public bool Cancel(Skill skill) => false;
public bool Skip(Skill skill) => false;
}
#endif
public Skill1Stub() : base(1)
{
cls = 0;
name = "虎击";
nativename = "虎击";
icon = "虎击";
max_level = 10;
type = 1;
apcost = 0;
arrowcost = 0;
apgain = 10;
attr = 1;
rank = 0;
eventflag = 0;
posdouble = 0;
clslimit = 0;
time_type = 0;
showorder = 1101;
allow_land = true;
allow_air = true;
allow_water = true;
allow_ride = false;
auto_attack = true;
long_range = 0;
restrict_corpse = 0;
allow_forms = 1;
effect = "虎击";
doenchant = 0;
dobless = 0;
commoncooldown = 0;
commoncooldowntime = 0;
m_szFlyGfxPath = string.Empty;
m_szHitGrndGfxPath = string.Empty;
m_szHitGfxPath = "策划联入/人物技能/击中/虎击击中.gfx";
// GFX Movement and Behavior Parameters / GFX移动和行为参数
m_MoveMode = (GfxMoveMode)0;
m_TargetMode = (GfxTargetMode)0;
m_AttFlyMode = (GfxAttackMode)0;
m_AttHitMode = (GfxAttackMode)0;
m_dwFlyTime = 0;
m_bTraceTarget = false;
m_FlyClusterCount = 1;
m_FlyClusterInterval = 0;
m_HitClusterCount = 1;
m_HitClusterInterval = 0;
m_bOneHit = true;
m_bFadeOut = false;
m_bRelScl = false;
m_fDefTarScl = 1.8f;
// Area parameters (commented out) / 区域参数(已注释)
// m_bArea = false;
// m_Shape = (EmitShape)0;
// m_vSize = new Vector3(0.0f, 0.0f, 0.0f);
// Param (commented out) / 参数(已注释)
// m_paramType = (GfxSkillValType)1;
// m_param = new GFX_SKILL_PARAM();
// m_param.nVal = 0;
restrict_weapons.Add(0);
restrict_weapons.Add(1);
restrict_weapons.Add(182);
restrict_weapons.Add(5);
restrict_weapons.Add(292);
restrict_weapons.Add(9);
range = new Range();
range.type = 0;
#if SKILL_SERVER
statestub.Add(new State1());
statestub.Add(new State2());
statestub.Add(new State3());
#endif
}
~Skill1Stub() { }
public override float GetMpcost(Skill skill) => (float)(-5 + 7 * skill.GetLevel());
public override int GetExecutetime(Skill skill) => 700;
public override int GetCoolingtime(Skill skill) => 3000;
public float GetRadius(Skill skill) => 0f;
public float GetAttackdistance(Skill skill) => 0f;
public float GetAngle(Skill skill) => (float)(1 - 0.0111111 * 0);
public override float GetPraydistance(Skill skill) => (float)(skill.GetPlayer().GetRange());
public override int GetRequiredLevel(Skill skill) => RequiredLevelArray[skill.GetLevel() - 1];
public override int GetRequiredSp(Skill skill) => RequiredSpArray[skill.GetLevel() - 1];
public override int GetRequiredMoney(Skill skill) => RequiredMoneyArray[skill.GetLevel() - 1];
#if SKILL_CLIENT
public override int GetIntroduction(Skill skill, StringBuilder buffer, string format)
{
buffer.Append(GPDataTypeHelper.ReplacePercentD(format,
skill.GetLevel(),
-5 + 7 * skill.GetLevel(),
1.9 * skill.GetLevel() * skill.GetLevel() + 64 * skill.GetLevel() + 36.7));
return buffer.Length;
}
#endif
#if SKILL_SERVER
public int GetEnmity(Skill skill) => 0;
public bool TakeEffect(Skill skill) => true;
public float GetEffectdistance(Skill skill) => 13.3f;
public int GetAttackspeed(Skill skill) => 3;
public float GetHitrate(Skill skill) => 1.2f + 0.05f * skill.GetLevel();
#endif
}
}
+1
View File
@@ -116,6 +116,7 @@ public partial class CECGameRun
private void LoadPrefabs()
{
BMLogger.LogError("CECGameRun::LoadPrefabs, Loading prefabs from Resources. Consider using Addressables for better performance and memory management.");
_playerPrefab = Resources.Load<GameObject>(AddressResourceConfig.PlayerPrefab);
_monsterPrefab = Resources.Load<GameObject>(AddressResourceConfig.MonsterPrefab);
_npcServerPrefab = Resources.Load<GameObject>(AddressResourceConfig.NpcServerPrefab);
+224 -138
View File
@@ -52,7 +52,7 @@ A3DSkillGfxMan.AddSkillGfxEvent() - Creates GFX events with clustering ✅
A3DSkillGfxEvent.Tick() - State machine (Wait → Flying → Hit → Finished) ⚠️ COMMENTED OUT
CGfxMoveBase / IGfxMovement - Movement calculation ❌ NOT CREATED
CGfxMoveBase - Movement calculation ❌ NOT CREATED
GFX Spawning (fly/hit) - Instantiate prefabs ❌ NOT IMPLEMENTED
```
@@ -169,7 +169,7 @@ GFX Spawning (fly/hit) - Instantiate prefabs ❌ NOT IMPLEMENTED
### 2.3 What's Completely Missing ❌
1. **Movement System (CGfxMoveBase)**
- No `IGfxMovement` interface
- No `CGfxMoveBase` abstract class
- No movement implementations (Linear, Parabolic, Missile, etc.)
- `m_pMoveMethod` referenced in comments but never created
- All movement-related calls are commented out
@@ -278,140 +278,226 @@ event.Tick() → mostly no-op event.Tick() → full state machine:
**New Files:**
**`Assets/PerfectWorld/Scripts/Vfx/IGfxMovement.cs`**
**`Assets/PerfectWorld/Scripts/Vfx/CGfxMoveBase.cs`** (same name as C++)
```csharp
public interface IGfxMovement
// Mirrors C++ CGfxMoveBase exactly:
// - StartMove / TickMove are pure virtual (= 0) → abstract
// - SetParam / SetIsCluster / SetReverse / UpdateGfxParam are virtual with default impl → virtual
// - GetMode / GetPos / GetMoveDir / IsReverse / SetMaxFlyTime are non-virtual → regular methods
public abstract class CGfxMoveBase
{
void SetMaxFlyTime(uint dwMaxFlyTime);
void StartMove(Vector3 vHost, Vector3 vTarget);
bool TickMove(float deltaTimeMs, Vector3 vHostPos, Vector3 vTargetPos);
Vector3 GetPos();
Vector3 GetMoveDir();
GfxMoveMode GetMode();
GfxHitPos GetHitPos();
bool IsReverse();
void SetReverse(bool bReverse);
void SetParam(GFX_SKILL_PARAM param);
void SetIsCluster(bool bCluster);
// Fields (same as C++)
protected GfxMoveMode m_Mode;
protected GfxHitPos m_HitPos = GfxHitPos.enumHitCenter;
protected Vector3 m_vPos;
protected Vector3 m_vMoveDir;
protected bool m_bOneOfCluser; // C++ spelling kept
protected uint m_dwMaxFlyTime;
protected bool m_bReverse;
protected bool m_bArea;
protected EmitShape m_Shape;
protected Vector3 m_vSize;
protected Vector3 m_vXRange;
protected Vector3 m_vYRange;
protected Vector3 m_vZRange;
protected float m_fSquare;
protected float m_fSquareH;
protected CGfxMoveBase(GfxMoveMode mode)
{
m_Mode = mode;
m_HitPos = GfxHitPos.enumHitCenter;
}
// Pure virtual (= 0) → abstract — subclasses MUST override
public abstract void StartMove(Vector3 vHost, Vector3 vTarget);
public abstract bool TickMove(uint dwDeltaTime, Vector3 vHostPos, Vector3 vTargetPos);
// Protected helpers (same as C++ CGfxMoveBase)
protected static float Normalize(ref Vector3 v)
{
float mag = v.magnitude;
if (mag < 1e-6f) { v = Vector3.zero; return 0f; }
v /= mag;
return mag;
}
protected void CalcRange(Vector3 vDir)
{
m_vYRange = Vector3.up;
m_vZRange = new Vector3(vDir.x, 0, vDir.z);
if (Normalize(ref m_vZRange) < 0.01f) m_vZRange = Vector3.forward;
m_vXRange = Vector3.Cross(m_vYRange, m_vZRange);
m_vXRange *= m_vSize.x;
m_vYRange *= m_vSize.y;
m_vZRange *= m_vSize.z;
m_fSquare = m_vSize.sqrMagnitude;
m_fSquareH = m_vSize.x * m_vSize.x + m_vSize.z * m_vSize.z;
}
protected Vector3 GetRandOff()
{
// Mirrors C++ GetRandOff() with enumBox/enumSphere/enumCylinder
float x, y, z;
if (m_Shape == EmitShape.enumBox)
return SymRandom() * m_vXRange + SymRandom() * m_vYRange + SymRandom() * m_vZRange;
else if (m_Shape == EmitShape.enumSphere)
{
do { x = SymRandom(); y = SymRandom(); z = SymRandom(); }
while (x*x + y*y + z*z > 1f);
return x * m_vXRange + y * m_vYRange + z * m_vZRange;
}
else if (m_Shape == EmitShape.enumCylinder)
{
y = SymRandom();
do { x = SymRandom(); z = SymRandom(); }
while (x*x + z*z > 1f);
return x * m_vXRange + y * m_vYRange + z * m_vZRange;
}
return Vector3.zero;
}
private static float SymRandom() { return UnityEngine.Random.value * 2f - 1f; }
// Virtual with default implementation — subclasses CAN override, but don't have to
public virtual void SetParam(GFX_SKILL_PARAM param)
{
m_bArea = param.m_bArea;
m_Shape = param.m_Shape;
m_vSize = new Vector3(param.m_vSize.x, param.m_vSize.y, param.m_vSize.z);
}
public virtual void SetIsCluster(bool bCluster) { m_bOneOfCluser = bCluster; }
public virtual void SetReverse(bool bReverse) { m_bReverse = bReverse; }
public virtual void UpdateGfxParam(Vector3 vHost, Vector3 vTarget) { }
// Non-virtual — same as C++ (no override possible)
public GfxMoveMode GetMode() { return m_Mode; }
public GfxHitPos GetHitPos() { return m_HitPos; }
public Vector3 GetPos() { return m_vPos; }
public Vector3 GetMoveDir() { return m_vMoveDir; }
public bool IsReverse() { return m_bReverse; }
public void SetMaxFlyTime(uint dwTime) { m_dwMaxFlyTime = dwTime; if (m_dwMaxFlyTime == 0) m_dwMaxFlyTime = 1; }
public void SetIsArea(bool bArea) { m_bArea = bArea; }
public void SetShape(EmitShape shape) { m_Shape = shape; }
public void SetRange(Vector3 vSize) { m_vSize = vSize; }
// Factory method (same as C++ CGfxMoveBase::CreateMoveMethod)
public static CGfxMoveBase CreateMoveMethod(GfxMoveMode mode)
{
switch (mode)
{
case GfxMoveMode.enumLinearMove: return new CGfxLinearMove(mode);
case GfxMoveMode.enumOnTarget: return new CGfxOnTargetMove(mode);
// Phase 2: add more modes
default: return new CGfxOnTargetMove(mode);
}
}
}
```
**`Assets/PerfectWorld/Scripts/Vfx/GfxLinearMove.cs`** (C++ ref: A3DSkillGfxEvent2.cpp:22-52)
**`Assets/PerfectWorld/Scripts/Vfx/CGfxLinearMove.cs`** (C++ ref: A3DSkillGfxEvent2.cpp:22-52)
```csharp
public class GfxLinearMove : IGfxMovement
{
private Vector3 m_vPos;
private Vector3 m_vMoveDir;
private float m_fSpeed;
private uint m_dwMaxFlyTime;
private bool m_bReverse;
private const float _fly_speed = 20.0f; // units/sec
public void SetMaxFlyTime(uint dwMaxFlyTime) { m_dwMaxFlyTime = dwMaxFlyTime; }
public void StartMove(Vector3 vHost, Vector3 vTarget)
{
m_vPos = vHost;
m_vMoveDir = vTarget - m_vPos;
float fDist = m_vMoveDir.magnitude;
if (fDist < 1e-4f) { m_vMoveDir = Vector3.forward; m_fSpeed = _fly_speed; return; }
m_vMoveDir /= fDist;
float fMax = _fly_speed / 1000f * m_dwMaxFlyTime;
m_fSpeed = fMax >= fDist ? _fly_speed / 1000f : fDist / m_dwMaxFlyTime;
}
public bool TickMove(float deltaTimeMs, Vector3 vHostPos, Vector3 vTargetPos)
{
Vector3 vFlyDir = vTargetPos - m_vPos;
float fDist = vFlyDir.magnitude;
if (fDist < 1e-4f) return true;
vFlyDir /= fDist;
float fFlyDist = m_fSpeed * deltaTimeMs;
if (fFlyDist >= fDist) return true;
m_vPos += vFlyDir * fFlyDist;
m_vMoveDir = vFlyDir;
return false;
}
public Vector3 GetPos() => m_vPos;
public Vector3 GetMoveDir() => m_vMoveDir;
public GfxMoveMode GetMode() => GfxMoveMode.enumLinearMove;
public GfxHitPos GetHitPos() => GfxHitPos.enumHitCenter;
public bool IsReverse() => m_bReverse;
public void SetReverse(bool b) { m_bReverse = b; }
public void SetParam(GFX_SKILL_PARAM param) { }
public void SetIsCluster(bool b) { }
}
```
**`Assets/PerfectWorld/Scripts/Vfx/GfxOnTargetMove.cs`** (for instant-hit skills like 虎击)
```csharp
public class GfxOnTargetMove : IGfxMovement
{
private Vector3 m_vPos;
private Vector3 m_vMoveDir;
private bool m_bReverse;
public void SetMaxFlyTime(uint dwMaxFlyTime) { }
public void StartMove(Vector3 vHost, Vector3 vTarget)
{
m_vPos = vTarget; // Instantly at target
m_vMoveDir = (vTarget - vHost);
if (m_vMoveDir.magnitude > 1e-4f) m_vMoveDir.Normalize();
else m_vMoveDir = Vector3.forward;
}
public bool TickMove(float deltaTimeMs, Vector3 vHostPos, Vector3 vTargetPos)
{
m_vPos = vTargetPos;
return true; // Instantly hits
}
public Vector3 GetPos() => m_vPos;
public Vector3 GetMoveDir() => m_vMoveDir;
public GfxMoveMode GetMode() => GfxMoveMode.enumOnTarget;
public GfxHitPos GetHitPos() => GfxHitPos.enumHitCenter;
public bool IsReverse() => m_bReverse;
public void SetReverse(bool b) { m_bReverse = b; }
public void SetParam(GFX_SKILL_PARAM param) { }
public void SetIsCluster(bool b) { }
}
```
**`Assets/PerfectWorld/Scripts/Vfx/GfxMoveFactory.cs`**
```csharp
public static class GfxMoveFactory
// Mirrors C++ CGfxLinearMove exactly (A3DSkillGfxEvent2.cpp:22-52)
public class CGfxLinearMove : CGfxMoveBase
{
public static IGfxMovement Create(GfxMoveMode mode)
protected float m_fSpeed;
private const float _fly_speed = 20.0f / 1000.0f; // same as C++
public CGfxLinearMove(GfxMoveMode mode) : base(mode) { }
public override void StartMove(Vector3 vHost, Vector3 vTarget)
{
switch (mode)
if (m_bArea)
{
case GfxMoveMode.enumLinearMove: return new GfxLinearMove();
case GfxMoveMode.enumOnTarget: return new GfxOnTargetMove();
// Phase 2: add more modes
default: return new GfxLinearMove();
CalcRange((vTarget - vHost).normalized);
m_vPos = vHost + GetRandOff();
}
else
m_vPos = vHost;
m_vMoveDir = vTarget - m_vPos;
float fDist = Normalize(ref m_vMoveDir);
float fMax = _fly_speed * m_dwMaxFlyTime;
if (fMax >= fDist)
m_fSpeed = _fly_speed;
else
m_fSpeed = fDist / m_dwMaxFlyTime;
}
public override bool TickMove(uint dwDeltaTime, Vector3 vHostPos, Vector3 vTargetPos)
{
Vector3 vFlyDir = vTargetPos - m_vPos;
float fDist = Normalize(ref vFlyDir);
float fFlyDist = m_fSpeed * dwDeltaTime;
if (fFlyDist >= fDist) return true; // target hit
m_vPos += vFlyDir * fFlyDist;
m_vMoveDir = vFlyDir;
return false;
}
}
```
**`Assets/PerfectWorld/Scripts/Vfx/CGfxOnTargetMove.cs`** (for instant-hit skills like 虎击)
```csharp
// Mirrors C++ CGfxOnTargetMove exactly (A3DSkillGfxEvent2.cpp:297-322)
public class CGfxOnTargetMove : CGfxMoveBase
{
protected float m_fRadius;
protected Vector3 m_vOffset;
public CGfxOnTargetMove(GfxMoveMode mode) : base(mode) { m_HitPos = GfxHitPos.enumHitBottom; m_fRadius = 0; }
public override void StartMove(Vector3 vHost, Vector3 vTarget)
{
m_vPos = vTarget;
m_vMoveDir = vTarget - vHost;
m_vMoveDir.y = 0; // C++: zero out Y before normalize
if (Normalize(ref m_vMoveDir) == 0)
m_vMoveDir = Vector3.forward; // _unit_z
if (m_bOneOfCluser)
{
float fRandAng = UnityEngine.Random.value * Mathf.PI * 2f;
float fRadius = UnityEngine.Random.value * m_fRadius;
m_vOffset.x = Mathf.Cos(fRandAng) * fRadius;
m_vOffset.z = Mathf.Sin(fRandAng) * fRadius;
m_vOffset.y = 0;
m_vPos += m_vOffset;
}
else
m_vOffset = Vector3.zero;
}
public override bool TickMove(uint dwDeltaTime, Vector3 vHostPos, Vector3 vTargetPos)
{
m_vPos = vTargetPos + m_vOffset;
return false; // C++ returns false — hit is triggered by fly time timeout, NOT by TickMove
}
public override void SetParam(GFX_SKILL_PARAM param)
{
base.SetParam(param);
m_fRadius = param.value.fVal; // C# union access: param.value.fVal
}
}
```
*(No separate factory file needed — `CGfxMoveBase.CreateMoveMethod()` handles creation, same as C++)*
#### Task 1.2: Un-comment & Wire Up Event State Machine (1-2 days)
**Changes to `A3DSkillGfxMan.cs` — `A3DSkillGfxEvent` class:**
1. Add `m_pMoveMethod` field (un-comment):
```csharp
protected IGfxMovement m_pMoveMethod;
protected CGfxMoveBase m_pMoveMethod;
```
2. In constructor, create movement:
```csharp
m_pMoveMethod = GfxMoveFactory.Create(mode);
m_pMoveMethod = CGfxMoveBase.CreateMoveMethod(mode);
```
3. Un-comment the state machine in `Tick()`:
@@ -491,18 +577,18 @@ public static class GfxMoveFactory
| Mode | Class | C++ Ref | Complexity | Priority |
|------|-------|---------|------------|----------|
| Linear | `GfxLinearMove` | lines 22-52 | ✅ Done in Phase 1 | — |
| OnTarget | `GfxOnTargetMove` | — | ✅ Done in Phase 1 | — |
| Parabolic | `GfxParabolicMove` | lines 54-92 | Medium | High |
| Missile | `GfxMissileMove` | lines 94-142 | High | High |
| Meteoric | `GfxMeteoricMove` | lines 144-188 | Low | Medium |
| Accelerated | `GfxAccMove` | lines 319-370 | Low | Medium |
| Helix | `GfxHelixMove` | lines 190-256 | Medium | Low |
| Curved | `GfxCurvedMove` | lines 258-317 | High | Low |
| Link | `GfxLinkMove` | lines 372-420 | Medium | Low |
| Random | `GfxRandMove` | lines 422-458 | Medium | Low |
| Linear | `CGfxLinearMove` | lines 22-52 | ✅ Done in Phase 1 | — |
| OnTarget | `CGfxOnTargetMove` | — | ✅ Done in Phase 1 | — |
| Parabolic | `CGfxParabolicMove` | lines 54-92 | Medium | High |
| Missile | `CGfxMissileMove` | lines 94-142 | High | High |
| Meteoric | `CGfxMeteoricMove` | lines 144-188 | Low | Medium |
| Accelerated | `CGfxAccMove` | lines 319-370 | Low | Medium |
| Helix | `CGfxHelixMove` | lines 190-256 | Medium | Low |
| Curved | `CGfxCurvedMove` | lines 258-317 | High | Low |
| Link | `CGfxLinkMove` | lines 372-420 | Medium | Low |
| Random | `CGfxRandMove` | lines 422-458 | Medium | Low |
**Pattern:** Each implements `IGfxMovement`, added to `GfxMoveFactory.Create()`
**Pattern:** Each extends `CGfxMoveBase`, added to `CGfxMoveBase.CreateMoveMethod()`
#### Task 2.2: GFX Lifecycle Management (2-3 days)
@@ -676,7 +762,7 @@ Complete the NPC melee branch in `DoFire()`:
| File | Lines | What Needs Changing |
|------|-------|---------------------|
| `A3DSkillGfxMan.cs` | 516 | Un-comment movement + state machine in Tick(), add IGfxMovement field, remove SpawnGFX call |
| `A3DSkillGfxMan.cs` | 516 | Un-comment movement + state machine in Tick(), add CGfxMoveBase field, remove SpawnGFX call |
| `CECSkillGfxMan.cs` | 878 | Un-comment Tick() position updates, implement GFX spawning, un-comment HitTarget() |
| `CECAttacksMan.cs` | 1354 | Fix Play() GFX strings, add scale fields, un-comment NPC skill branch, remove SpawnGFX hack |
@@ -684,18 +770,18 @@ Complete the NPC melee branch in `DoFire()`:
| File | Phase | Purpose |
|------|-------|---------|
| `IGfxMovement.cs` | Phase 1 | Movement interface |
| `GfxLinearMove.cs` | Phase 1 | Straight-line projectile |
| `GfxOnTargetMove.cs` | Phase 1 | Instant-hit (no flight) |
| `GfxMoveFactory.cs` | Phase 1 | Factory for movement modes |
| `GfxParabolicMove.cs` | Phase 2 | Arc trajectory |
| `GfxMissileMove.cs` | Phase 2 | Homing missile |
| `GfxMeteoricMove.cs` | Phase 2 | Falls from sky |
| `GfxAccMove.cs` | Phase 2 | Accelerated flight |
| `GfxHelixMove.cs` | Phase 2 | Spiral path |
| `GfxCurvedMove.cs` | Phase 2 | Bezier curve |
| `GfxLinkMove.cs` | Phase 2 | Lightning chain |
| `GfxRandMove.cs` | Phase 2 | Random walk |
| `CGfxMoveBase.cs` | Phase 1 | Movement abstract base class (same as C++) |
| `CGfxLinearMove.cs` | Phase 1 | Straight-line projectile |
| `CGfxOnTargetMove.cs` | Phase 1 | Instant-hit (no flight) |
| *(no separate factory file)* | — | *Factory is `CGfxMoveBase.CreateMoveMethod()`, same as C++* |
| `CGfxParabolicMove.cs` | Phase 2 | Arc trajectory |
| `CGfxMissileMove.cs` | Phase 2 | Homing missile |
| `CGfxMeteoricMove.cs` | Phase 2 | Falls from sky |
| `CGfxAccMove.cs` | Phase 2 | Accelerated flight |
| `CGfxHelixMove.cs` | Phase 2 | Spiral path |
| `CGfxCurvedMove.cs` | Phase 2 | Bezier curve |
| `CGfxLinkMove.cs` | Phase 2 | Lightning chain |
| `CGfxRandMove.cs` | Phase 2 | Random walk |
| `GfxPool.cs` | Phase 2 | Object pooling |
---
@@ -822,8 +908,8 @@ BMLogger.Log($"[GFX_FLOW] HitTarget at {vTarget}");
| Line(s) | What | Action |
|---------|------|--------|
| 205-206 | `m_pMoveMethod` field | Un-comment, use IGfxMovement |
| 251 | `m_pMoveMethod = CGfxMoveBase.CreateMoveMethod(mode)` | Replace with `GfxMoveFactory.Create(mode)` |
| 205-206 | `m_pMoveMethod` field | Un-comment, use CGfxMoveBase |
| 251 | `m_pMoveMethod = CGfxMoveBase.CreateMoveMethod(mode)` | Un-comment (same name as C++) |
| 336-337 | `GetMoveMethod()`, `GetMode()`, `GetHitPos()` | Un-comment |
| 343 | `SetReverse()` | Un-comment |
| 344-345 | `SetParam()`, `SetIsCluster()` | Un-comment |
+208 -111
View File
@@ -9,7 +9,7 @@
- ⚠️ **State machine commented out** — Tick() logic exists but is commented out
- ⚠️ **GFX strings empty** — Play() passes `""` instead of actual GFX names
- ⚠️ **SpawnGFX temp hack** — Instantiates flyGFX at target pos (wrong, needs removal)
-**Movement system missing** — No IGfxMovement, no GfxLinearMove, etc.
-**Movement system missing** — No CGfxMoveBase, no CGfxLinearMove, etc.
-**GFX spawning missing** — No fly/hit GFX instantiation in event Tick()
### What's the Skill GFX System?
@@ -37,7 +37,7 @@ SpawnGFX() temp hack ⚠️ WRONG - needs removal
A3DSkillGfxEvent.Tick() ⚠️ State machine COMMENTED OUT
IGfxMovement.TickMove() ❌ NOT CREATED
CGfxMoveBase.TickMove() ❌ NOT CREATED
Spawn/update fly GFX ❌ NOT IMPLEMENTED
@@ -52,142 +52,239 @@ CECAttackEvent.DoDamage() ✅ Working
### Step 1: Create Movement System (1 day)
Create 4 new files in `Assets/PerfectWorld/Scripts/Vfx/`:
Create 3 new files in `Assets/PerfectWorld/Scripts/Vfx/`:
**`IGfxMovement.cs`** — Movement interface
```csharp
namespace BrewMonster
{
public interface IGfxMovement
{
void SetMaxFlyTime(uint dwMaxFlyTime);
void StartMove(UnityEngine.Vector3 vHost, UnityEngine.Vector3 vTarget);
bool TickMove(float deltaTimeMs, UnityEngine.Vector3 vHostPos, UnityEngine.Vector3 vTargetPos);
UnityEngine.Vector3 GetPos();
UnityEngine.Vector3 GetMoveDir();
GfxMoveMode GetMode();
GfxHitPos GetHitPos();
bool IsReverse();
void SetReverse(bool bReverse);
void SetParam(GFX_SKILL_PARAM param);
void SetIsCluster(bool bCluster);
}
}
```
**`GfxLinearMove.cs`** — Straight-line projectile
**`CGfxMoveBase.cs`** — Movement base class (same name and pattern as C++)
```csharp
using UnityEngine;
namespace BrewMonster
{
public class GfxLinearMove : IGfxMovement
// Mirrors C++ CGfxMoveBase exactly:
// - StartMove / TickMove → pure virtual (= 0) → abstract
// - SetParam / SetIsCluster / SetReverse → virtual with default impl → virtual
// - GetMode / GetPos / GetMoveDir / IsReverse / SetMaxFlyTime → non-virtual → regular
public abstract class CGfxMoveBase
{
private Vector3 m_vPos, m_vMoveDir;
private float m_fSpeed;
private uint m_dwMaxFlyTime;
private bool m_bReverse;
private const float _fly_speed = 20.0f; // units/sec → 0.02 units/ms
protected GfxMoveMode m_Mode;
protected GfxHitPos m_HitPos = GfxHitPos.enumHitCenter;
protected Vector3 m_vPos;
protected Vector3 m_vMoveDir;
protected bool m_bOneOfCluser; // C++ spelling kept
protected uint m_dwMaxFlyTime;
protected bool m_bReverse;
protected bool m_bArea;
protected EmitShape m_Shape;
protected Vector3 m_vSize;
protected Vector3 m_vXRange;
protected Vector3 m_vYRange;
protected Vector3 m_vZRange;
protected float m_fSquare;
protected float m_fSquareH;
public void SetMaxFlyTime(uint t) { m_dwMaxFlyTime = t; }
public void StartMove(Vector3 vHost, Vector3 vTarget)
protected CGfxMoveBase(GfxMoveMode mode)
{
m_vPos = vHost;
m_vMoveDir = vTarget - vHost;
float fDist = m_vMoveDir.magnitude;
if (fDist < 1e-4f) { m_vMoveDir = Vector3.forward; m_fSpeed = _fly_speed / 1000f; return; }
m_vMoveDir /= fDist;
float fMax = (_fly_speed / 1000f) * m_dwMaxFlyTime;
m_fSpeed = fMax >= fDist ? _fly_speed / 1000f : fDist / m_dwMaxFlyTime;
m_Mode = mode;
m_HitPos = GfxHitPos.enumHitCenter;
}
public bool TickMove(float dtMs, Vector3 vHostPos, Vector3 vTargetPos)
// Pure virtual (= 0) → abstract — subclasses MUST override
public abstract void StartMove(Vector3 vHost, Vector3 vTarget);
public abstract bool TickMove(uint dwDeltaTime, Vector3 vHostPos, Vector3 vTargetPos);
// Protected helpers (same as C++ CGfxMoveBase)
protected static float Normalize(ref Vector3 v)
{
Vector3 dir = vTargetPos - m_vPos;
float dist = dir.magnitude;
if (dist < 1e-4f) return true;
dir /= dist;
float fly = m_fSpeed * dtMs;
if (fly >= dist) return true;
m_vPos += dir * fly;
m_vMoveDir = dir;
return false;
float mag = v.magnitude;
if (mag < 1e-6f) { v = Vector3.zero; return 0f; }
v /= mag;
return mag;
}
public Vector3 GetPos() => m_vPos;
public Vector3 GetMoveDir() => m_vMoveDir;
public GfxMoveMode GetMode() => GfxMoveMode.enumLinearMove;
public GfxHitPos GetHitPos() => GfxHitPos.enumHitCenter;
public bool IsReverse() => m_bReverse;
public void SetReverse(bool b) { m_bReverse = b; }
public void SetParam(GFX_SKILL_PARAM p) { }
public void SetIsCluster(bool b) { }
}
}
```
**`GfxOnTargetMove.cs`** — Instant hit (no flight)
```csharp
using UnityEngine;
namespace BrewMonster
{
public class GfxOnTargetMove : IGfxMovement
{
private Vector3 m_vPos, m_vMoveDir;
private bool m_bReverse;
public void SetMaxFlyTime(uint t) { }
public void StartMove(Vector3 vHost, Vector3 vTarget)
protected void CalcRange(Vector3 vDir)
{
m_vPos = vTarget;
m_vMoveDir = (vTarget - vHost);
if (m_vMoveDir.magnitude > 1e-4f) m_vMoveDir.Normalize();
else m_vMoveDir = Vector3.forward;
m_vYRange = Vector3.up;
m_vZRange = new Vector3(vDir.x, 0, vDir.z);
if (Normalize(ref m_vZRange) < 0.01f) m_vZRange = Vector3.forward;
m_vXRange = Vector3.Cross(m_vYRange, m_vZRange);
m_vXRange *= m_vSize.x;
m_vYRange *= m_vSize.y;
m_vZRange *= m_vSize.z;
m_fSquare = m_vSize.sqrMagnitude;
m_fSquareH = m_vSize.x * m_vSize.x + m_vSize.z * m_vSize.z;
}
public bool TickMove(float dtMs, Vector3 h, Vector3 t) { m_vPos = t; return true; }
public Vector3 GetPos() => m_vPos;
public Vector3 GetMoveDir() => m_vMoveDir;
public GfxMoveMode GetMode() => GfxMoveMode.enumOnTarget;
public GfxHitPos GetHitPos() => GfxHitPos.enumHitCenter;
public bool IsReverse() => m_bReverse;
public void SetReverse(bool b) { m_bReverse = b; }
public void SetParam(GFX_SKILL_PARAM p) { }
public void SetIsCluster(bool b) { }
}
}
```
**`GfxMoveFactory.cs`** — Factory
```csharp
namespace BrewMonster
{
public static class GfxMoveFactory
{
public static IGfxMovement Create(GfxMoveMode mode)
protected Vector3 GetRandOff()
{
float x, y, z;
if (m_Shape == EmitShape.enumBox)
return SymRandom() * m_vXRange + SymRandom() * m_vYRange + SymRandom() * m_vZRange;
else if (m_Shape == EmitShape.enumSphere)
{
do { x = SymRandom(); y = SymRandom(); z = SymRandom(); }
while (x*x + y*y + z*z > 1f);
return x * m_vXRange + y * m_vYRange + z * m_vZRange;
}
else if (m_Shape == EmitShape.enumCylinder)
{
y = SymRandom();
do { x = SymRandom(); z = SymRandom(); }
while (x*x + z*z > 1f);
return x * m_vXRange + y * m_vYRange + z * m_vZRange;
}
return Vector3.zero;
}
private static float SymRandom() { return UnityEngine.Random.value * 2f - 1f; }
// Virtual with default implementation — subclasses CAN override
public virtual void SetParam(GFX_SKILL_PARAM param)
{
m_bArea = param.m_bArea;
m_Shape = param.m_Shape;
m_vSize = new Vector3(param.m_vSize.x, param.m_vSize.y, param.m_vSize.z);
}
public virtual void SetIsCluster(bool bCluster) { m_bOneOfCluser = bCluster; }
public virtual void SetReverse(bool bReverse) { m_bReverse = bReverse; }
public virtual void UpdateGfxParam(Vector3 vHost, Vector3 vTarget) { }
// Non-virtual — same as C++
public GfxMoveMode GetMode() { return m_Mode; }
public GfxHitPos GetHitPos() { return m_HitPos; }
public Vector3 GetPos() { return m_vPos; }
public Vector3 GetMoveDir() { return m_vMoveDir; }
public bool IsReverse() { return m_bReverse; }
public void SetMaxFlyTime(uint dwTime)
{
m_dwMaxFlyTime = dwTime;
if (m_dwMaxFlyTime == 0) m_dwMaxFlyTime = 1;
}
public void SetIsArea(bool bArea) { m_bArea = bArea; }
public void SetShape(EmitShape shape) { m_Shape = shape; }
public void SetRange(Vector3 vSize) { m_vSize = vSize; }
// Factory method (same as C++ CGfxMoveBase::CreateMoveMethod)
public static CGfxMoveBase CreateMoveMethod(GfxMoveMode mode)
{
switch (mode)
{
case GfxMoveMode.enumOnTarget: return new GfxOnTargetMove();
case GfxMoveMode.enumLinearMove: return new GfxLinearMove();
default: return new GfxLinearMove(); // fallback
case GfxMoveMode.enumOnTarget: return new CGfxOnTargetMove(mode);
case GfxMoveMode.enumLinearMove: return new CGfxLinearMove(mode);
default: return new CGfxLinearMove(mode);
}
}
}
}
```
**`CGfxLinearMove.cs`** — Straight-line projectile
```csharp
using UnityEngine;
namespace BrewMonster
{
// Mirrors C++ CGfxLinearMove exactly (A3DSkillGfxEvent2.cpp:22-52)
public class CGfxLinearMove : CGfxMoveBase
{
protected float m_fSpeed;
private const float _fly_speed = 20.0f / 1000.0f; // same as C++
public CGfxLinearMove(GfxMoveMode mode) : base(mode) { }
public override void StartMove(Vector3 vHost, Vector3 vTarget)
{
if (m_bArea)
{
CalcRange((vTarget - vHost).normalized);
m_vPos = vHost + GetRandOff();
}
else
m_vPos = vHost;
m_vMoveDir = vTarget - m_vPos;
float fDist = Normalize(ref m_vMoveDir);
float fMax = _fly_speed * m_dwMaxFlyTime;
if (fMax >= fDist)
m_fSpeed = _fly_speed;
else
m_fSpeed = fDist / m_dwMaxFlyTime;
}
public override bool TickMove(uint dwDeltaTime, Vector3 vHostPos, Vector3 vTargetPos)
{
Vector3 vFlyDir = vTargetPos - m_vPos;
float fDist = Normalize(ref vFlyDir);
float fFlyDist = m_fSpeed * dwDeltaTime;
if (fFlyDist >= fDist) return true; // target hit
m_vPos += vFlyDir * fFlyDist;
m_vMoveDir = vFlyDir;
return false;
}
}
}
```
**`CGfxOnTargetMove.cs`** — Instant hit (no flight)
```csharp
using UnityEngine;
namespace BrewMonster
{
// Mirrors C++ CGfxOnTargetMove exactly (A3DSkillGfxEvent2.cpp:297-322)
public class CGfxOnTargetMove : CGfxMoveBase
{
protected float m_fRadius;
protected Vector3 m_vOffset;
public CGfxOnTargetMove(GfxMoveMode mode) : base(mode) { m_HitPos = GfxHitPos.enumHitBottom; m_fRadius = 0; }
public override void StartMove(Vector3 vHost, Vector3 vTarget)
{
m_vPos = vTarget;
m_vMoveDir = vTarget - vHost;
m_vMoveDir.y = 0; // C++: zero out Y before normalize
if (Normalize(ref m_vMoveDir) == 0)
m_vMoveDir = Vector3.forward; // _unit_z
if (m_bOneOfCluser)
{
float fRandAng = UnityEngine.Random.value * Mathf.PI * 2f;
float fRadius = UnityEngine.Random.value * m_fRadius;
m_vOffset.x = Mathf.Cos(fRandAng) * fRadius;
m_vOffset.z = Mathf.Sin(fRandAng) * fRadius;
m_vOffset.y = 0;
m_vPos += m_vOffset;
}
else
m_vOffset = Vector3.zero;
}
public override bool TickMove(uint dwDeltaTime, Vector3 vHostPos, Vector3 vTargetPos)
{
m_vPos = vTargetPos + m_vOffset;
return false; // C++ returns false — hit triggered by fly time timeout
}
public override void SetParam(GFX_SKILL_PARAM param)
{
base.SetParam(param);
m_fRadius = param.value.fVal; // C# union access: param.value.fVal
}
}
}
```
### Step 2: Wire Up A3DSkillGfxEvent State Machine (1-2 days)
**In `A3DSkillGfxMan.cs`:**
1. **Add field** (un-comment line ~205):
```csharp
protected IGfxMovement m_pMoveMethod;
protected CGfxMoveBase m_pMoveMethod;
```
2. **In constructor** (replace line ~251):
```csharp
m_pMoveMethod = GfxMoveFactory.Create(mode);
m_pMoveMethod = CGfxMoveBase.CreateMoveMethod(mode);
```
3. **Un-comment in Tick():**
@@ -195,15 +292,16 @@ namespace BrewMonster
- Line 434-435: `m_pMoveMethod.SetMaxFlyTime()` + `StartMove()`
- Line 454: `m_pMoveMethod.TickMove()` → `HitTarget(GetTargetCenter())`
4. **Un-comment setters** (lines 336-345):
4. **Un-comment delegating setters** (lines 336-345) — these delegate to `m_pMoveMethod` which has the correct default behavior:
```csharp
public IGfxMovement GetMoveMethod() { return m_pMoveMethod; }
public CGfxMoveBase GetMoveMethod() { return m_pMoveMethod; }
public GfxMoveMode GetMode() { return m_pMoveMethod.GetMode(); }
public GfxHitPos GetHitPos() { return m_pMoveMethod.GetHitPos(); }
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); }
```
These work correctly because `CGfxMoveBase.SetIsCluster()` / `SetReverse()` / `SetParam()` have default implementations that store the values. Subclasses inherit the defaults unless they need to do extra work (like `CGfxMeteoricMove.SetParam()` which also stores a radius).
5. **In AddOneSkillGfxEvent():**
- Un-comment line 150: `pEvent.SetReverse(bReverse)`
@@ -352,7 +450,7 @@ if (composerMan != null)
**Expected behavior:**
1. Player attacks → DoFire()
2. Composer loads: `m_MoveMode = enumOnTarget`, `m_dwFlyTime = 0`
3. Movement: `GfxOnTargetMove.TickMove()` → returns `true` immediately
3. Movement: `CGfxOnTargetMove.TickMove()` → returns `false` (stays at target + offset), hit triggered by fly time timeout
4. `HitTarget()` → spawns hit GFX at target
5. `m_timeToDoDamage = 1` → damage immediately
@@ -426,10 +524,9 @@ BMLogger.Log($"[GFX] Hit target at {vTarget}, hitGFX={m_pComposer.GetHitGFX()?.n
| File | What to Change | Phase |
|------|----------------|-------|
| `IGfxMovement.cs` | **CREATE** — movement interface | 1 |
| `GfxLinearMove.cs` | **CREATE** — linear projectile | 1 |
| `GfxOnTargetMove.cs` | **CREATE** — instant hit | 1 |
| `GfxMoveFactory.cs` | **CREATE** — factory | 1 |
| `CGfxMoveBase.cs` | **CREATE** — movement base class + factory (same as C++) | 1 |
| `CGfxLinearMove.cs` | **CREATE** — linear projectile | 1 |
| `CGfxOnTargetMove.cs` | **CREATE** — instant hit | 1 |
| `A3DSkillGfxMan.cs` | **EDIT** — un-comment state machine, add movement field | 1 |
| `CECSkillGfxMan.cs` | **EDIT** — un-comment Tick, add GFX spawning | 1 |
| `CECAttacksMan.cs` | **EDIT** — fix GFX strings, remove SpawnGFX, add scale | 1 |
@@ -438,7 +535,7 @@ BMLogger.Log($"[GFX] Hit target at {vTarget}, hitGFX={m_pComposer.GetHitGFX()?.n
## Estimated Time: 3-5 days for Phase 1
- Day 1: Create movement system (4 files)
- Day 1: Create movement system (3 files)
- Day 2: Un-comment + wire up state machine in A3DSkillGfxMan + CECSkillGfxMan
- Day 3: Fix composer Play() GFX refs, remove SpawnGFX hack, add accessor methods
- Day 4: Testing, debugging, logging
+191
View File
@@ -0,0 +1,191 @@
# Naming Conventions - CRITICAL RULES
## ⚠️ CRITICAL: Preserve C++ Names Exactly
**Never change C++ class/field/method names unless explicitly requested by the user.**
## Class Names
### Movement System
| C++ Name | C# Name | ✅/❌ |
|----------|---------|------|
| `CGfxMoveBase` | `CGfxMoveBase` | ✅ Correct |
| `CGfxLinearMove` | `CGfxLinearMove` | ✅ Correct |
| `CGfxOnTargetMove` | `CGfxOnTargetMove` | ✅ Correct |
| `CGfxParabolicMove` | `CGfxParabolicMove` | ✅ Correct |
| `CGfxMissileMove` | `CGfxMissileMove` | ✅ Correct |
| `CGfxMeteoricMove` | `CGfxMeteoricMove` | ✅ Correct |
| `CGfxHelixMove` | `CGfxHelixMove` | ✅ Correct |
| `CGfxCurvedMove` | `CGfxCurvedMove` | ✅ Correct |
| `CGfxAccMove` | `CGfxAccMove` | ✅ Correct |
| `CGfxLinkMove` | `CGfxLinkMove` | ✅ Correct |
| `CGfxRandMove` | `CGfxRandMove` | ✅ Correct |
**❌ WRONG:** `IGfxMovement`, `GfxLinearMove`, `LinearMovement`
### Event System
| C++ Name | C# Name | ✅/❌ |
|----------|---------|------|
| `A3DSkillGfxEvent` | `A3DSkillGfxEvent` | ✅ Correct |
| `A3DSkillGfxMan` | `A3DSkillGfxMan` | ✅ Correct |
| `A3DSkillGfxComposer` | `A3DSkillGfxComposer` | ✅ Correct |
| `A3DSkillGfxComposerMan` | `A3DSkillGfxComposerMan` | ✅ Correct |
| `CECSkillGfxEvent` | `CECSkillGfxEvent` | ✅ Correct |
| `CECSkillGfxMan` | `CECSkillGfxMan` | ✅ Correct |
## Field Names
### Movement Base Fields
| C++ Name | C# Name | Notes |
|----------|---------|-------|
| `m_Mode` | `m_Mode` | GfxMoveMode enum |
| `m_HitPos` | `m_HitPos` | GfxHitPos enum |
| `m_vPos` | `m_vPos` | Position vector |
| `m_vMoveDir` | `m_vMoveDir` | Movement direction |
| `m_bOneOfCluser` | `m_bOneOfCluser` | ⚠️ **TYPO PRESERVED** - C++ has this spelling |
| `m_dwMaxFlyTime` | `m_dwMaxFlyTime` | Max flight time (uint) |
| `m_bReverse` | `m_bReverse` | Reverse direction flag |
| `m_bArea` | `m_bArea` | Area emission flag |
| `m_Shape` | `m_Shape` | EmitShape enum |
| `m_vSize` | `m_vSize` | Size vector |
| `m_vXRange` | `m_vXRange` | X range for area emission |
| `m_vYRange` | `m_vYRange` | Y range for area emission |
| `m_vZRange` | `m_vZRange` | Z range for area emission |
| `m_fSquare` | `m_fSquare` | Square magnitude |
| `m_fSquareH` | `m_fSquareH` | Horizontal square magnitude |
**❌ WRONG:** `m_bOneOfCluster` (correct spelling) - C++ has the typo, we must preserve it
### Event Fields
| C++ Name | C# Name | Notes |
|----------|---------|-------|
| `m_pComposer` | `m_pComposer` | A3DSkillGfxComposer reference |
| `m_pMoveMethod` | `m_pMoveMethod` | CGfxMoveBase reference |
| `m_pFlyGfx` | `m_pFlyGfx` | Fly GFX instance |
| `m_pHitGfx` | `m_pHitGfx` | Hit GFX instance |
| `m_dwFlyTimeSpan` | `m_dwFlyTimeSpan` | Flight time span (uint) |
| `m_dwCurSpan` | `m_dwCurSpan` | Current span (uint) |
| `m_dwDelayTime` | `m_dwDelayTime` | Delay time (uint) |
| `m_bTraceTarget` | `m_bTraceTarget` | Trace target flag |
| `m_bFadeOut` | `m_bFadeOut` | Fade out flag |
| `m_nHostID` | `m_nHostID` | Host ID (long/clientid_t) |
| `m_nTargetID` | `m_nTargetID` | Target ID (long/clientid_t) |
| `m_dwModifier` | `m_dwModifier` | Modifier (uint) |
| `m_bIsGoblinSkill` | `m_bIsGoblinSkill` | Goblin skill flag |
| `m_vHostPos` | `m_vHostPos` | Host position |
| `m_vTargetPos` | `m_vTargetPos` | Target position |
| `m_vTargetDir` | `m_vTargetDir` | Target direction |
| `m_vTargetUp` | `m_vTargetUp` | Target up vector |
| `m_bHostExist` | `m_bHostExist` | Host exists flag |
| `m_bTargetExist` | `m_bTargetExist` | Target exists flag |
| `m_bHitGfxInfinite` | `m_bHitGfxInfinite` | Hit GFX infinite flag |
| `m_bTargetDirAndUpExist` | `m_bTargetDirAndUpExist` | Target dir/up exist flag |
| `m_enumState` | `m_enumState` | GfxSkillEventState enum |
## Method Names
### Movement Methods
| C++ Name | C# Name | Notes |
|----------|---------|-------|
| `StartMove()` | `StartMove()` | Initialize movement |
| `TickMove()` | `TickMove()` | Update movement (returns bool) |
| `SetParam()` | `SetParam()` | Set GFX_SKILL_PARAM |
| `SetIsCluster()` | `SetIsCluster()` | Set cluster flag |
| `SetReverse()` | `SetReverse()` | Set reverse flag |
| `UpdateGfxParam()` | `UpdateGfxParam()` | Update GFX parameters |
| `GetMode()` | `GetMode()` | Get movement mode |
| `GetHitPos()` | `GetHitPos()` | Get hit position |
| `GetPos()` | `GetPos()` | Get current position |
| `GetMoveDir()` | `GetMoveDir()` | Get movement direction |
| `IsReverse()` | `IsReverse()` | Check reverse flag |
| `SetMaxFlyTime()` | `SetMaxFlyTime()` | Set max flight time |
| `CreateMoveMethod()` | `CreateMoveMethod()` | Static factory method |
### Event Methods
| C++ Name | C# Name | Notes |
|----------|---------|-------|
| `Tick()` | `Tick()` | Update event |
| `HitTarget()` | `HitTarget()` | Handle target hit |
| `GetTargetCenter()` | `GetTargetCenter()` | Get target center (abstract) |
| `GetTargetDirAndUp()` | `GetTargetDirAndUp()` | Get target direction/up |
| `SetComposer()` | `SetComposer()` | Set composer |
| `GetMoveMethod()` | `GetMoveMethod()` | Get movement method |
| `Resume()` | `Resume()` | Resume event |
## Enum Names
| C++ Name | C# Name | Notes |
|----------|---------|-------|
| `GfxMoveMode` | `GfxMoveMode` | Movement mode enum |
| `GfxHitPos` | `GfxHitPos` | Hit position enum |
| `EmitShape` | `EmitShape` | Emission shape enum |
| `GfxSkillValType` | `GfxSkillValType` | Value type enum |
| `GfxSkillEventState` | `GfxSkillEventState` | Event state enum |
## Enum Values
### GfxMoveMode
- `enumLinearMove` (0)
- `enumParabolicMove` (1)
- `enumMissileMove` (2)
- `enumMeteoricMove` (3)
- `enumHelixMove` (4)
- `enumCurvedMove` (5)
- `enumAccMove` (6)
- `enumOnTarget` (7)
- `enumLink` (8)
- `enumRandMove` (9)
- `enumMoveModeNum` (10)
### GfxHitPos
- `enumHitCenter` (0)
- `enumHitBottom` (1)
### EmitShape
- `enumBox` (0)
- `enumSphere` (1)
- `enumCylinder` (2)
- `enumShapeNum` (3)
### GfxSkillEventState
- `enumWait` (0)
- `enumFlying` (1)
- `enumHit` (2)
- `enumFinished` (3)
## File Names
| C++ File | C# File | Location |
|----------|---------|----------|
| `A3DSkillGfxEvent2.h` | `CGfxMoveBase.cs` | `Assets/PerfectWorld/Scripts/Vfx/` |
| `A3DSkillGfxEvent2.cpp` | `CGfxLinearMove.cs` | `Assets/PerfectWorld/Scripts/Vfx/` |
| `A3DSkillGfxEvent2.cpp` | `CGfxOnTargetMove.cs` | `Assets/PerfectWorld/Scripts/Vfx/` |
| `A3DSkillGfxMan.h` | `A3DSkillGfxMan.cs` | `Assets/PerfectWorld/Scripts/Managers/` |
| `CECSkillGfxMan.h` | `CECSkillGfxMan.cs` | `Assets/PerfectWorld/Scripts/Managers/` |
## Naming Patterns
### Hungarian Notation (Preserved from C++)
- `m_` prefix = member variable
- `p` prefix = pointer/reference (C#: reference)
- `n` prefix = number/ID
- `dw` prefix = DWORD (uint)
- `b` prefix = bool
- `f` prefix = float
- `v` prefix = vector
- `sz` prefix = string (C#: string)
### Examples
- `m_pMoveMethod` = member pointer/reference to MoveMethod
- `m_dwFlyTimeSpan` = member DWORD (uint) for fly time span
- `m_bTraceTarget` = member bool for trace target
- `m_vHostPos` = member vector for host position
- `szFlyGfx` = string for fly GFX path
## Rules Summary
1. **Preserve ALL C++ names exactly** - including typos (`m_bOneOfCluser`)
2. **Keep Hungarian notation** - maintain `m_`, `p`, `dw`, `b`, `f`, `v` prefixes
3. **Match enum values** - exact same names and order
4. **Preserve file structure** - similar organization to C++
5. **Never "improve" names** - unless user explicitly requests it
+286
View File
@@ -0,0 +1,286 @@
# Type Mappings - C++ to C# Conversions
## Primitive Types
| C++ Type | C# Type | Notes |
|----------|---------|-------|
| `DWORD` | `uint` | 32-bit unsigned integer |
| `bool` | `bool` | Boolean |
| `int` | `int` | 32-bit signed integer |
| `float` | `float` | 32-bit floating point |
| `double` | `double` | 64-bit floating point |
| `char*` | `string` | String (C# uses managed strings) |
| `const char*` | `string` | Constant string |
| `size_t` | `uint` or `ulong` | Size type (usually uint) |
| `clientid_t` | `long` | Client ID type |
## Vector Types
| C++ Type | C# Type | Conversion Notes |
|----------|---------|------------------|
| `A3DVECTOR3` | `Vector3` | Unity's Vector3 |
| `A3DVECTOR3&` | `Vector3` (ref) | Pass by reference |
| `const A3DVECTOR3&` | `Vector3` (ref) | Const reference |
### A3DVECTOR3 to Vector3 Conversion
**C++:**
```cpp
A3DVECTOR3 vPos;
vPos.x = 1.0f;
vPos.y = 2.0f;
vPos.z = 3.0f;
```
**C#:**
```csharp
Vector3 vPos = new Vector3(1.0f, 2.0f, 3.0f);
// Or
Vector3 vPos;
vPos.x = 1.0f;
vPos.y = 2.0f;
vPos.z = 3.0f;
```
**When accessing from A3DVECTOR3 struct:**
```csharp
// If param.m_vSize is A3DVECTOR3
m_vSize = new Vector3(param.m_vSize.x, param.m_vSize.y, param.m_vSize.z);
```
## Pointer Types
| C++ Type | C# Type | Notes |
|----------|---------|-------|
| `T*` | `T` (reference) | C# uses references, not pointers |
| `T*&` | `ref T` | Reference to pointer → ref reference |
| `const T*` | `T` (readonly) | Const pointer → readonly reference |
| `T**` | `ref T` or `T[]` | Pointer to pointer → ref or array |
### Examples
**C++:**
```cpp
A3DSkillGfxComposer* m_pComposer;
CGfxMoveBase* m_pMoveMethod;
void SetComposer(A3DSkillGfxComposer* pComposer);
```
**C#:**
```csharp
A3DSkillGfxComposer m_pComposer;
CGfxMoveBase m_pMoveMethod;
void SetComposer(A3DSkillGfxComposer pComposer);
```
## Struct Types
### GFX_SKILL_PARAM
**C++:**
```cpp
struct GFX_SKILL_PARAM
{
union
{
bool bVal;
int nVal;
float fVal;
};
bool m_bArea;
EmitShape m_Shape;
A3DVECTOR3 m_vSize;
};
```
**C#:**
```csharp
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct GFX_SKILL_PARAM
{
public ValueUnion value; // Union wrapped in struct
public bool m_bArea;
public EmitShape m_Shape;
public A3DVECTOR3 m_vSize; // Note: Uses A3DVECTOR3, not Vector3
[StructLayout(LayoutKind.Explicit)]
public struct ValueUnion
{
[FieldOffset(0)]
public bool bVal;
[FieldOffset(0)]
public int nVal;
[FieldOffset(0)]
public float fVal;
}
}
```
**Access Pattern:**
```csharp
// C++: param.fVal
// C#: param.value.fVal
m_fRadius = param.value.fVal;
```
## Enum Types
| C++ Enum | C# Enum | Notes |
|----------|---------|-------|
| `enum GfxMoveMode { ... }` | `public enum GfxMoveMode { ... }` | Same values |
| `enum GfxHitPos { ... }` | `public enum GfxHitPos { ... }` | Same values |
| `enum EmitShape { ... }` | `public enum EmitShape { ... }` | Same values |
**Preserve exact enum value names and order!**
## Function Signatures
### Method Parameters
**C++:**
```cpp
bool TickMove(DWORD dwDeltaTime, const A3DVECTOR3& vHostPos, const A3DVECTOR3& vTargetPos);
void SetParam(const GFX_SKILL_PARAM* param);
```
**C#:**
```csharp
bool TickMove(uint dwDeltaTime, Vector3 vHostPos, Vector3 vTargetPos);
void SetParam(GFX_SKILL_PARAM param); // Struct passed by value (or ref if needed)
```
### Return Types
**C++:**
```cpp
CGfxMoveBase* CreateMoveMethod(GfxMoveMode mode);
const A3DVECTOR3& GetPos() const;
```
**C#:**
```csharp
static CGfxMoveBase CreateMoveMethod(GfxMoveMode mode);
Vector3 GetPos(); // No const in C#
```
## Const Correctness
| C++ | C# Equivalent |
|-----|---------------|
| `const T&` | `T` (value type) or `readonly T` (reference) |
| `const T*` | `T` (reference, readonly) |
| `const` method | No equivalent (use `readonly` for fields) |
**C++:**
```cpp
const A3DVECTOR3& GetPos() const;
bool IsReverse() const;
```
**C#:**
```csharp
Vector3 GetPos(); // No const modifier needed
bool IsReverse(); // No const modifier needed
```
## Memory Management
| C++ | C# |
|-----|-----|
| `new T()` | `new T()` |
| `delete ptr` | Automatic GC (or `Dispose()` for IDisposable) |
| `delete[] arr` | Automatic GC |
| `malloc/free` | Automatic GC |
**C++:**
```cpp
CGfxMoveBase* pMove = new CGfxLinearMove(mode);
delete pMove;
```
**C#:**
```csharp
CGfxMoveBase pMove = new CGfxLinearMove(mode);
// Automatic garbage collection, or implement IDisposable if needed
```
## Static Constants
**C++:**
```cpp
static const float _fly_speed = 20.0f / 1000.0f;
static const float _gravity = 9.8e-6f;
```
**C#:**
```csharp
private const float _fly_speed = 20.0f / 1000.0f;
private const float _gravity = 9.8e-6f;
```
## Helper Functions
### Normalize
**C++:**
```cpp
float Normalize(A3DVECTOR3& v)
{
float mag = v.Magnitude();
if (mag < 1e-6f) { v = _unit_zero; return 0f; }
v /= mag;
return mag;
}
```
**C#:**
```csharp
protected static float Normalize(ref Vector3 v)
{
float mag = v.magnitude;
if (mag < 1e-6f) { v = Vector3.zero; return 0f; }
v /= mag;
return mag;
}
```
## Common Patterns
### Passing by Reference
**C++:**
```cpp
void CalcRange(const A3DVECTOR3& vDir);
bool GetTargetDirAndUp(A3DVECTOR3& vDir, A3DVECTOR3& vUp);
```
**C#:**
```csharp
void CalcRange(Vector3 vDir); // Value type, passed by value
bool GetTargetDirAndUp(out Vector3 vDir, out Vector3 vUp); // Use out/ref
```
### Array/Container Types
| C++ | C# |
|-----|-----|
| `std::vector<T>` | `List<T>` |
| `std::list<T>` | `List<T>` or `LinkedList<T>` |
| `std::map<K,V>` | `Dictionary<K,V>` |
| `T[]` | `T[]` |
## Type Conversion Checklist
When converting a method:
- [ ] `DWORD``uint`
- [ ] `A3DVECTOR3``Vector3`
- [ ] `T*``T` (remove pointer)
- [ ] `const T&``T` (remove const and reference)
- [ ] `const T*``T` (remove const and pointer)
- [ ] `char*``string`
- [ ] `clientid_t``long`
- [ ] Union access: `param.fVal``param.value.fVal`
- [ ] `A3DVECTOR3` struct fields → `Vector3` constructor
+418
View File
@@ -0,0 +1,418 @@
# Code Patterns - C++ to C# Conversions
## Abstract Base Classes
### C++ Pattern
```cpp
class CGfxMoveBase
{
protected:
CGfxMoveBase(GfxMoveMode mode) : m_Mode(mode) {}
public:
virtual ~CGfxMoveBase() {}
virtual void StartMove(const A3DVECTOR3& vHost, const A3DVECTOR3& vTarget) = 0;
virtual bool TickMove(DWORD dwDeltaTime, ...) = 0;
virtual void SetParam(const GFX_SKILL_PARAM* param) { /* default impl */ }
static CGfxMoveBase* CreateMoveMethod(GfxMoveMode mode);
};
```
### C# Pattern
```csharp
public abstract class CGfxMoveBase
{
protected CGfxMoveBase(GfxMoveMode mode)
{
m_Mode = mode;
}
// Pure virtual (= 0) → abstract
public abstract void StartMove(Vector3 vHost, Vector3 vTarget);
public abstract bool TickMove(uint dwDeltaTime, ...);
// Virtual with default → virtual
public virtual void SetParam(GFX_SKILL_PARAM param)
{
// default implementation
}
// Static factory method
public static CGfxMoveBase CreateMoveMethod(GfxMoveMode mode)
{
switch (mode)
{
case GfxMoveMode.enumLinearMove: return new CGfxLinearMove(mode);
default: return new CGfxLinearMove(mode);
}
}
}
```
## Inheritance Patterns
### C++ Pattern
```cpp
class CGfxLinearMove : public CGfxMoveBase
{
protected:
float m_fSpeed;
public:
CGfxLinearMove(GfxMoveMode mode) : CGfxMoveBase(mode) {}
virtual void StartMove(const A3DVECTOR3& vHost, const A3DVECTOR3& vTarget);
virtual bool TickMove(DWORD dwDeltaTime, ...);
};
```
### C# Pattern
```csharp
public class CGfxLinearMove : CGfxMoveBase
{
protected float m_fSpeed;
public CGfxLinearMove(GfxMoveMode mode) : base(mode) { }
public override void StartMove(Vector3 vHost, Vector3 vTarget)
{
// implementation
}
public override bool TickMove(uint dwDeltaTime, ...)
{
// implementation
}
}
```
## Static Factory Methods
### C++ Pattern
```cpp
inline CGfxMoveBase* CGfxMoveBase::CreateMoveMethod(GfxMoveMode mode)
{
switch(mode)
{
case enumLinearMove:
return new CGfxLinearMove(mode);
case enumOnTarget:
return new CGfxOnTargetMove(mode);
default:
assert(false);
return NULL;
}
}
```
### C# Pattern
```csharp
public static CGfxMoveBase CreateMoveMethod(GfxMoveMode mode)
{
switch (mode)
{
case GfxMoveMode.enumLinearMove:
return new CGfxLinearMove(mode);
case GfxMoveMode.enumOnTarget:
return new CGfxOnTargetMove(mode);
default:
return new CGfxLinearMove(mode); // fallback
}
}
```
## Vector Operations
### Normalize Pattern
**C++:**
```cpp
A3DVECTOR3 vDir = vTarget - m_vPos;
float fDist = vDir.Normalize(); // In-place normalize, returns magnitude
if (fDist < 1e-4f) return true;
```
**C#:**
```csharp
Vector3 vDir = vTarget - m_vPos;
float fDist = Normalize(ref vDir); // Helper method
if (fDist < 1e-4f) return true;
// Helper implementation:
protected static float Normalize(ref Vector3 v)
{
float mag = v.magnitude;
if (mag < 1e-6f) { v = Vector3.zero; return 0f; }
v /= mag;
return mag;
}
```
### Vector Math
**C++:**
```cpp
m_vPos += vFlyDir * fFlyDist;
m_vMoveDir = vFlyDir;
```
**C#:**
```csharp
m_vPos += vFlyDir * fFlyDist;
m_vMoveDir = vFlyDir;
```
## Protected Helper Methods
### C++ Pattern
```cpp
protected:
void CalcRange(const A3DVECTOR3& vDir)
{
m_vYRange = _unit_y;
m_vZRange.Set(vDir.x, 0, vDir.z);
if (m_vZRange.Normalize() < .01f) m_vZRange = _unit_z;
m_vXRange = CrossProduct(m_vYRange, m_vZRange);
// ...
}
A3DVECTOR3 GetRandOff() const
{
if (m_Shape == enumBox)
{
// ...
}
// ...
}
```
### C# Pattern
```csharp
protected void CalcRange(Vector3 vDir)
{
m_vYRange = Vector3.up;
m_vZRange = new Vector3(vDir.x, 0, vDir.z);
if (Normalize(ref m_vZRange) < 0.01f) m_vZRange = Vector3.forward;
m_vXRange = Vector3.Cross(m_vYRange, m_vZRange);
// ...
}
protected Vector3 GetRandOff()
{
if (m_Shape == EmitShape.enumBox)
{
// ...
}
// ...
}
```
## State Machine Pattern
### C++ Pattern
```cpp
enum
{
enumWait,
enumFlying,
enumHit,
enumFinished
} m_enumState;
void Tick(DWORD dwDeltaTime)
{
if (m_enumState == enumFinished) return;
else if (m_enumState == enumHit)
{
// handle hit state
}
else if (m_enumState == enumWait)
{
if (m_dwCurSpan < m_dwDelayTime) return;
m_enumState = enumFlying;
}
else // enumFlying
{
if (m_pMoveMethod.TickMove(dwDeltaTime, ...))
HitTarget(GetTargetCenter());
}
}
```
### C# Pattern
```csharp
protected GfxSkillEventState m_enumState;
public virtual void Tick(uint dwDeltaTime)
{
if (m_enumState == GfxSkillEventState.enumFinished) return;
else if (m_enumState == GfxSkillEventState.enumHit)
{
// handle hit state
}
else if (m_enumState == GfxSkillEventState.enumWait)
{
if (m_dwCurSpan < m_dwDelayTime) return;
m_enumState = GfxSkillEventState.enumFlying;
}
else // enumFlying
{
if (m_pMoveMethod.TickMove(dwDeltaTime, ...))
HitTarget(GetTargetCenter());
}
}
```
## Const Correctness → Readonly
### C++ Pattern
```cpp
const A3DVECTOR3& GetPos() const { return m_vPos; }
GfxMoveMode GetMode() const { return m_Mode; }
```
### C# Pattern
```csharp
public Vector3 GetPos() { return m_vPos; } // No const needed
public GfxMoveMode GetMode() { return m_Mode; } // No const needed
```
## Delegate Pattern (Virtual Methods)
### C++ Pattern
```cpp
class A3DSkillGfxEvent
{
CGfxMoveBase* m_pMoveMethod;
public:
GfxMoveMode GetMode() const { return m_pMoveMethod->GetMode(); }
void SetReverse(bool bReverse) const { m_pMoveMethod->SetReverse(bReverse); }
void SetParam(const GFX_SKILL_PARAM* param) { m_pMoveMethod->SetParam(param); }
};
```
### C# Pattern
```csharp
public class A3DSkillGfxEvent
{
protected CGfxMoveBase m_pMoveMethod;
public GfxMoveMode GetMode() { return m_pMoveMethod.GetMode(); }
public void SetReverse(bool bReverse) { m_pMoveMethod.SetReverse(bReverse); }
public void SetParam(GFX_SKILL_PARAM param) { m_pMoveMethod.SetParam(param); }
}
```
## Random Number Generation
### C++ Pattern
```cpp
float _SymmetricRandom() { return (rand() / (float)RAND_MAX) * 2.0f - 1.0f; }
float _UnitRandom() { return rand() / (float)RAND_MAX; }
A3DVECTOR3 GetRandOff() const
{
if (m_Shape == enumBox)
{
A3DVECTOR3 xOff, yOff, zOff;
xOff = _SymmetricRandom() * m_vXRange;
yOff = _SymmetricRandom() * m_vYRange;
zOff = _SymmetricRandom() * m_vZRange;
return xOff + yOff + zOff;
}
}
```
### C# Pattern
```csharp
private static float SymRandom()
{
return UnityEngine.Random.value * 2f - 1f;
}
private static float UnitRandom()
{
return UnityEngine.Random.value;
}
protected Vector3 GetRandOff()
{
if (m_Shape == EmitShape.enumBox)
{
Vector3 xOff = SymRandom() * m_vXRange;
Vector3 yOff = SymRandom() * m_vYRange;
Vector3 zOff = SymRandom() * m_vZRange;
return xOff + yOff + zOff;
}
}
```
## Conditional Compilation → #if UNITY_EDITOR
### C++ Pattern
```cpp
#ifdef _DEBUG
assert(false);
#endif
```
### C# Pattern
```csharp
#if UNITY_EDITOR
UnityEngine.Debug.Assert(false);
#endif
```
## Memory Management Patterns
### C++ Pattern
```cpp
void ReleaseFlyGfx()
{
if (m_pFlyGfx)
{
if (m_bFadeOut)
AfxGetGFXExMan()->QueueFadeOutGfx(m_pFlyGfx, 1000);
else
{
m_pFlyGfx->Release();
delete m_pFlyGfx;
}
m_pFlyGfx = NULL;
}
}
```
### C# Pattern
```csharp
protected void ReleaseFlyGfx()
{
if (m_pFlyGfx != null)
{
if (m_bFadeOut)
AfxGetGFXExMan().QueueFadeOutGfx(m_pFlyGfx, 1000);
else
{
m_pFlyGfx.Release();
// C#: Automatic garbage collection, no delete needed
}
m_pFlyGfx = null;
}
}
```
## Common Conversion Checklist
When converting a class:
- [ ] Abstract base → `abstract class`
- [ ] Pure virtual methods → `abstract` methods
- [ ] Virtual with default → `virtual` methods
- [ ] Non-virtual → regular methods
- [ ] Static factory → `static` method
- [ ] Const methods → regular methods (no const)
- [ ] Pointers → references (remove `*`)
- [ ] References → value types or `ref`/`out`
- [ ] `DWORD``uint`
- [ ] `A3DVECTOR3``Vector3`
- [ ] Union access → `param.value.fVal`
- [ ] Hungarian notation → preserved
@@ -0,0 +1,250 @@
# Architecture Understanding
## System Overview
The Skill GFX system manages visual effects when skills are cast in Perfect World. It handles projectile flight, hit effects, and various movement patterns.
## High-Level Flow
```
Player Casts Skill
CECAttacksMan.AddSkillAttack()
CECAttackEvent.Tick() - Timing system
CECAttackEvent.DoFire() - Triggers when skill fires
A3DSkillGfxComposerMan.Play() - Finds composer by skill ID
A3DSkillGfxComposer.Play() - Iterates targets, calls AddOneTarget
A3DSkillGfxMan.AddSkillGfxEvent() - Creates GFX event
A3DSkillGfxEvent.Tick() - State machine (Wait → Flying → Hit → Finished)
CGfxMoveBase.TickMove() - Updates projectile position
Unity VFX System - Renders effects
```
## Core Components
### 1. Attack Event System
- **CECAttacksMan**: Manages all attack events
- **CECAttackEvent**: Individual attack event, handles timing
- **Purpose**: Determines WHEN to trigger GFX (based on skill timing)
### 2. GFX Composer System
- **A3DSkillGfxComposerMan**: Manages composers, loads from SkillStub
- **A3DSkillGfxComposer**: Contains GFX paths and parameters for a skill
- **Purpose**: Maps skill ID → GFX configuration
### 3. GFX Event System
- **A3DSkillGfxMan**: Base manager (abstract)
- **A3DSkillGfxEvent**: Base event class (abstract)
- **CECSkillGfxMan**: Unity-specific manager (inherits A3DSkillGfxMan)
- **CECSkillGfxEvent**: Unity-specific event (inherits A3DSkillGfxEvent)
- **Purpose**: Manages active GFX instances and state machine
### 4. Movement System
- **CGfxMoveBase**: Abstract base class for movement patterns
- **CGfxLinearMove**: Straight-line projectile
- **CGfxOnTargetMove**: Instant hit (no flight)
- **CGfxParabolicMove**: Arc trajectory
- **CGfxMissileMove**: Homing missile
- **Purpose**: Calculates projectile position over time
## Class Hierarchy
### Movement Classes
```
CGfxMoveBase (abstract)
├── CGfxLinearMove
├── CGfxOnTargetMove
├── CGfxParabolicMove
├── CGfxMissileMove
├── CGfxMeteoricMove
├── CGfxHelixMove
├── CGfxCurvedMove
├── CGfxAccMove
├── CGfxLinkMove
└── CGfxRandMove
```
### Event Classes
```
A3DSkillGfxMan (abstract)
└── CECSkillGfxMan (Unity implementation)
A3DSkillGfxEvent (abstract)
└── CECSkillGfxEvent (Unity implementation)
```
### Composer Classes
```
A3DSkillGfxComposerMan
└── A3DSkillGfxComposer
```
## State Machine
### A3DSkillGfxEvent States
1. **enumWait**: Waiting for delay time
- Increments `m_dwCurSpan`
- Transitions to `enumFlying` when delay expires
2. **enumFlying**: Projectile in flight
- Calls `m_pMoveMethod.TickMove()` each frame
- Updates GFX position
- Transitions to `enumHit` when `TickMove()` returns `true` OR timeout
3. **enumHit**: Hit effect playing
- Plays hit GFX at target position
- Transitions to `enumFinished` when hit GFX completes
4. **enumFinished**: Event complete
- Event can be recycled/pooled
## Data Flow
### Skill Configuration
```
SkillStub (C# data structure)
A3DSkillGfxComposer.Load(SkillStub)
Populates:
- Fly GFX path
- Hit GFX path
- Movement mode
- Fly time
- Scales
- Parameters
```
### Event Creation
```
A3DSkillGfxComposer.Play()
For each target:
A3DSkillGfxMan.AddSkillGfxEvent(
composer,
hostID,
targetID,
flyGfxPath,
hitGfxPath,
flyTimeSpan,
moveMode,
...
)
Creates A3DSkillGfxEvent:
- Sets composer
- Creates movement method (CGfxMoveBase.CreateMoveMethod)
- Sets parameters
- Initializes state to enumWait
```
### Runtime Update
```
A3DSkillGfxMan.Tick() (called each frame)
For each active event:
A3DSkillGfxEvent.Tick(deltaTime)
State machine:
- enumWait: Check delay
- enumFlying:
- m_pMoveMethod.TickMove()
- Update GFX position
- Check timeout
- enumHit: Check hit GFX completion
- enumFinished: Mark for cleanup
```
## Key Relationships
### A3DSkillGfxEvent → CGfxMoveBase
- **Relationship**: Composition
- **Field**: `m_pMoveMethod : CGfxMoveBase`
- **Usage**: Delegates movement calculation to movement method
- **Creation**: `CGfxMoveBase.CreateMoveMethod(mode)`
### A3DSkillGfxEvent → A3DSkillGfxComposer
- **Relationship**: Reference
- **Field**: `m_pComposer : A3DSkillGfxComposer`
- **Usage**: Accesses GFX paths and parameters
- **Set**: `SetComposer(composer)`
### A3DSkillGfxMan → A3DSkillGfxEvent
- **Relationship**: Container/Manager
- **Storage**: List/array of active events
- **Management**: Creates, updates, destroys events
- **Pooling**: Reuses finished events
### CECSkillGfxEvent → Unity VFX
- **Relationship**: Unity integration
- **Fields**: `m_flyGfxInstance : GameObject`, `m_hitGfxInstance : GameObject`
- **Usage**: Instantiates Unity particle systems/prefabs
- **Loading**: Uses Addressables for async loading
## Memory Management
### C++ Pattern
- Manual `new`/`delete`
- Object pooling for events
- GFX caching system
### C# Pattern
- Automatic garbage collection
- Object pooling still recommended for performance
- Unity Addressables for asset loading
## Threading Model
- **C++**: Single-threaded game loop
- **C#**: Unity main thread (MonoBehaviour.Update)
- **Async**: Addressables loading can be async, but GFX updates on main thread
## Performance Considerations
1. **Object Pooling**: Reuse `A3DSkillGfxEvent` instances
2. **GFX Caching**: Cache loaded GFX prefabs
3. **Batch Updates**: Update all events in single loop
4. **Early Exit**: Skip finished events quickly
5. **LOD System**: Use `m_bGfxUseLod` for distance-based quality
## Integration Points
### With Attack System
- **Trigger**: `CECAttackEvent.DoFire()` calls `A3DSkillGfxComposerMan.Play()`
- **Timing**: GFX starts when skill "fires" (not when cast begins)
### With Character System
- **Position**: `GetTargetCenter()` gets character position
- **Tracking**: `m_bTraceTarget` enables position updates during flight
- **Hooks**: Future feature for bone attachment
### With Unity VFX
- **Loading**: Addressables async loading
- **Instantiation**: Unity GameObject/Prefab system
- **Rendering**: Unity Particle System or custom VFX
## Extension Points
### New Movement Modes
1. Create new class inheriting `CGfxMoveBase`
2. Implement `StartMove()` and `TickMove()`
3. Add case to `CreateMoveMethod()`
### New Event Types
1. Create new class inheriting `A3DSkillGfxEvent` or `CECSkillGfxEvent`
2. Override `GetTargetCenter()` and other virtual methods
3. Implement Unity-specific rendering
### Custom GFX Systems
1. Override `LoadFlyGfx()` / `LoadHitGfx()`
2. Implement custom VFX loading/instantiation
3. Use Unity Addressables or custom asset system
+333
View File
@@ -0,0 +1,333 @@
# Skill GFX System Deep Dive
## Movement System Architecture
### CGfxMoveBase Design
**Purpose**: Abstract base for all projectile movement patterns.
**Key Responsibilities**:
- Calculate position over time
- Handle area emission (random offsets)
- Support clustering (multiple projectiles)
- Provide position/direction queries
**Core Methods**:
```csharp
public abstract void StartMove(Vector3 vHost, Vector3 vTarget);
public abstract bool TickMove(uint dwDeltaTime, Vector3 vHostPos, Vector3 vTargetPos);
```
**Return Value Semantics**:
- `StartMove()`: Initialize movement, set initial position/direction
- `TickMove()`:
- Returns `true` = target reached/hit
- Returns `false` = still in flight
- Updates `m_vPos` and `m_vMoveDir`
### Movement Mode Details
#### CGfxLinearMove
- **Pattern**: Straight line from host to target
- **Speed**: `_fly_speed = 20.0f / 1000.0f` (units per millisecond)
- **Behavior**:
- If max fly time allows, uses constant speed
- Otherwise, calculates speed to reach target in max time
- Updates direction each frame to track moving target
#### CGfxOnTargetMove
- **Pattern**: Instant hit (no flight)
- **Behavior**:
- `StartMove()`: Sets position to target immediately
- `TickMove()`: Returns `false` (stays at target)
- Hit triggered by fly time timeout, NOT by `TickMove()` return
- Supports cluster offset (random radius around target)
#### CGfxParabolicMove
- **Pattern**: Arc trajectory with gravity
- **Physics**:
- Horizontal velocity constant
- Vertical velocity affected by gravity
- Calculates initial vertical velocity to reach target
#### CGfxMissileMove
- **Pattern**: Homing missile
- **Behavior**:
- Accelerates toward target
- Rotates to face target
- Constant acceleration until hit
#### CGfxMeteoricMove
- **Pattern**: Falls from sky
- **Behavior**:
- Starts above target
- Falls straight down
- Configurable radius
#### CGfxHelixMove
- **Pattern**: Spiral path
- **Behavior**:
- Spiral around center axis
- Radius can shrink over time
- Configurable radius parameter
#### CGfxCurvedMove
- **Pattern**: Bezier-like curve
- **Behavior**:
- Curves from host to target
- Can curve left or right
- Configurable lateral speed
#### CGfxAccMove
- **Pattern**: Accelerated flight
- **Behavior**:
- Constant acceleration
- Configurable acceleration value
#### CGfxLinkMove
- **Pattern**: Lightning chain
- **Behavior**:
- Links between host and target
- Updates GFX parameters dynamically
- No movement (stays linked)
#### CGfxRandMove
- **Pattern**: Random walk
- **Behavior**:
- Random direction changes
- Configurable step size and speed
- Uses control points for smoothness
## Area Emission System
### Purpose
Emit multiple projectiles in a random area around the start position.
### Configuration
- **m_bArea**: Enable area emission
- **m_Shape**: Emission shape (Box, Sphere, Cylinder)
- **m_vSize**: Size of emission area
### Implementation
**CalcRange()**: Calculates X/Y/Z range vectors based on movement direction.
```csharp
protected void CalcRange(Vector3 vDir)
{
m_vYRange = Vector3.up;
m_vZRange = new Vector3(vDir.x, 0, vDir.z); // Project to horizontal
if (Normalize(ref m_vZRange) < 0.01f) m_vZRange = Vector3.forward;
m_vXRange = Vector3.Cross(m_vYRange, m_vZRange); // Perpendicular
// Scale by m_vSize
}
```
**GetRandOff()**: Generates random offset based on shape.
- **Box**: Uniform distribution in box
- **Sphere**: Uniform distribution in sphere (rejection sampling)
- **Cylinder**: Uniform in horizontal circle, uniform in Y
### Usage
```csharp
if (m_bArea)
{
CalcRange((vTarget - vHost).normalized);
m_vPos = vHost + GetRandOff();
}
```
## Clustering System
### Purpose
Emit multiple projectiles with time intervals (e.g., machine gun effect).
### Configuration
- **m_bOneOfCluser**: This projectile is part of a cluster
- **Cluster params**: Count and interval (handled by manager)
### OnTarget Clustering
For `CGfxOnTargetMove`, clustering adds random offset:
```csharp
if (m_bOneOfCluser)
{
float fRandAng = Random.value * 2π;
float fRadius = Random.value * m_fRadius;
m_vOffset = new Vector3(cos(ang) * radius, 0, sin(ang) * radius);
m_vPos += m_vOffset;
}
```
## State Machine Details
### enumWait State
- **Entry**: Event created
- **Behavior**:
- Increment `m_dwCurSpan`
- Wait for `m_dwDelayTime`
- **Exit**: When `m_dwCurSpan >= m_dwDelayTime`
- **Transition**: → `enumFlying`
### enumFlying State
- **Entry**: Delay expired
- **Behavior**:
- Call `m_pMoveMethod.SetMaxFlyTime(m_dwFlyTimeSpan)`
- Call `m_pMoveMethod.StartMove(m_vHostPos, m_vTargetPos)`
- Each frame:
- Update host/target positions if `m_bTraceTarget`
- Call `m_pMoveMethod.TickMove(dwDeltaTime, ...)`
- Update GFX position
- Check timeout: `m_dwCurSpan > m_dwFlyTimeSpan`
- **Exit Conditions**:
- `TickMove()` returns `true` (target hit)
- `m_dwCurSpan > m_dwFlyTimeSpan` (timeout)
- **Transition**: → `enumHit`
### enumHit State
- **Entry**: Target hit or timeout
- **Behavior**:
- Release fly GFX (fade out or immediate)
- Spawn hit GFX at target position
- Update hit GFX position if `m_bTraceTarget`
- Check if hit GFX is infinite (`m_bHitGfxInfinite`)
- **Exit Conditions**:
- Hit GFX completes (not infinite)
- Hit GFX timeout (5 seconds for infinite)
- Target disappears
- **Transition**: → `enumFinished`
### enumFinished State
- **Entry**: Hit effect complete
- **Behavior**:
- Release all GFX
- Mark event for cleanup/pooling
- **Exit**: Event removed from active list
## Position Tracking
### Host Position
- **Source**: Character position from `m_nHostID`
- **Update**: Each frame if `m_bHostExist`
- **Usage**:
- Initial position for `StartMove()`
- Dynamic updates for `TickMove()` (if target moves)
### Target Position
- **Source**: Character position from `m_nTargetID`
- **Update**: Each frame if `m_bTargetExist` and `m_bTraceTarget`
- **Usage**:
- Target position for `StartMove()`
- Dynamic updates for `TickMove()` (tracking)
### GetTargetCenter()
- **Abstract method**: Must be implemented by derived class
- **Purpose**: Get current target center position
- **Implementation**:
- `CECSkillGfxEvent`: Gets character position from ID
- Can use hooks/bones in future
## GFX Loading & Instantiation
### C++ Pattern
```cpp
A3DGFXEx* LoadFlyGfx(A3DDevice* pDev, const char* szPath);
void SetFlyGfx(A3DGFXEx* pFlyGfx);
```
### C# Pattern (Unity)
```csharp
// Async loading with Addressables
async Task<GameObject> LoadFlyGfxAsync(string path)
{
var handle = Addressables.LoadAssetAsync<GameObject>(path);
await handle.Task;
return handle.Result;
}
// Instantiation
GameObject instance = Instantiate(prefab, position, rotation);
```
### Lifecycle
1. **Load**: Async load prefab (Addressables)
2. **Instantiate**: Create instance at position
3. **Update**: Update position each frame
4. **Release**: Destroy instance, release prefab
## Parameter System
### GFX_SKILL_PARAM Structure
```csharp
public struct GFX_SKILL_PARAM
{
public ValueUnion value; // Union: bVal, nVal, or fVal
public bool m_bArea;
public EmitShape m_Shape;
public A3DVECTOR3 m_vSize;
}
```
### Usage by Movement Mode
- **CGfxLinearMove**: Uses `m_bArea`, `m_Shape`, `m_vSize` (default)
- **CGfxOnTargetMove**: Also uses `value.fVal` for radius
- **CGfxMeteoricMove**: Uses `value.fVal` for fall radius
- **CGfxHelixMove**: Uses `value.fVal` for spiral radius
- **CGfxCurvedMove**: Uses `value.bVal` for curve direction
- **CGfxAccMove**: Uses `value.fVal` for acceleration
- **CGfxRandMove**: Uses `value.fVal` for speed
### Setting Parameters
```csharp
GFX_SKILL_PARAM param = new GFX_SKILL_PARAM();
param.value.fVal = 5.0f; // Radius for OnTarget
param.m_bArea = true;
param.m_Shape = EmitShape.enumSphere;
param.m_vSize = new A3DVECTOR3(2, 2, 2);
m_pMoveMethod.SetParam(param);
```
## Performance Optimizations
### Object Pooling
- **Purpose**: Reuse event instances
- **Implementation**: `m_FreeLst` in `CECSkillGfxMan`
- **Usage**:
- Get from pool when creating event
- Return to pool when finished
### GFX Caching
- **Purpose**: Avoid reloading same prefabs
- **Implementation**: Cache loaded Addressable handles
- **Key**: Use path as key
### Batch Updates
- **Purpose**: Update all events efficiently
- **Implementation**: Single loop through active events
- **Optimization**: Early exit for finished events
### LOD System
- **Purpose**: Reduce quality for distant effects
- **Field**: `m_bGfxUseLod`
- **Usage**: Disable expensive effects when far away
## Debugging Tips
### Common Issues
1. **Projectile doesn't move**: Check `StartMove()` called, `m_fSpeed` set
2. **Projectile goes wrong direction**: Check `m_vMoveDir` calculation
3. **Hit doesn't trigger**: Check `TickMove()` return value, timeout
4. **GFX doesn't appear**: Check loading, instantiation, position
5. **Performance issues**: Check pooling, caching, LOD
### Debug Fields
- `m_vPos`: Current position
- `m_vMoveDir`: Current direction
- `m_enumState`: Current state
- `m_dwCurSpan`: Current time span
- `m_dwFlyTimeSpan`: Max fly time
### Visualization
- Draw gizmos for projectile position
- Draw line from host to target
- Show state in inspector
- Log state transitions
+45
View File
@@ -0,0 +1,45 @@
# Agent Skills for Perfect World Unity C++ to C# Conversion
This folder contains comprehensive skills and guidelines for AI agents working on the Perfect World Unity C++ to C# conversion project.
## Skills Index
### Core Conversion Skills
1. **[Naming Conventions](./01-naming-conventions.md)** - Maintain C++ naming exactly (CGfxMoveBase, m_bOneOfCluser, etc.)
2. **[Type Mappings](./02-type-mappings.md)** - C++ to C# type conversions (DWORD→uint, A3DVECTOR3→Vector3, etc.)
3. **[Code Patterns](./03-code-patterns.md)** - Common C++ patterns and their C# equivalents
### Architecture & Understanding
4. **[Architecture Understanding](./04-architecture-understanding.md)** - System structure, flow, and relationships
5. **[Skill GFX System Deep Dive](./05-skill-gfx-deep-dive.md)** - Detailed GFX system architecture
### Quality & Validation
6. **[Testing & Validation](./06-testing-validation.md)** - How to verify conversions are correct
7. **[Common Pitfalls](./07-common-pitfalls.md)** - Mistakes to avoid during conversion
8. **[Best Practices](./08-best-practices.md)** - Recommended approaches and patterns
### Reference
9. **[C++ Source Reference](./09-cpp-source-reference.md)** - Key C++ files and their locations
10. **[Unity C# Reference](./10-unity-csharp-reference.md)** - Key Unity C# files and their locations
## Quick Start
When starting a conversion task:
1. Read [Naming Conventions](./01-naming-conventions.md) - **CRITICAL** - Never change C++ names unnecessarily
2. Check [Type Mappings](./02-type-mappings.md) for type conversions
3. Review [Common Pitfalls](./07-common-pitfalls.md) before coding
4. Use [Testing & Validation](./06-testing-validation.md) to verify your work
## Priority Rules
1. **Preserve C++ naming** - If C++ has `CGfxMoveBase`, use `CGfxMoveBase` (not `IGfxMovement`)
2. **Match C++ behavior exactly** - Don't "improve" logic unless explicitly requested
3. **Maintain field names** - Even typos like `m_bOneOfCluser` must be preserved
4. **Verify against C++ source** - Always check the actual C++ implementation
## Project Context
- **Source:** `perfect-world-source/` - Original C++ codebase
- **Target:** `perfect-world-unity/Assets/` - Unity C# project
- **Main Plan:** `SKILL_GFX_CONVERSION_PLAN.md` - Overall conversion strategy
- **Quick Guide:** `SKILL_GFX_QUICK_START.md` - Fast implementation guide