465 lines
19 KiB
Markdown
465 lines
19 KiB
Markdown
# Animation Test Scene Setup
|
||
|
||
A standalone scene for testing character animations, skill VFX, weapon logic, and shape-change
|
||
without any server connection. All triggers are issued via local commands.
|
||
|
||
**Skill test workflow (AnimatedModifier / LogPanel / SkillPanel):** see [`AnimationSceneSkillTest.md`](AnimationSceneSkillTest.md).
|
||
|
||
---
|
||
|
||
## Related Source Files
|
||
|
||
| File | Namespace / Class | Role |
|
||
|---|---|---|
|
||
| `Assets/PerfectWorld/Scripts/Move/CECPlayer.cs` | `BrewMonster.CECPlayer` | Abstract base — model load, action dispatch, weapon, shape, GFX state |
|
||
| `Assets/Scripts/CECHostPlayer.cs` | `BrewMonster.CECHostPlayer` | Concrete host player — message routing, skill prep, combat messages |
|
||
| `Assets/Scripts/CECHostPlayer.Skill.cs` | partial `CECHostPlayer` | Skill shortcut, `GetNormalSkill`, spam guard |
|
||
| `Assets/Scripts/CECHostPlayer.Combat.cs` | partial `CECHostPlayer` | `PlayAttackEffect` callers, melee/skill result handlers |
|
||
| `Assets/PerfectWorld/Scripts/NPC/CECModel.cs` | `BrewMonster.CECModel` | Wraps model `GameObject`; manages `SkeletonBuilder`, hooks, `NamedAnimancerComponent` |
|
||
| `Assets/PerfectWorld/Scripts/Players/CECPlayerActionController.cs` | `CECPlayerActionController` | Routes Play/Queue calls to the active `CECPlayerActionPlayPolicy` |
|
||
| `Assets/PerfectWorld/Scripts/Players/CECPlayerActionPlayPolicy.cs` | `CECPlayerActionPlayPolicy` | Default policy (no split-body); drives Animancer clips |
|
||
| `Assets/PerfectWorld/Scripts/Managers/NPCManager.cs` | `NPCManager` | Async model loader (`GetModelPlayer`, `GetDummyModel`) |
|
||
| `Assets/PerfectWorld/Scripts/Managers/CECAttacksMan.cs` | `CECAttacksMan` | Singleton: attack events, skill GFX preload, state-action config |
|
||
| `Assets/PerfectWorld/Scripts/Managers/CECSkillGfxMan.cs` | `CECSkillGfxMan` / `CECSkillGfxEvent` | Skill GFX event state-machine (fly → hit → ground-hit) |
|
||
| `Assets/PerfectWorld/Scripts/Managers/A3DSkillGfxMan.cs` | `A3DSkillGfxMan` | Low-level GFX composer manager |
|
||
| `Assets/PerfectWorld/Scripts/Vfx/A3DSkillGfxComposerMan.cs` | `A3DSkillGfxComposerMan` | Per-skill GFX composer (fly/hit/ground-hit paths) |
|
||
| `Assets/PerfectWorld/Scripts/Sound/SFXManager.cs` | `SFXManager` | Sound pool, `sound.txt` table, `PlaySkillSfxAtPointAsync` |
|
||
|
||
---
|
||
|
||
## 1. Model Loading
|
||
|
||
### Entry Point
|
||
`CECPlayer.SetPlayerModel(byte profession, byte gender)` (async)
|
||
|
||
### Flow
|
||
```
|
||
SetPlayerModel(profession, gender)
|
||
└─ InitializePlayerCECModel(profession, gender)
|
||
├─ NPCManager.Instance.GetModelPlayer(profession, gender)
|
||
│ └─ Loads .ecm prefab from Addressables / StreamingAssets
|
||
│ path: "models/players/形象/<class><gender>/躯干/<class><gender>.ecm"
|
||
├─ Finds SkeletonBuilder on model (retries next frame if not ready)
|
||
├─ Finds CombineActHolder → reads CombinedActionSO
|
||
├─ CECModel.SetSkeletonBuilder / SetNamedAnimancerComponent / SetTransform
|
||
├─ CECModel.SetCombinedAction (action map)
|
||
└─ CECModel.InitializeSkeletonBuilder() ← hooks now available
|
||
└─ AttachWeapon()
|
||
└─ RecreateActionController()
|
||
└─ BuildActionList() ← maps skill IDs → PLAYER_ACTION_INFO_CONFIG
|
||
└─ PlayAction(ACT_STAND / ACT_FIGHTSTAND)
|
||
```
|
||
|
||
### Key Model Paths (NPCManager._playerModelPaths)
|
||
| Index | Path |
|
||
|---|---|
|
||
| 0 | `models/players/形象/武侠男/躯干/武侠男.ecm` |
|
||
| 1 | `models/players/形象/武侠女/躯干/武侠女.ecm` |
|
||
| 2 | `models/players/形象/法师男/躯干/法师男.ecm` |
|
||
| 3 | `models/players/形象/法师女/躯干/法师女.ecm` |
|
||
| 4–5 | 巫师男/女 |
|
||
| 6–9 | 妖族 (妖精, 妖兽男) |
|
||
| 10–11 | 刺客男/女 |
|
||
| 12–15 | 羽族 (羽芒, 羽灵) |
|
||
| 16–19 | 灵族 (剑灵, 魅灵) |
|
||
| 20+ | Profession transform models (白虎, 火狐狸, 影族变身…) |
|
||
| 24+ | Skill transform dummy models (金钱蛙, 婚礼童, 树鸡, 龙…) |
|
||
|
||
---
|
||
|
||
## 2. Animation System
|
||
|
||
### Components on Model Prefab
|
||
- `SkeletonBuilder` — builds skeleton and registers hook points
|
||
- `NamedAnimancerComponent` — Animancer component; clips referenced by name
|
||
- `CombineActHolder` → `CombinedActionSO` — maps action prefix+weapon suffix to clip names
|
||
|
||
### CECPlayerActionController
|
||
Located: `Assets/PerfectWorld/Scripts/Players/CECPlayerActionController.cs`
|
||
|
||
Channels:
|
||
- `ACT_CHANNEL_UPPERBODY = 0`
|
||
- `ACT_CHANNEL_LOWERBODY = 1`
|
||
- `ACT_CHANNEL_WOUND = 2`
|
||
|
||
Key methods:
|
||
```csharp
|
||
// Immediate play
|
||
PlayNonSkillActionWithName(int iAction, string szActName, bool bRestart, int nTransTime, bool bNoFx, CECAttackEvent, uint dwFlagMode)
|
||
// Queue after current
|
||
QueueNonSkillActionWithName(int iAction, string szActName, int nTransTime, bool bForceStop, bool bNoFx, bool bResetSpeed, bool bResetActFlag, CECAttackEvent, uint dwNewFlagMode)
|
||
// Skill cast (charging / 吟唱)
|
||
PlaySkillCastActionWithName(int idSkill, string szActName, bool bNoFX)
|
||
// Skill attack (施放)
|
||
PlaySkillAttackActionWithName(int idSkill, string szActName, bool bNoFX, CECAttackEvent, uint dwFlagMode)
|
||
QueueSkillAttackActionWithName(int idSkill, string szActName, int nTransTime, bool bNoFX, ...)
|
||
// Stop
|
||
StopSkillCastAction()
|
||
StopSkillAttackAction()
|
||
StopChannelAction()
|
||
```
|
||
|
||
Flag mode constants (in `CECPlayer`):
|
||
- `COMACT_FLAG_MODE_ONCE_IGNOREGFX = 2`
|
||
- `COMACT_FLAG_MODE_ONCE_MULTIIGNOREGFX = 3`
|
||
|
||
### PLAYER_ACTION_TYPE Enum (key values)
|
||
| Constant | Description |
|
||
|---|---|
|
||
| `ACT_STAND` | Idle stand |
|
||
| `ACT_FIGHTSTAND` | Combat idle (maps to ACT_STAND when no fight anim) |
|
||
| `ACT_RUN` / `ACT_WALK` | Movement |
|
||
| `ACT_JUMP_START` / `ACT_JUMP_LOOP` / `ACT_JUMP_LAND` | Jump phases |
|
||
| `ACT_ATTACK_1..4` | Normal attack sequence |
|
||
| `ACT_ATTACK_TOSS` | Throw / ranged attack |
|
||
| `ACT_TAKEOFF` / `ACT_LANDON` | Flight transitions |
|
||
| `ACT_WOUNDED` | Hit reaction |
|
||
| `ACT_GROUNDDIE` | Death on ground |
|
||
| `ACT_REVIVE` | Revive animation |
|
||
| `ACT_PICKUP` / `ACT_PICKUP_MATTER` | Pick-up gestures |
|
||
| `ACT_EXP_WAVE..ACT_EXP_DANCE` | Expression animations |
|
||
| `ACT_EXP_FASHIONWEAPON` | Fashion weapon expression |
|
||
| `ACT_USING_TARGET_ITEM` | Use item animation |
|
||
|
||
### Animation Name Convention
|
||
For skill actions:
|
||
```
|
||
{action_prefix}_{weapon_suffix}_施放起_ ← attack launch (ground)
|
||
{action_prefix}_{weapon_suffix}_施放落_ ← attack land (ground)
|
||
{action_prefix}_{weapon_suffix}_吟唱_ ← cast/charging
|
||
|
||
// Air variants:
|
||
{action_prefix}_{weapon_suffix}_空中翅膀_施放起_ ← wing-type air attack
|
||
{action_prefix}_{weapon_suffix}_空中飞剑_施放起_ ← flysword-type air attack
|
||
{action_prefix}_{weapon_suffix}_空中翅膀_吟唱_
|
||
{action_prefix}_{weapon_suffix}_空中飞剑_吟唱_
|
||
```
|
||
|
||
Multi-section suffix appended: `szAct += "_" + suffix` (from `GetSkillSectionActionName`).
|
||
|
||
---
|
||
|
||
## 3. Skill Trigger Logic (No Target Required)
|
||
|
||
### Action Config Loading
|
||
`BuildActionList()` in `CECPlayer`:
|
||
- Reads `PLAYER_ACTION_INFO_CONFIG` from `elementdataman`
|
||
- Builds `_default_actions[]` array for standard action types
|
||
- Builds `_default_skill_actions` dictionary `(uint skillId → PLAYER_ACTION_INFO_CONFIG)`
|
||
- `PlayerSkillAction.NUM_WEAPON_TYPE` weapon suffix variants per skill
|
||
|
||
### Trigger Chain (local, no server)
|
||
```
|
||
[Local command: trigger skill idSkill]
|
||
1. PlaySkillCastAction(idSkill) ← optional: shows charging/casting animation
|
||
└─ BuildActionName(data, weapon_type, "_吟唱_")
|
||
└─ PlaySkillCastActionWithName(idSkill, szAct, bHideFX)
|
||
└─ ShowWeaponByConfig(data)
|
||
|
||
2. PlaySkillAttackAction(idSkill, nAttackSpeed, ref piAttackTime)
|
||
├─ BuildActionName(data, weapon_type, "_施放起_")
|
||
├─ GetSkillSectionActionName (multi-section support)
|
||
├─ GetComActTimeSpanByName → nTime1, nTime2
|
||
├─ SetPlaySpeed(vScale) if speed adjustment needed
|
||
├─ PlaySkillAttackActionWithName(idSkill, szAct, bHideFX, attackEvent)
|
||
└─ QueueSkillAttackActionWithName(idSkill, szAct2, 0, bHideFX)
|
||
|
||
3. PlayAttackEffect(idTarget=0, idSkill, skillLevel, nDamage=0, dwModifier, nAttackSpeed, ref attackTime)
|
||
├─ Creates CECAttackEvent via CECAttacksMan.AddSkillAttack(...)
|
||
├─ Calls PlaySkillAttackAction(...)
|
||
└─ CECAttacksMan ticks the event each frame (GFX flight + hit)
|
||
```
|
||
|
||
### Key Method: `PlayAttackEffect`
|
||
```csharp
|
||
// CECPlayer.cs line ~1658
|
||
public void PlayAttackEffect(int idTarget, int idSkill, int skillLevel, int nDamage,
|
||
uint dwModifier, int nAttackSpeed, ref int attackTime)
|
||
```
|
||
- `idTarget = 0` → target-less skill (still creates attack event for GFX)
|
||
- `idSkill = 0` → normal melee attack; uses `AddMeleeAttack`
|
||
- When `idSkill > 0`: uses `AddSkillAttack`, triggers `PlaySkillAttackAction`
|
||
|
||
### CECSkill Class (used by CECHostPlayer.Skill.cs)
|
||
- `GetSkillID()`, `GetSkillLevel()`, `GetCastRange()`, `GetType()`
|
||
- Types: `TYPE_PASSIVE`, `TYPE_PRODUCE`, `TYPE_LIVE`, `TYPE_GOBLIN`, active types
|
||
- Retrieved via: `GetNormalSkill(id)`, `GetPositiveSkillByID(id)`, `GetPassiveSkillByID(id)`
|
||
|
||
---
|
||
|
||
## 4. Weapon Logic
|
||
|
||
### Attach / Detach
|
||
```csharp
|
||
AttachWeapon() // checks left/right hook availability via CECModel.GetHook()
|
||
DetachWeapon() // sets m_bWeaponAttached = false
|
||
```
|
||
|
||
### Hook Position Strings
|
||
| Method | WEAPON_HANGER_HAND | WEAPON_HANGER_SHOULDER |
|
||
|---|---|---|
|
||
| `GetLeftWeaponHookPos` | `_hh_left_hand_weapon` | `_hh_left_shoulder_weapon` |
|
||
| `GetRightWeaponHookPos` | `_hh_right_hand_weapon` | `_hh_right_shoulder_weapon` |
|
||
|
||
### Weapon Type Resolution
|
||
```csharp
|
||
int weapon_type = GetShowingWeaponType(); // considers fashion mode
|
||
int GetWeaponType(int iWeaponType) // maps raw type to canonical type (0-14)
|
||
int GetWeaponID() // equip pack weapon item ID (0 if shape-changed)
|
||
```
|
||
|
||
### Fashion Weapon
|
||
- `InFashionMode()` → `m_bFashionMode`
|
||
- `CanShowFashionWeapon(weapon_type, fashion_weapon_type)` → checks `FASHION_WEAPON_CONFIG.action_mask`
|
||
- `ShowWeaponByConfig(PLAYER_ACTION_INFO_CONFIG)` → shows/hides weapon per skill config
|
||
|
||
### Weapon SFX Maps (`m_aWeaponSFX`, `m_aWeaponHitSFX`)
|
||
| Weapon Type | Attack SFX | Hit SFX |
|
||
|---|---|---|
|
||
| 0,1 | `item/weaponattack/1hshort[a/b/c]` | `item/weaponattack/hitsword[big]` |
|
||
| 2 | `item/weaponattack/2hlong[a/b/c/d]` | `item/weaponattack/hitmace[big]` |
|
||
| 3 | `item/weaponattack/1hshort[a/b/c]` | `item/weaponattack/hithammer[big]` |
|
||
| 4 | `item/weaponattack/2hlong[a/b/c/d]` | `item/weaponattack/hitaxe[big]` |
|
||
| 5 | `item/weaponattack/1hshort[a/b]` | `item/weaponattack/hithammer` |
|
||
| 6,7,9 | `item/weaponattack/bow[/b/drawbow]` | `item/weaponattack/hitthrow` |
|
||
| 8,10 | `item/weaponattack/fist[a/b/c/d]` | `item/weaponattack/hithand` |
|
||
|
||
---
|
||
|
||
## 5. SFX System
|
||
|
||
### SFXManager
|
||
Located: `Assets/PerfectWorld/Scripts/Sound/SFXManager.cs`
|
||
|
||
- Singleton (`MonoSingleton<SFXManager>`)
|
||
- Loads `Resources/sound.txt` (tab-separated: `id path`) into `_soundTable`
|
||
- Pool of `_sfxPoolSize` (default 8) `AudioSource` components
|
||
- Routes all skill SFX through `_sfxMixerGroup`
|
||
|
||
Key method:
|
||
```csharp
|
||
SFXManager.Instance.PlaySkillSfxAtPointAsync(string soundPath, Vector3 position, float delay)
|
||
```
|
||
|
||
Usage in `PlayAttackAction`:
|
||
```csharp
|
||
string soundPath = m_aWeaponSFX[weapon_type][rand % count];
|
||
string hitSoundPath = m_aWeaponHitSFX[weapon_type][rand % count];
|
||
SFXManager.Instance.PlaySkillSfxAtPointAsync(soundPath, Vector3.zero, iTransTime / 1000f);
|
||
SFXManager.Instance.PlaySkillSfxAtPointAsync(hitSoundPath, Vector3.zero, iTransTime / 1000f + 0.1f);
|
||
```
|
||
|
||
Movement SFX: `_moveSoundSource` (2D looping AudioSource, assigned in Inspector).
|
||
|
||
---
|
||
|
||
## 6. GFX / VFX System
|
||
|
||
### State-Effect GFX (persistent buffs/debuffs)
|
||
Base path: `"gfx/策划联入/状态效果/"`
|
||
|
||
```csharp
|
||
// Play GFX on player model
|
||
PlayGfx(string strGFXFile, string szHook, float fScale, uint iShapeTypeMask, bool persist)
|
||
// Remove GFX
|
||
RemoveGfx(string szPath, string szHook, uint iShapeTypeMask)
|
||
// Play GFX on weapon model
|
||
PlayStateGfxOnModel(CECModel pWeapon, string path, string hook, float fScale)
|
||
RemoveStateGfxFromModel(CECModel pWeapon, string path, string hook)
|
||
```
|
||
|
||
Active state GFX tracked in: `_stateGfxObjects` (Dictionary keyed by `path+hook`).
|
||
|
||
Weapon hook resolution:
|
||
```csharp
|
||
IsWeaponHookPos(string szHH, out bool bLeft, out CECModel pWeapon)
|
||
GetWeaponGFXHookPos(CECModel pModel, bool bLeft)
|
||
```
|
||
|
||
### Skill GFX (projectiles / area effects)
|
||
Managed by `CECAttacksMan` + `A3DSkillGfxComposerMan`:
|
||
|
||
```
|
||
CECAttacksMan.LoadAllSkillGfxAsync()
|
||
└─ For each skill: ElementSkill.GetAllGFX(skillId) → (flyGFX, hitGrdGFX, hitGFX)
|
||
└─ A3DSkillGfxComposerMan.LoadOneComposerAsync(skillId, skillStub, flyPath, hitGrdPath, hitPath)
|
||
|
||
// On-demand (when skill first used):
|
||
CECAttacksMan.LoadSkillGfxOnDemand(uint skillId)
|
||
```
|
||
|
||
GFX event lifecycle (per `CECSkillGfxEvent`):
|
||
1. Spawn fly GFX at caster position
|
||
2. Move toward target (or self for targetless)
|
||
3. On arrival: spawn hit GFX, optionally spawn ground-hit GFX
|
||
4. Mark `m_bFinished = true` → removed from `m_targets` linked list
|
||
|
||
Ticked every frame: `SkillGfxMan.InstanceSub.Tick(dwDeltaTime)` in `CECAttacksMan.Update()`.
|
||
|
||
---
|
||
|
||
## 7. Attack Event (CECAttackEvent)
|
||
|
||
Created by `CECAttacksMan`:
|
||
```csharp
|
||
// Melee (idSkill == 0)
|
||
CECAttacksMan.Instance.AddMeleeAttack(idHost, idTarget, idWeapon, nDamage, dwModifier)
|
||
|
||
// Skill
|
||
CECAttacksMan.Instance.AddSkillAttack(idHost, idSkillTarget, idTarget, idWeapon,
|
||
idSkill, skillLevel, dwModifier, nDamage)
|
||
```
|
||
|
||
Key fields on `CECAttackEvent`:
|
||
- `m_idHost` — attacker entity ID
|
||
- `m_bFinished` — set true when GFX resolved
|
||
- `m_bSignaled` — damage applied flag (see `SetApplyDamage`)
|
||
- `SetSkillSection(nSection)` — for multi-section skills
|
||
|
||
For the animation test scene, pass `idTarget = 0` and `nDamage = 0` — the event will drive GFX travel with no actual damage.
|
||
|
||
---
|
||
|
||
## 8. Change Shape / TransformShape
|
||
|
||
### Entry Point
|
||
```csharp
|
||
await player.TransformShape(byte iShape, bool bLoadAtOnce = false)
|
||
```
|
||
|
||
### Shape ID Encoding (8-bit)
|
||
```
|
||
| Bit 7–6 | Bit 5–0 |
|
||
| TYPE | Model ID |
|
||
```
|
||
| TYPE value | PLAYERMODEL_TYPE | Meaning |
|
||
|---|---|---|
|
||
| `0x00` | PLAYERMODEL_TYPE_NONE | Invalid / legacy (auto-corrected to 0x40) |
|
||
| `0x40` | PLAYERMODEL_PROFESSION | Class-based transform (mapped via `_GetProfessionTransformModelID`) |
|
||
| `0x80` | PLAYERMODEL_DUMMY | Skill-transform / dummy model |
|
||
|
||
### Flow
|
||
```
|
||
TransformShape(iShape)
|
||
├─ SetShape(iShape) ← decode type+ID, fix legacy format
|
||
├─ IsShapeChanged()?
|
||
│ ├─ YES: QueueLoadDummyModel(m_iShape, bLoadAtOnce)
|
||
│ │ └─ NPCManager.Instance.GetDummyModel(iShapeID)
|
||
│ │ └─ ApplyShapeModelChange(pDummyModel)
|
||
│ └─ NO: ApplyShapeModelChange(GetMajorModel()) ← revert to base model
|
||
└─ ApplyShapeModelChange(pModel)
|
||
├─ OnModelChange(pModel) → RefreshCECModel(pModel)
|
||
│ ├─ CECModel.SetSkeletonBuilder(...)
|
||
│ ├─ CECModel.SetNamedAnimancerComponent(...)
|
||
│ ├─ CECModel.SetTransform(...)
|
||
│ ├─ CECModel.SetCombinedAction(...)
|
||
│ └─ CECModel.InitializeSkeletonBuilder()
|
||
├─ Sync position/rotation from old model
|
||
├─ RecreateActionController()
|
||
└─ PlayAction(ACT_STAND)
|
||
```
|
||
|
||
### Profession → Transform Model Mapping
|
||
| Profession | Male | Female |
|
||
|---|---|---|
|
||
| PROF_HAG (妖族) | `RES_MOD_ORC_FOX` | `RES_MOD_ORC_FOX2` |
|
||
| PROF_ORC (妖兽) | `RES_MOD_ORC_TIGER` | `RES_MOD_ORC_PANDER` |
|
||
| PROF_MONK / PROF_GHOST | `RES_MOD_SHADOW_FISH_M` | `RES_MOD_SHADOW_FISH_F` |
|
||
| PROF_YEYING (夜影) | `RES_MOD_YEYING_RESHAPE_M` | `RES_MOD_YEYING_RESHAPE_F` |
|
||
| PROF_YUEXIAN (月仙) | `RES_MOD_YUEXIAN_RESHAPE_M` | `RES_MOD_YUEXIAN_RESHAPE_F` |
|
||
|
||
Revert to original: `TransformShape(0)` → `IsShapeChanged()` returns false → `ApplyShapeModelChange(GetMajorModel())`.
|
||
|
||
---
|
||
|
||
## 9. Required Scene GameObjects
|
||
|
||
| GameObject | Component(s) | Notes |
|
||
|---|---|---|
|
||
| `NPCManager` | `NPCManager` | Async model loader; must be in scene |
|
||
| `CECAttacksMan` | `CECAttacksMan` | Manages attack events and skill GFX; assign `SkillStateActionConfig` SO |
|
||
| `SFXManager` | `SFXManager` | Assign `_moveSoundSource`, `_sfxMixerGroup` in Inspector |
|
||
| `EC_ManMessageMono` | `EC_ManMessageMono` | Provides `EC_ManPlayer`, `CECNPCMan` |
|
||
| `ElementDataManProvider` | `elementdataman` provider | Required for skill/action config lookups |
|
||
| Player Object | `CECHostPlayer` (or subclass) | `parentModel` Transform, `txtName` TMP text |
|
||
| SkillGfxMan | `A3DSkillGfxMan` | Low-level GFX manager (instantiated via `SkillGfxMan.InstanceSub`) |
|
||
|
||
---
|
||
|
||
## 10. Local Command API (No Server)
|
||
|
||
Replace server message handlers with these direct calls:
|
||
|
||
```csharp
|
||
// --- Model ---
|
||
await player.SetPlayerModel(profession, gender);
|
||
|
||
// --- Standard animations ---
|
||
player.PlayAction((int)PLAYER_ACTION_TYPE.ACT_STAND, true);
|
||
player.PlayAction((int)PLAYER_ACTION_TYPE.ACT_RUN, true);
|
||
player.PlayAction((int)PLAYER_ACTION_TYPE.ACT_ATTACK_1, false);
|
||
|
||
// --- Skill cast (charging phase) ---
|
||
player.PlaySkillCastAction(idSkill); // 吟唱 animation
|
||
|
||
// --- Skill attack (fire phase, no target) ---
|
||
int attackTime = 0;
|
||
player.PlaySkillAttackAction(idSkill, attackSpeed, ref attackTime);
|
||
|
||
// --- Full effect chain (animation + GFX + SFX, no target) ---
|
||
int attackTime = 0;
|
||
player.PlayAttackEffect(
|
||
idTarget: 0, // 0 = no target
|
||
idSkill: idSkill,
|
||
skillLevel: 1,
|
||
nDamage: 0,
|
||
dwModifier: 0,
|
||
nAttackSpeed: 50, // 50 = default 1x speed
|
||
ref attackTime);
|
||
|
||
// --- Change shape ---
|
||
await player.TransformShape(shapeID); // e.g. 0x40 | modelID
|
||
await player.TransformShape(0); // revert to base
|
||
|
||
// --- Fashion mode toggle ---
|
||
player.m_bFashionMode = true;
|
||
player.m_bShowWeapon = true;
|
||
```
|
||
|
||
---
|
||
|
||
## 11. Initialization Order for Scene
|
||
|
||
```
|
||
1. Awake / Start:
|
||
- NPCManager initializes
|
||
- CECAttacksMan.SetupAttacksMan() → A3DSkillGfxComposerMan created
|
||
- SFXManager.Initialize() → loads sound.txt, builds AudioSource pool
|
||
|
||
2. Player Init:
|
||
- player.Init(playerInfo) ← sets profession, gender, equips, shape
|
||
- await player.SetPlayerModel(profession, gender)
|
||
→ NPCManager.GetModelPlayer() → model loaded
|
||
→ CECModel setup (SkeletonBuilder, Animancer, hooks)
|
||
→ AttachWeapon()
|
||
→ RecreateActionController()
|
||
→ BuildActionList() ← requires elementdataman populated
|
||
→ PlayAction(ACT_STAND)
|
||
|
||
3. Skill GFX preload (background, non-blocking):
|
||
- CECAttacksMan.LoadAllSkillGfxAsync()
|
||
|
||
4. Ready — call local commands to trigger animations
|
||
```
|
||
|
||
---
|
||
|
||
## 12. Notes & Caveats
|
||
|
||
- **`elementdataman`** must be loaded before `BuildActionList()`. Skills without a matching config entry in `_default_skill_actions` will silently skip the cast/attack animation.
|
||
- **Animancer** (`NamedAnimancerComponent`) must be present on the model prefab. The `SkeletonBuilder` may build asynchronously; if `InitializePlayerCECModelDelayed` coroutine fires, hooks are not ready until the next frame.
|
||
- **`CECAttacksMan` is required even for animation-only scenes** because `PlayAttackEffect` references it, and `SkillGfxMan.InstanceSub` is initialized via its `OnDestroy`.
|
||
- **Target-less GFX**: passing `idTarget = 0` to `AddSkillAttack` creates a valid event; the GFX composer will still instantiate fly/hit effects but travel to `Vector3.zero` unless a target position override is added.
|
||
- **Multi-section skills**: use `SetSkillSection(nSection)` on the `CECAttackEvent` before passing it to the animation method; `GetSkillSectionActionName` appends the section suffix automatically.
|
||
- **Shape model caching**: `m_pModels[iShapeType]` caches loaded dummy models per type slot. Re-calling `TransformShape` with the same shape reuses the cached model instantly.
|