# 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(); 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**