13 KiB
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:
- Parent GFX GameObject directly to hook Transform?
- Eliminate the need for
GetHookWorldPosition()calculations? - 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:
// A3DSkillGfxEvent2.cpp:502-508
if (m_bTraceTarget)
{
A3DMATRIX4 matTran;
matTran.Identity();
matTran.SetRow(3, GetTargetCenter()); // Updates every frame
m_pHitGfx->SetParentTM(matTran);
}
Unity Approach:
// 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:
// 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:
// 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:
// Relative offset: transform offset in hook's local space
if (bRelHook) {
vPos = pHook->GetAbsoluteTM() * (*pOffset);
}
Unity Approach:
// 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:
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:
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:
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
Transforminstead ofVector3 - 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:
-
Hit GFX position updates (if
bTraceTarget = true)- Instead of:
UpdateHitGfxPosition()every frame - Use:
transform.SetParent(hookTransform)once
- Instead of:
-
Initial spawn position calculation
- Instead of:
HookUtils.GetHookWorldPosition(hook, offset, bRelHook) - Use:
hookTransform.positionorhookTransform.TransformPoint(offset)
- Instead of:
-
Relative offset calculations (for hit GFX)
- Instead of: Manual matrix multiplication every frame
- Use:
transform.localPosition = offsetonce
❌ Cannot Eliminate:
-
Fly GFX position during flight
- Must update manually (moves independently)
- But can use hook Transform for initial position
-
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
-
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
// 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
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
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:
- Hit GFX that follows target: Parent to hook Transform → Unity handles updates automatically
- Fly GFX spawn position: Use hook Transform.position (one-time) → No manual calculation
- Relative offsets: Use
transform.localPosition→ No matrix math needed - Absolute offsets: Calculate once (not every frame) → Much simpler
⚠️ Still need manual updates for:
- Fly GFX during flight: Must update position manually (moves independently)
- 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