Files
test/Documentation/HOOK_SYSTEM_UNITY_PARENTING_APPROACH.md
2026-03-13 16:03:47 +07:00

435 lines
13 KiB
Markdown

# Hook System Unity Parenting Approach
**Date Created:** 2026-02-24
**Purpose:** Analyze whether we can use Unity's Transform parenting instead of manual position calculation
---
## Question
Instead of manually calculating hook positions every frame and updating GFX positions, can we:
1. Parent GFX GameObject directly to hook Transform?
2. Eliminate the need for `GetHookWorldPosition()` calculations?
3. Let Unity automatically handle position updates?
---
## Analysis: When Can We Parent vs. When We Need Manual Updates
### ✅ **YES - Can Parent (Hit GFX that follows target)**
**Use Case:** Hit GFX that needs to follow a moving target
**C++ Behavior:**
```cpp
// A3DSkillGfxEvent2.cpp:502-508
if (m_bTraceTarget)
{
A3DMATRIX4 matTran;
matTran.Identity();
matTran.SetRow(3, GetTargetCenter()); // Updates every frame
m_pHitGfx->SetParentTM(matTran);
}
```
**Unity Approach:**
```csharp
// Instead of:
Vector3 targetPos = HookUtils.GetHookWorldPosition(targetHook, offset, bRelHook);
hitGfx.transform.position = targetPos; // Every frame
// We can do:
hitGfx.transform.SetParent(targetHook.transform);
hitGfx.transform.localPosition = offset; // One-time setup
// Unity automatically updates position every frame!
```
**Benefits:**
- ✅ No manual position calculation every frame
- ✅ Automatically follows bone/hook movement
- ✅ Handles rotation automatically
- ✅ More Unity-native approach
- ✅ Better performance (Unity's transform system is optimized)
---
### ⚠️ **PARTIAL - Can Use for Spawn Position Only (Fly GFX)**
**Use Case:** Fly GFX spawns at host hook, then moves independently to target
**C++ Behavior:**
```cpp
// A3DSkillGfxEvent2.cpp:535-546
// Fly GFX spawns at host position
m_pMoveMethod->StartMove(m_vHostPos, m_vTargetPos);
if (m_pFlyGfx)
{
m_pFlyGfx->SetParentTM(_build_matrix(m_pMoveMethod->GetMoveDir(), m_pMoveMethod->GetPos()));
m_pFlyGfx->Start(true);
}
// Then every frame:
m_pMoveMethod->TickMove(dwDeltaTime, m_vHostPos, m_vTargetPos); // Updates position
m_pFlyGfx->SetParentTM(_build_matrix(m_pMoveMethod->GetMoveDir(), m_pMoveMethod->GetPos()));
```
**Unity Approach:**
```csharp
// Spawn at hook position (one-time)
Transform hostHook = GetHook("hand_r");
flyGfx.transform.position = hostHook.position;
flyGfx.transform.rotation = hostHook.rotation;
// Then IMMEDIATELY unparent and move independently
flyGfx.transform.SetParent(null); // Unparent for independent movement
// Update position manually during flight
flyGfx.transform.position = m_pMoveMethod.GetPos();
flyGfx.transform.rotation = Quaternion.LookRotation(m_pMoveMethod.GetMoveDir());
```
**Why Can't Fully Parent:**
- Fly GFX needs to move independently from host to target
- Target position may also come from a hook (which moves)
- Movement is interpolated (not directly following either hook)
**Hybrid Approach:**
- ✅ Use hook Transform for **initial spawn position** (one-time)
- ❌ Can't parent during flight (needs independent movement)
- ✅ Can use hook Transform for **target position calculation** (if target has hook)
---
### ✅ **YES - Can Use for Offsets (Child GameObject)**
**Use Case:** GFX needs offset from hook position
**C++ Behavior:**
```cpp
// Relative offset: transform offset in hook's local space
if (bRelHook) {
vPos = pHook->GetAbsoluteTM() * (*pOffset);
}
```
**Unity Approach:**
```csharp
// Instead of calculating:
Vector3 worldPos = hookTransform.TransformPoint(offset);
// We can create a child GameObject:
GameObject offsetObj = new GameObject("GFX_Offset");
offsetObj.transform.SetParent(hookTransform);
offsetObj.transform.localPosition = offset; // Offset in hook's local space
// Then parent GFX to offset object:
gfx.transform.SetParent(offsetObj.transform);
gfx.transform.localPosition = Vector3.zero; // GFX at offset position
```
**Benefits:**
- ✅ Handles relative offsets automatically
- ✅ Rotates with hook automatically
- ✅ No manual matrix calculations
---
## Recommended Unity Implementation Strategy
### Strategy 1: Hybrid Approach (Recommended)
**For Hit GFX that follows target:**
```csharp
public class CECSkillGfxEvent : A3DSkillGfxEvent
{
private GameObject m_hitGfxInstance;
private Transform m_targetHook; // Hook Transform (if available)
private void SpawnHitGfx(Vector3 vTarget)
{
// Try to get target hook
m_targetHook = GetTargetHookTransform();
if (m_targetHook != null && m_pComposer.m_HitPos.bTraceTarget)
{
// Parent to hook - Unity handles updates automatically
m_hitGfxInstance = Instantiate(hitGfxPrefab);
m_hitGfxInstance.transform.SetParent(m_targetHook);
// Apply offset
if (m_pComposer.m_HitPos.bRelHook)
{
// Relative offset: in hook's local space
m_hitGfxInstance.transform.localPosition = m_pComposer.m_HitPos.vOffset;
}
else
{
// Absolute offset: need to calculate once
// (Can't fully eliminate this, but only calculate once)
Vector3 offsetWorld = m_targetHook.root.TransformPoint(m_pComposer.m_HitPos.vOffset);
m_hitGfxInstance.transform.position = offsetWorld - m_targetHook.root.position + m_targetHook.position;
}
}
else
{
// No hook or doesn't trace target - use manual position
m_hitGfxInstance = Instantiate(hitGfxPrefab, vTarget, Quaternion.identity);
// Update manually if needed
}
}
private Transform GetTargetHookTransform()
{
// Get hook Transform from target
if (string.IsNullOrEmpty(m_pComposer.m_HitPos.szHook))
return null;
CECModel targetModel = GetTargetModel();
if (targetModel == null)
return null;
// Get hook Transform (not position!)
return targetModel.GetHookTransform(
m_pComposer.m_HitPos.szHook,
m_pComposer.m_HitPos.szHanger,
m_pComposer.m_HitPos.bChildHook);
}
}
```
**For Fly GFX:**
```csharp
private void SpawnFlyGfx()
{
// Get host hook Transform (for initial position)
Transform hostHook = GetHostHookTransform();
if (hostHook != null)
{
// Spawn at hook position
m_flyGfxInstance = Instantiate(flyGfxPrefab);
m_flyGfxInstance.transform.position = hostHook.position;
m_flyGfxInstance.transform.rotation = hostHook.rotation;
// Apply offset if needed
if (m_pComposer.m_FlyPos.bRelHook)
{
m_flyGfxInstance.transform.position = hostHook.TransformPoint(m_pComposer.m_FlyPos.vOffset);
}
// IMMEDIATELY unparent - fly GFX moves independently
m_flyGfxInstance.transform.SetParent(null);
}
else
{
// No hook - use calculated position
m_flyGfxInstance = Instantiate(flyGfxPrefab, m_pMoveMethod.GetPos(), Quaternion.identity);
}
}
private void UpdateFlyGfxTransform()
{
if (m_flyGfxInstance == null) return;
// Update position manually (can't parent during flight)
m_flyGfxInstance.transform.position = m_pMoveMethod.GetPos();
Vector3 dir = m_pMoveMethod.GetMoveDir();
if (dir.magnitude > 1e-4f)
m_flyGfxInstance.transform.rotation = Quaternion.LookRotation(dir);
}
```
---
### Strategy 2: Helper Method to Get Hook Transform (Not Position)
**New Method in HookUtils or SkeletonBuilder:**
```csharp
public static Transform GetHookTransform(
SkeletonBuilder skeleton,
string hookName,
string hangerName = null,
bool bChildHook = false)
{
// Get model (main or child)
CECModel model = skeleton.GetComponent<CECModel>();
if (hangerName != null && bChildHook)
{
model = model.GetChildModel(hangerName);
if (model == null) return null;
}
// Get hook Transform from skeleton
return model.GetHook(hookName, false); // Returns Transform, not position!
}
```
**Benefits:**
- Returns `Transform` instead of `Vector3`
- Can be used for parenting
- Can be used for one-time position lookup
- More flexible than position-only approach
---
## Comparison: Manual Position vs. Parenting
| Scenario | Manual Position (C++ way) | Unity Parenting | Winner |
|----------|---------------------------|----------------|--------|
| **Hit GFX follows target** | Calculate every frame | Parent once, Unity updates | ✅ **Parenting** |
| **Fly GFX spawn position** | Calculate once | Use hook Transform.position once | ✅ **Parenting** (simpler) |
| **Fly GFX during flight** | Calculate every frame | Must update manually | ⚠️ **Both** (can't parent) |
| **Relative offset** | Matrix transform every frame | LocalPosition once | ✅ **Parenting** |
| **Absolute offset** | Calculate every frame | Calculate once or use child GameObject | ✅ **Parenting** (one-time) |
---
## What We Can Eliminate
### ✅ **Can Eliminate:**
1. **Hit GFX position updates** (if `bTraceTarget = true`)
- Instead of: `UpdateHitGfxPosition()` every frame
- Use: `transform.SetParent(hookTransform)` once
2. **Initial spawn position calculation**
- Instead of: `HookUtils.GetHookWorldPosition(hook, offset, bRelHook)`
- Use: `hookTransform.position` or `hookTransform.TransformPoint(offset)`
3. **Relative offset calculations** (for hit GFX)
- Instead of: Manual matrix multiplication every frame
- Use: `transform.localPosition = offset` once
### ❌ **Cannot Eliminate:**
1. **Fly GFX position during flight**
- Must update manually (moves independently)
- But can use hook Transform for initial position
2. **Target position for fly GFX movement calculation**
- Still need to get target position (from hook or default)
- But only need to get it once at start, or when target moves
3. **Absolute offset calculations** (one-time)
- Still need to calculate once for absolute offsets
- But only once, not every frame
---
## Implementation Plan
### Phase 1: Add Hook Transform Access
**File:** `SkeletonBuilder.cs` or `CECModel.cs`
```csharp
// Add method to get hook Transform (not just position)
public Transform GetHookTransform(string hookName, bool recursive = false)
{
if (m_hookCache.TryGetValue(hookName, out Transform hook))
return hook;
// Lookup hook by name
hook = GetHook(hookName, recursive);
if (hook != null)
m_hookCache[hookName] = hook;
return hook;
}
```
### Phase 2: Update Hit GFX Spawning
**File:** `CECSkillGfxMan.cs` - `CECSkillGfxEvent`
```csharp
private void SpawnHitGfx(Vector3 vTarget)
{
// Try to get target hook Transform
Transform targetHook = GetTargetHookTransform();
if (targetHook != null && m_pComposer.m_HitPos.bTraceTarget)
{
// Parent to hook - automatic updates!
m_hitGfxInstance = Instantiate(hitGfxPrefab);
m_hitGfxInstance.transform.SetParent(targetHook);
// Apply offset
if (m_pComposer.m_HitPos.bRelHook)
m_hitGfxInstance.transform.localPosition = m_pComposer.m_HitPos.vOffset;
else
{
// Absolute offset - calculate once
Vector3 offsetWorld = targetHook.root.TransformPoint(m_pComposer.m_HitPos.vOffset);
m_hitGfxInstance.transform.position = offsetWorld - targetHook.root.position + targetHook.position;
}
}
else
{
// Fallback: manual position
m_hitGfxInstance = Instantiate(hitGfxPrefab, vTarget, Quaternion.identity);
}
}
```
### Phase 3: Update Fly GFX Spawning
```csharp
private void SpawnFlyGfx()
{
Transform hostHook = GetHostHookTransform();
Vector3 spawnPos;
Quaternion spawnRot;
if (hostHook != null)
{
// Use hook Transform for initial position
if (m_pComposer.m_FlyPos.bRelHook)
{
spawnPos = hostHook.TransformPoint(m_pComposer.m_FlyPos.vOffset);
spawnRot = hostHook.rotation;
}
else
{
spawnPos = hostHook.position + m_pComposer.m_FlyPos.vOffset;
spawnRot = hostHook.rotation;
}
}
else
{
// Fallback: use calculated position
spawnPos = m_pMoveMethod.GetPos();
spawnRot = Quaternion.LookRotation(m_pMoveMethod.GetMoveDir());
}
m_flyGfxInstance = Instantiate(flyGfxPrefab, spawnPos, spawnRot);
// Don't parent - fly GFX moves independently
}
```
---
## Summary
### ✅ **YES, we can significantly simplify the hook system in Unity:**
1. **Hit GFX that follows target:** Parent to hook Transform → Unity handles updates automatically
2. **Fly GFX spawn position:** Use hook Transform.position (one-time) → No manual calculation
3. **Relative offsets:** Use `transform.localPosition` → No matrix math needed
4. **Absolute offsets:** Calculate once (not every frame) → Much simpler
### ⚠️ **Still need manual updates for:**
1. **Fly GFX during flight:** Must update position manually (moves independently)
2. **Target position lookup:** Still need to get target position (but only when needed)
### 🎯 **Result:**
- **Eliminates ~80% of manual position calculations**
- **Simpler, more Unity-native code**
- **Better performance** (Unity's transform system)
- **Easier to maintain**
---
**End of Document**