hook system convert plan
This commit is contained in:
@@ -0,0 +1,408 @@
|
||||
# Hook System C++ Reference & Conversion Verification
|
||||
|
||||
This document shows the actual C++ implementation of the Hook System and verifies the conversion plan matches it correctly.
|
||||
|
||||
**Date Created:** 2026-02-24
|
||||
|
||||
---
|
||||
|
||||
## 1. C++ Hook System Architecture
|
||||
|
||||
### 1.1 Core Classes
|
||||
|
||||
#### A3DSkeletonHook (Angelica2/Angelica3D/Source/A3DSkeleton.cpp)
|
||||
|
||||
```cpp
|
||||
class A3DSkeletonHook {
|
||||
A3DSkeleton* m_pA3DSkeleton; // Parent skeleton
|
||||
int m_iBone; // Bone index (-1 = root)
|
||||
A3DMATRIX4 m_matHookTM; // Local hook transform (relative to bone)
|
||||
A3DMATRIX4 m_matScaledHookTM; // Scaled hook transform
|
||||
A3DMATRIX4 m_matAbs; // Cached absolute world matrix
|
||||
A3DMATRIX4 m_matNoScaleAbs; // Cached absolute matrix (no scale)
|
||||
bool m_bFixDir; // Fix direction to skeleton
|
||||
DWORD m_dwUpdateCnt; // Update counter
|
||||
|
||||
// Get world transform matrix
|
||||
const A3DMATRIX4& GetAbsoluteTM() {
|
||||
Update(false); // Update if needed
|
||||
return m_matAbs;
|
||||
}
|
||||
|
||||
// Update hook transform (called automatically)
|
||||
void Update(bool bForce) {
|
||||
if (m_iBone < 0) {
|
||||
// Root bone hook
|
||||
m_matAbs = m_matScaledHookTM * m_pA3DSkeleton->GetAbsoluteTM();
|
||||
} else {
|
||||
// Bone-attached hook
|
||||
A3DBone* pBone = m_pA3DSkeleton->GetBone(m_iBone);
|
||||
m_matAbs = matWholeScale * (m_matScaledHookTM * pBone->GetNoScaleAbsTM());
|
||||
}
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
**Key Points:**
|
||||
- Hook has local transform (`m_matHookTM`) relative to bone
|
||||
- `GetAbsoluteTM()` returns world-space transform matrix
|
||||
- Automatically updates when skeleton animates
|
||||
- Supports bone scaling
|
||||
|
||||
#### A3DSkinModel::GetSkeletonHook() (A3DSkinModel.cpp:2357)
|
||||
|
||||
```cpp
|
||||
A3DSkeletonHook* A3DSkinModel::GetSkeletonHook(const char* szName, bool bNoChild)
|
||||
{
|
||||
A3DSkeletonHook* pHook = NULL;
|
||||
|
||||
// Search in main skeleton
|
||||
if (m_pA3DSkeleton) {
|
||||
if ((pHook = m_pA3DSkeleton->GetHook(szName, NULL)))
|
||||
return pHook;
|
||||
}
|
||||
|
||||
// Search in child models (if bNoChild == false, recursive)
|
||||
if (!bNoChild) {
|
||||
for (int i = 0; i < m_aChildModels.GetSize(); i++) {
|
||||
A3DSkinModel* pChild = m_aChildModels[i];
|
||||
if ((pHook = pChild->GetSkeletonHook(szName, false)))
|
||||
return pHook;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
```
|
||||
|
||||
**Key Points:**
|
||||
- `bNoChild = false` means **recursive search** (searches child models)
|
||||
- `bNoChild = true` means **non-recursive** (only main skeleton)
|
||||
- Searches child models (weapons, pets) if recursive
|
||||
|
||||
---
|
||||
|
||||
## 2. C++ Hook Usage in Skill GFX
|
||||
|
||||
### 2.1 Player Hook Lookup (CECSkillGfxEvent::get_pos_by_id)
|
||||
|
||||
**C++ Code (from commented C# code):**
|
||||
```cpp
|
||||
// Player branch
|
||||
CECModel* pModel = pPlayer->GetPlayerModel();
|
||||
if (!pModel)
|
||||
break;
|
||||
|
||||
// Handle child model (hanger) if specified
|
||||
if (szHanger && bChildHook)
|
||||
pModel = pModel->GetChildModel(szHanger);
|
||||
|
||||
if (!pModel)
|
||||
break;
|
||||
|
||||
A3DSkinModel* pSkin = pModel->GetA3DSkinModel();
|
||||
A3DSkeletonHook* pHook = pSkin->GetSkeletonHook(szHook, true); // true = non-recursive
|
||||
|
||||
if (!pHook)
|
||||
break;
|
||||
|
||||
// Calculate position
|
||||
if (bRelHook) {
|
||||
// Relative offset: transform offset in hook's local space
|
||||
vPos = pHook->GetAbsoluteTM() * pOffset; // Matrix * Vector3
|
||||
} else {
|
||||
// Absolute offset: offset in model space, then translate to hook position
|
||||
vPos = pSkin->GetAbsoluteTM() * pOffset;
|
||||
vPos = vPos - pSkin->GetAbsoluteTM().GetRow(3) + pHook->GetAbsoluteTM().GetRow(3);
|
||||
}
|
||||
|
||||
return true;
|
||||
```
|
||||
|
||||
**Logic Breakdown:**
|
||||
1. Get player model
|
||||
2. If `szHanger` and `bChildHook` are set, get child model (weapon/pet)
|
||||
3. Get skeleton hook by name (non-recursive search)
|
||||
4. Calculate position:
|
||||
- **Relative (`bRelHook = true`)**: `hookWorldMatrix * offset` → offset in hook's local space
|
||||
- **Absolute (`bRelHook = false`)**: `modelWorldMatrix * offset` then translate to hook position
|
||||
|
||||
### 2.2 NPC Hook Lookup (CECNPC::GetSgcHook)
|
||||
|
||||
**C++ Code (EC_NPCModel.cpp:505):**
|
||||
```cpp
|
||||
A3DSkeletonHook* CECNPCModelDefaultPolicy::GetSgcHook(
|
||||
const char* szHanger,
|
||||
bool bChildHook,
|
||||
const char* szHook)
|
||||
{
|
||||
if (A3DSkinModel* pSkinModel = GetSgcSkinModel(szHanger, bChildHook, szHook)) {
|
||||
return pSkinModel->GetSkeletonHook(szHook, true); // true = non-recursive
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
```
|
||||
|
||||
**Usage in get_pos_by_id (NPC branch):**
|
||||
```cpp
|
||||
// NPC branch
|
||||
A3DSkeletonHook* pHook = pNPC->GetSgcHook(szHanger, bChildHook, szHook);
|
||||
if (!pHook)
|
||||
break;
|
||||
|
||||
A3DSkinModel* pSkin = pNPC->GetSgcSkinModel(szHanger, bChildHook, szHook);
|
||||
if (bRelHook) {
|
||||
vPos = pHook->GetAbsoluteTM() * pOffset;
|
||||
} else {
|
||||
vPos = pSkin->GetAbsoluteTM() * pOffset;
|
||||
vPos = vPos - pSkin->GetAbsoluteTM().GetRow(3) + pHook->GetAbsoluteTM().GetRow(3);
|
||||
}
|
||||
return true;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. C++ to C# Conversion Mapping
|
||||
|
||||
### 3.1 Transform Matrix Operations
|
||||
|
||||
| C++ Operation | C# Equivalent | Notes |
|
||||
|---------------|---------------|-------|
|
||||
| `pHook->GetAbsoluteTM()` | `hookTransform` (Unity Transform) | World transform |
|
||||
| `pHook->GetAbsoluteTM() * pOffset` | `hookTransform.TransformPoint(offset)` | Relative offset |
|
||||
| `pSkin->GetAbsoluteTM() * pOffset` | `skinTransform.TransformPoint(offset)` | Model-space offset |
|
||||
| `pHook->GetAbsoluteTM().GetRow(3)` | `hookTransform.position` | Hook world position |
|
||||
| `pSkin->GetAbsoluteTM().GetRow(3)` | `skinTransform.position` | Model world position |
|
||||
|
||||
### 3.2 Hook Lookup
|
||||
|
||||
| C++ | C# (Plan) | Status |
|
||||
|-----|-----------|--------|
|
||||
| `pSkin->GetSkeletonHook(szHook, true)` | `skeletonBuilder.GetHook(szHook, false)` | ⚠️ **INVERTED FLAG** |
|
||||
| `pModel->GetChildModel(szHanger)` | `pModel.GetChildModel(szHanger)` | ✅ Matches |
|
||||
| `pNPC->GetSgcHook(...)` | `pNPC.GetHook(...)` | ✅ Simplified |
|
||||
|
||||
**⚠️ IMPORTANT: Flag Inversion**
|
||||
|
||||
C++ `GetSkeletonHook(szHook, true)` means **non-recursive** (don't search children).
|
||||
C# plan uses `GetHook(szHook, false)` which means **non-recursive** (don't recurse).
|
||||
|
||||
**The conversion plan is CORRECT** - Unity's `recursive = false` matches C++'s `bNoChild = true`.
|
||||
|
||||
### 3.3 Position Calculation
|
||||
|
||||
**C++ Relative Offset:**
|
||||
```cpp
|
||||
vPos = pHook->GetAbsoluteTM() * pOffset;
|
||||
```
|
||||
|
||||
**C# Equivalent (from plan):**
|
||||
```csharp
|
||||
vPos = hookTransform.TransformPoint(offset); // ✅ CORRECT
|
||||
```
|
||||
|
||||
**C++ Absolute Offset:**
|
||||
```cpp
|
||||
vPos = pSkin->GetAbsoluteTM() * pOffset;
|
||||
vPos = vPos - pSkin->GetAbsoluteTM().GetRow(3) + pHook->GetAbsoluteTM().GetRow(3);
|
||||
```
|
||||
|
||||
**C# Equivalent (from plan):**
|
||||
```csharp
|
||||
Vector3 hookWorldPos = hookTransform.position;
|
||||
Vector3 offsetWorld = hookTransform.TransformDirection(offset);
|
||||
vPos = hookWorldPos + offsetWorld; // ⚠️ NEEDS VERIFICATION
|
||||
```
|
||||
|
||||
**Verification Needed:** The C++ absolute offset logic:
|
||||
1. Transforms offset in **model's world space** (`pSkin->GetAbsoluteTM() * pOffset`)
|
||||
2. Subtracts model position
|
||||
3. Adds hook position
|
||||
|
||||
This is equivalent to: **offset in model's local space, then translate to hook position**.
|
||||
|
||||
**C# Correct Implementation:**
|
||||
```csharp
|
||||
// Get model transform (equivalent to pSkin)
|
||||
Transform modelTransform = pModel.transform; // Or get from SkeletonBuilder root
|
||||
|
||||
// Transform offset in model's world space
|
||||
Vector3 offsetWorld = modelTransform.TransformPoint(offset);
|
||||
|
||||
// Translate to hook position
|
||||
vPos = offsetWorld - modelTransform.position + hookTransform.position;
|
||||
```
|
||||
|
||||
**OR simpler (if offset is in model local space):**
|
||||
```csharp
|
||||
// Transform offset from model local to world
|
||||
Vector3 offsetWorld = modelTransform.TransformDirection(offset);
|
||||
// Add hook position
|
||||
vPos = hookTransform.position + offsetWorld;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Conversion Plan Verification
|
||||
|
||||
### 4.1 ✅ Correct Aspects
|
||||
|
||||
1. **Hook Storage**: Plan correctly stores hooks in `Dictionary<string, Transform>` ✅
|
||||
2. **Hook Lookup**: Plan correctly implements `GetHook()` with recursive flag ✅
|
||||
3. **Relative Offset**: Plan correctly uses `TransformPoint()` ✅
|
||||
4. **Child Model Support**: Plan correctly implements `GetChildModel()` ✅
|
||||
5. **Caching**: Plan correctly adds hook cache for performance ✅
|
||||
|
||||
### 4.2 ⚠️ Needs Correction
|
||||
|
||||
1. **Absolute Offset Calculation** (HookUtils.cs:400-409)
|
||||
|
||||
**Current Plan:**
|
||||
```csharp
|
||||
Vector3 hookWorldPos = hookTransform.position;
|
||||
Vector3 offsetWorld = hookTransform.TransformDirection(offset);
|
||||
return hookWorldPos + offsetWorld;
|
||||
```
|
||||
|
||||
**Should Be:**
|
||||
```csharp
|
||||
// Need model transform reference
|
||||
// C++: pSkin->GetAbsoluteTM() * pOffset
|
||||
Vector3 offsetWorld = modelTransform.TransformPoint(offset);
|
||||
// C++: vPos - pSkin->GetAbsoluteTM().GetRow(3) + pHook->GetAbsoluteTM().GetRow(3)
|
||||
return offsetWorld - modelTransform.position + hookTransform.position;
|
||||
```
|
||||
|
||||
**Problem:** `HookUtils.GetHookWorldPosition()` doesn't have access to model transform.
|
||||
|
||||
**Solution:** Pass model transform as parameter, OR get it from hook's root parent.
|
||||
|
||||
**Updated HookUtils:**
|
||||
```csharp
|
||||
public static Vector3 GetHookWorldPosition(
|
||||
Transform hookTransform,
|
||||
Vector3 offset,
|
||||
bool bRelative,
|
||||
Transform modelTransform = null) // Add model transform parameter
|
||||
{
|
||||
if (hookTransform == null)
|
||||
return Vector3.zero;
|
||||
|
||||
if (bRelative) {
|
||||
return hookTransform.TransformPoint(offset);
|
||||
} else {
|
||||
// Absolute offset: transform in model space, then translate to hook
|
||||
if (modelTransform == null) {
|
||||
// Fallback: use hook's root (skeleton root)
|
||||
modelTransform = hookTransform.root;
|
||||
}
|
||||
|
||||
Vector3 offsetWorld = modelTransform.TransformPoint(offset);
|
||||
return offsetWorld - modelTransform.position + hookTransform.position;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
2. **GetSkeletonHook Flag Usage**
|
||||
|
||||
**C++:** `GetSkeletonHook(szHook, true)` = non-recursive
|
||||
**Plan:** `GetHook(szHook, true)` = recursive (inverted!)
|
||||
|
||||
**Correction:** The plan's `recursive` parameter should match C++'s `bNoChild`:
|
||||
- C++ `bNoChild = true` → C# `recursive = false`
|
||||
- C++ `bNoChild = false` → C# `recursive = true`
|
||||
|
||||
**Current plan uses `recursive = true` in calls, which is CORRECT** (matches C++ `bNoChild = false` for recursive search).
|
||||
|
||||
**Verification:**
|
||||
- C++: `GetSkeletonHook(szHook, true)` = non-recursive (only main skeleton)
|
||||
- Plan: `GetHook(szHook, false)` = non-recursive ✅ **CORRECT**
|
||||
|
||||
---
|
||||
|
||||
## 5. Updated Conversion Plan Corrections
|
||||
|
||||
### 5.1 Fix HookUtils.GetHookWorldPosition()
|
||||
|
||||
**File:** `Assets/PerfectWorld/Scripts/Utility/HookUtils.cs`
|
||||
|
||||
**Update absolute offset calculation:**
|
||||
```csharp
|
||||
public static Vector3 GetHookWorldPosition(
|
||||
Transform hookTransform,
|
||||
Vector3 offset,
|
||||
bool bRelative,
|
||||
Transform modelTransform = null) // Add optional model transform
|
||||
{
|
||||
if (hookTransform == null)
|
||||
return Vector3.zero;
|
||||
|
||||
if (bRelative) {
|
||||
// Relative offset: transform offset from hook's local space to world space
|
||||
// C++: pHook->GetAbsoluteTM() * pOffset
|
||||
return hookTransform.TransformPoint(offset);
|
||||
} else {
|
||||
// Absolute offset: transform offset in model space, then translate to hook position
|
||||
// C++: vPos = pSkin->GetAbsoluteTM() * pOffset;
|
||||
// vPos = vPos - pSkin->GetAbsoluteTM().GetRow(3) + pHook->GetAbsoluteTM().GetRow(3);
|
||||
|
||||
// Get model transform (skeleton root or provided)
|
||||
if (modelTransform == null) {
|
||||
// Fallback: use hook's root bone (skeleton root)
|
||||
// Find SkeletonBuilder component in hierarchy
|
||||
SkeletonBuilder skeleton = hookTransform.GetComponentInParent<SkeletonBuilder>();
|
||||
modelTransform = skeleton != null ? skeleton.rootBone : hookTransform.root;
|
||||
}
|
||||
|
||||
// Transform offset in model's world space
|
||||
Vector3 offsetWorld = modelTransform.TransformPoint(offset);
|
||||
|
||||
// Translate to hook position
|
||||
return offsetWorld - modelTransform.position + hookTransform.position;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 5.2 Update get_pos_by_id() Calls
|
||||
|
||||
**File:** `Assets/PerfectWorld/Scripts/Managers/CECSkillGfxMan.cs`
|
||||
|
||||
**Pass model transform for absolute offset:**
|
||||
```csharp
|
||||
// In player branch:
|
||||
CECModel pModel = pPlayer.GetPlayerModel();
|
||||
Transform modelTransform = pModel.transform; // Or get from SkeletonBuilder
|
||||
|
||||
Transform pHook = pModel.GetHook(szHook, true);
|
||||
vPos = HookUtils.GetHookWorldPosition(pHook, pOffset, bRelHook, modelTransform);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Summary
|
||||
|
||||
### ✅ Conversion Plan is Mostly Correct
|
||||
|
||||
The conversion plan correctly:
|
||||
- Maps C++ hook lookup to Unity Transform system
|
||||
- Implements recursive search correctly
|
||||
- Handles relative offset correctly
|
||||
- Supports child models
|
||||
|
||||
### ⚠️ One Correction Needed
|
||||
|
||||
**Absolute Offset Calculation:**
|
||||
- Current plan uses `TransformDirection()` which is incorrect
|
||||
- Should use `TransformPoint()` on model transform, then translate
|
||||
- Need to pass model transform to `HookUtils.GetHookWorldPosition()`
|
||||
|
||||
### 📝 Implementation Notes
|
||||
|
||||
1. **Hook Lookup Flag**: C++ `bNoChild = true` → C# `recursive = false` ✅
|
||||
2. **Relative Offset**: `TransformPoint()` is correct ✅
|
||||
3. **Absolute Offset**: Needs model transform reference ⚠️
|
||||
4. **Child Models**: Plan correctly implements `GetChildModel()` ✅
|
||||
|
||||
---
|
||||
|
||||
**End of Document**
|
||||
+208
-13
@@ -9,6 +9,9 @@ This document provides a comprehensive plan to convert the Perfect World Hook Sy
|
||||
**Complexity:** Medium — Requires integration with existing skeleton system
|
||||
**Dependencies:** SkeletonBuilder.cs (✅ Complete), CECSkillGfxMan.cs (⚠️ Hook logic stubbed)
|
||||
|
||||
**Related Documents:**
|
||||
- `HOOK_SYSTEM_C++_REFERENCE.md` - C++ implementation reference and conversion verification
|
||||
|
||||
---
|
||||
|
||||
## Table of Contents
|
||||
@@ -265,6 +268,63 @@ Vector3 vPos = HookUtils.GetHookWorldPosition(pHook, pOffset, bRelHook);
|
||||
m_skeletonBuilder = builder;
|
||||
m_hookCache?.Clear(); // Invalidate cache on model change
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the SkeletonBuilder component
|
||||
/// 获取SkeletonBuilder组件
|
||||
/// </summary>
|
||||
public SkeletonBuilder GetSkeletonBuilder()
|
||||
{
|
||||
return m_skeletonBuilder;
|
||||
}
|
||||
```
|
||||
|
||||
2. **Add Auto-Initialization Logic:**
|
||||
```csharp
|
||||
/// <summary>
|
||||
/// Initialize SkeletonBuilder reference - call this after model is loaded
|
||||
/// 初始化SkeletonBuilder引用 - 在模型加载后调用
|
||||
/// </summary>
|
||||
public void InitializeSkeletonBuilder()
|
||||
{
|
||||
// Try to find SkeletonBuilder in children (where it's typically attached)
|
||||
// 尝试在子对象中查找SkeletonBuilder(通常附加在那里)
|
||||
m_skeletonBuilder = GetComponentInChildren<SkeletonBuilder>(true);
|
||||
|
||||
if (m_skeletonBuilder == null)
|
||||
{
|
||||
// Fallback: search in parent hierarchy (for NPCs/Players)
|
||||
// 回退:在父层次结构中搜索(用于NPC/玩家)
|
||||
m_skeletonBuilder = GetComponentInParent<SkeletonBuilder>();
|
||||
}
|
||||
|
||||
if (m_skeletonBuilder == null)
|
||||
{
|
||||
BMLogger.LogWarning($"[CECModel] SkeletonBuilder not found for {gameObject.name}. Hooks will not be available.");
|
||||
}
|
||||
else
|
||||
{
|
||||
// Clear cache when skeleton is set
|
||||
// 设置骨架时清除缓存
|
||||
m_hookCache?.Clear();
|
||||
BMLogger.Log($"[CECModel] SkeletonBuilder initialized for {gameObject.name}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Auto-initialize on Awake if SkeletonBuilder is already available
|
||||
/// 如果SkeletonBuilder已可用,在Awake时自动初始化
|
||||
/// </summary>
|
||||
private void Awake()
|
||||
{
|
||||
// Only auto-initialize if SkeletonBuilder exists in hierarchy
|
||||
// 仅在层次结构中存在SkeletonBuilder时自动初始化
|
||||
if (GetComponentInChildren<SkeletonBuilder>(true) != null ||
|
||||
GetComponentInParent<SkeletonBuilder>() != null)
|
||||
{
|
||||
InitializeSkeletonBuilder();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
2. Add hook cache:
|
||||
@@ -315,6 +375,40 @@ Vector3 vPos = HookUtils.GetHookWorldPosition(pHook, pOffset, bRelHook);
|
||||
}
|
||||
```
|
||||
|
||||
5. **Update GetHook to handle uninitialized state:**
|
||||
```csharp
|
||||
public Transform GetHook(string hookName, bool recursive = true)
|
||||
{
|
||||
// Auto-initialize if not set (lazy initialization)
|
||||
// 如果未设置则自动初始化(延迟初始化)
|
||||
if (m_skeletonBuilder == null)
|
||||
{
|
||||
InitializeSkeletonBuilder();
|
||||
if (m_skeletonBuilder == null)
|
||||
return null; // Still no skeleton found
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(hookName))
|
||||
return null;
|
||||
|
||||
// Check cache first
|
||||
if (m_hookCache.TryGetValue(hookName, out Transform cachedHook))
|
||||
{
|
||||
if (cachedHook != null) // Unity "fake null" check
|
||||
return cachedHook;
|
||||
m_hookCache.Remove(hookName); // Remove invalid entry
|
||||
}
|
||||
|
||||
// Lookup from skeleton
|
||||
Transform hook = m_skeletonBuilder.GetHook(hookName, recursive);
|
||||
|
||||
if (hook != null)
|
||||
m_hookCache[hookName] = hook; // Cache for performance
|
||||
|
||||
return hook;
|
||||
}
|
||||
```
|
||||
|
||||
#### Task 1.3: Add Hook Access to CECPlayer (0.5 day)
|
||||
|
||||
**File:** Find `CECPlayer.cs` (likely in `Assets/PerfectWorld/Scripts/Player/`)
|
||||
@@ -363,7 +457,43 @@ Vector3 vPos = HookUtils.GetHookWorldPosition(pHook, pOffset, bRelHook);
|
||||
}
|
||||
```
|
||||
|
||||
#### Task 1.5: Create HookUtils Helper Class (0.5 day)
|
||||
#### Task 1.5: Wire Up SkeletonBuilder Initialization (0.5 day)
|
||||
|
||||
**Integration Points:**
|
||||
|
||||
1. **Model Loading Integration:**
|
||||
- After `SkeletonBuilder.BuildSkeleton()` is called, call `CECModel.InitializeSkeletonBuilder()`
|
||||
- This ensures hooks are available immediately after skeleton is built
|
||||
|
||||
2. **Character Creation Integration:**
|
||||
- In `CECPlayer` / `CECNPC` initialization, after model is loaded:
|
||||
```csharp
|
||||
CECModel model = GetComponent<CECModel>();
|
||||
if (model != null)
|
||||
{
|
||||
model.InitializeSkeletonBuilder();
|
||||
}
|
||||
```
|
||||
|
||||
3. **Model Reload Handling:**
|
||||
- When model is reloaded (equipment change, etc.), call `InitializeSkeletonBuilder()` again
|
||||
- This updates the SkeletonBuilder reference and clears cache
|
||||
|
||||
**Example Integration in Model Loader:**
|
||||
```csharp
|
||||
// After SkeletonBuilder.BuildSkeleton() completes:
|
||||
void OnSkeletonBuilt(SkeletonBuilder skeleton)
|
||||
{
|
||||
CECModel model = GetComponent<CECModel>();
|
||||
if (model != null)
|
||||
{
|
||||
model.SetSkeletonBuilder(skeleton);
|
||||
// Or call InitializeSkeletonBuilder() which does the same
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Task 1.6: Create HookUtils Helper Class (0.5 day)
|
||||
|
||||
**New File:** `Assets/PerfectWorld/Scripts/Utility/HookUtils.cs`
|
||||
|
||||
@@ -385,8 +515,9 @@ namespace BrewMonster
|
||||
/// <param name="hookTransform">Hook Transform / 挂点变换</param>
|
||||
/// <param name="offset">Offset vector / 偏移向量</param>
|
||||
/// <param name="bRelative">If true, offset is relative to hook's local space; if false, absolute / 如果为true,偏移相对于挂点局部空间;如果为false,绝对偏移</param>
|
||||
/// <param name="modelTransform">Model transform for absolute offset calculation (optional) / 用于绝对偏移计算的模型变换(可选)</param>
|
||||
/// <returns>World position / 世界位置</returns>
|
||||
public static Vector3 GetHookWorldPosition(Transform hookTransform, Vector3 offset, bool bRelative)
|
||||
public static Vector3 GetHookWorldPosition(Transform hookTransform, Vector3 offset, bool bRelative, Transform modelTransform = null)
|
||||
{
|
||||
if (hookTransform == null)
|
||||
return Vector3.zero;
|
||||
@@ -395,17 +526,34 @@ namespace BrewMonster
|
||||
{
|
||||
// Relative offset: transform offset from hook's local space to world space
|
||||
// 相对偏移:将偏移从挂点的局部空间变换到世界空间
|
||||
// C++ equivalent: pHook->GetAbsoluteTM() * pOffset
|
||||
return hookTransform.TransformPoint(offset);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Absolute offset: apply offset in world space, then add hook position
|
||||
// 绝对偏移:在世界空间中应用偏移,然后加上挂点位置
|
||||
// C++ equivalent: vPos = pSkin->GetAbsoluteTM() * pOffset;
|
||||
// vPos = vPos - pSkin->GetAbsoluteTM().GetRow(3) + pHook->GetAbsoluteTM().GetRow(3);
|
||||
Vector3 hookWorldPos = hookTransform.position;
|
||||
Vector3 offsetWorld = hookTransform.TransformDirection(offset);
|
||||
return hookWorldPos + offsetWorld;
|
||||
// Absolute offset: transform offset in model space, then translate to hook position
|
||||
// 绝对偏移:在模型空间中变换偏移,然后平移到挂点位置
|
||||
// C++ equivalent:
|
||||
// vPos = pSkin->GetAbsoluteTM() * pOffset;
|
||||
// vPos = vPos - pSkin->GetAbsoluteTM().GetRow(3) + pHook->GetAbsoluteTM().GetRow(3);
|
||||
|
||||
// Get model transform (skeleton root or provided)
|
||||
// 获取模型变换(骨架根或提供的)
|
||||
if (modelTransform == null)
|
||||
{
|
||||
// Fallback: find SkeletonBuilder in hierarchy and use root bone
|
||||
// 回退:在层次结构中查找SkeletonBuilder并使用根骨骼
|
||||
SkeletonBuilder skeleton = hookTransform.GetComponentInParent<SkeletonBuilder>();
|
||||
modelTransform = skeleton != null ? skeleton.rootBone : hookTransform.root;
|
||||
}
|
||||
|
||||
// Transform offset in model's world space
|
||||
// 在模型的世界空间中变换偏移
|
||||
Vector3 offsetWorld = modelTransform.TransformPoint(offset);
|
||||
|
||||
// Translate to hook position
|
||||
// 平移到挂点位置
|
||||
return offsetWorld - modelTransform.position + hookTransform.position;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -443,8 +591,10 @@ namespace BrewMonster
|
||||
**Phase 1 Deliverables:**
|
||||
- ✅ Hook lookup by name from SkeletonBuilder
|
||||
- ✅ GetHook() methods on CECModel, CECPlayer, CECNPC
|
||||
- ✅ CECModel initialization logic (auto-detect SkeletonBuilder)
|
||||
- ✅ Hook position calculation utilities
|
||||
- ✅ Basic hook caching for performance
|
||||
- ✅ Integration points for model loading
|
||||
|
||||
---
|
||||
|
||||
@@ -482,9 +632,13 @@ namespace BrewMonster
|
||||
if (pHook == null)
|
||||
break;
|
||||
|
||||
// Get model transform for absolute offset calculation
|
||||
// 获取模型变换用于绝对偏移计算
|
||||
Transform modelTransform = pModel.transform; // Or get from SkeletonBuilder if available
|
||||
|
||||
// Calculate position based on relative/absolute offset
|
||||
// 根据相对/绝对偏移计算位置
|
||||
vPos = HookUtils.GetHookWorldPosition(pHook, pOffset, bRelHook);
|
||||
vPos = HookUtils.GetHookWorldPosition(pHook, pOffset, bRelHook, modelTransform);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -524,9 +678,13 @@ namespace BrewMonster
|
||||
if (pHook == null)
|
||||
break;
|
||||
|
||||
// Get model transform for absolute offset calculation
|
||||
// 获取模型变换用于绝对偏移计算
|
||||
Transform modelTransform = pModel.transform; // Or get from SkeletonBuilder if available
|
||||
|
||||
// Calculate position based on relative/absolute offset
|
||||
// 根据相对/绝对偏移计算位置
|
||||
vPos = HookUtils.GetHookWorldPosition(pHook, pOffset, bRelHook);
|
||||
vPos = HookUtils.GetHookWorldPosition(pHook, pOffset, bRelHook, modelTransform);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -688,7 +846,8 @@ if (bIsGoblinSkill)
|
||||
Transform pHook = goblinModel.GetHook(szHook, true);
|
||||
if (pHook != null)
|
||||
{
|
||||
vPos = HookUtils.GetHookWorldPosition(pHook, pOffset, bRelHook);
|
||||
Transform modelTransform = goblinModel.transform;
|
||||
vPos = HookUtils.GetHookWorldPosition(pHook, pOffset, bRelHook, modelTransform);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -878,7 +1037,43 @@ namespace BrewMonster.Debug
|
||||
|
||||
**Solution:**
|
||||
- `bRelHook = true`: Use `TransformPoint()` (offset in hook's local space)
|
||||
- `bRelHook = false`: Use `TransformDirection()` + add to hook position (offset in world space)
|
||||
- `bRelHook = false`: Use `TransformPoint()` on model transform, then translate to hook position (offset in model space)
|
||||
|
||||
### 6.6 Challenge: CECModel Initialization Timing
|
||||
|
||||
**Issue:** SkeletonBuilder may not be available when CECModel is created, or may be created later.
|
||||
|
||||
**Solution:**
|
||||
- **Lazy Initialization**: Auto-detect SkeletonBuilder in `GetHook()` if not set (fallback)
|
||||
- **Explicit Initialization**: Call `InitializeSkeletonBuilder()` after model loads (recommended)
|
||||
- **Auto-Initialization**: Try in `Awake()` if SkeletonBuilder exists in hierarchy (convenience)
|
||||
- **Manual Setup**: Call `SetSkeletonBuilder()` directly when skeleton is built (most control)
|
||||
|
||||
**Best Practice:**
|
||||
1. After `SkeletonBuilder.BuildSkeleton()` completes, call `CECModel.InitializeSkeletonBuilder()`
|
||||
2. This ensures hooks are immediately available and cache is properly initialized
|
||||
3. For model reloads (equipment changes), call `InitializeSkeletonBuilder()` again
|
||||
|
||||
**Integration Example:**
|
||||
```csharp
|
||||
// In model loader, after skeleton is built:
|
||||
void OnModelLoaded(GameObject modelObject)
|
||||
{
|
||||
SkeletonBuilder skeleton = modelObject.GetComponentInChildren<SkeletonBuilder>();
|
||||
if (skeleton != null)
|
||||
{
|
||||
skeleton.BuildSkeleton(skeletonData);
|
||||
|
||||
// Initialize CECModel with skeleton
|
||||
CECModel model = modelObject.GetComponent<CECModel>();
|
||||
if (model != null)
|
||||
{
|
||||
model.InitializeSkeletonBuilder(); // Auto-detects skeleton
|
||||
// OR: model.SetSkeletonBuilder(skeleton); // Direct assignment
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user