8.1 KiB
C++ Skill GFX → SFX Play Flow (Perfect World client)
This document describes the C++ client-side processing flow that results in sound effects (SFX) playing when a skill’s GFX is spawned/started.
The key idea is:
- Skills don’t usually “play sound” directly.
- Instead, the skill system triggers GFX playback (fly/hit effects).
- The
.gfxresource can contain an embedded Sound element (ID_ELE_TYPE_SOUND). - When the GFX starts ticking, the sound element loads and plays SFX\ (or an audio event path in newer audio mode).
High-level flow (skill → gfx → sound)
flowchart TD
A[Skill attack fired] --> B[Skill GFX composer plays]
B --> C[A3DSkillGfxMan creates skill gfx events]
C --> D[Event loads fly/hit GFX resources]
D --> E[GFX starts: Start(true)]
E --> F[GFX elements tick each frame]
F --> G{Has Sound element?}
G -->|Yes| H[A3DGFXSound TickAnimation -> TickSound]
H --> I[GFXSOUNDIMP / GFXSOUNDIMP22 loads & plays]
G -->|No| J[No SFX from this GFX]
Step-by-step: where sound comes from
1) Skill triggers the Skill GFX system (not sound)
When a skill attack is ready to “fire”, the attacks system calls into the Skill GFX composer manager (single-section or multi-section).
You can see this flow summarized in your existing analysis doc:
Documentation/HOOK_SYSTEM_FLOW_C++_ANALYSIS.md
That doc traces:
CECAttackEvent::DoFire()→A3DSkillGfxComposerMan::Play()→A3DSkillGfxComposer::Play()→A3DSkillGfxMan::AddSkillGfxEvent()→AddOneSkillGfxEvent()→A3DSkillGfxEvent::Tick()
At this stage, the system is concerned with spawning/starting GFX, not explicitly choosing SFX.
2) Skill GFX events load GFX resources (fly/hit)
When a skill GFX event is created, it loads (or prepares) the referenced GFX resources (fly/hit). Once the event transitions into an active state, it calls Start(true) on the GFX instance.
Important: in this engine, “GFX” is a container of elements (particles, decals, models, sound, etc.). Sound is just another element type.
3) .gfx files can contain a Sound element
When a .gfx resource is loaded, its elements are created by element type. The sound element type is ID_ELE_TYPE_SOUND, which constructs an A3DGFXSound element.
Source: CElement/GfxCommon/A3DGFXElement.cpp
case ID_ELE_TYPE_ECMODEL:
pEle = new A3DGFXECModel(pGfx);
break;
case ID_ELE_TYPE_SOUND:
pEle = new A3DGFXSound(pGfx);
break;
case ID_ELE_TYPE_GFX_CONTAINER:
pEle = new A3DGFXContainer(pGfx);
break;
So the “mapping” is usually:
- GFX path → contains Sound element → references SFX file(s) or Audio event path
4) A3DGFXSound drives playback during animation ticking
A3DGFXSound is a A3DGFXElement that ticks sound as part of its animation tick.
Source: CElement/GfxCommon/A3DGFXSound.cpp
bool A3DGFXSound::TickAnimation(DWORD dwTickTime)
{
if (!A3DGFXElement::TickAnimation(dwTickTime))
{
if (IsFinished() && IsSoundValid())
StopSound();
return false;
}
TickSound(dwTickTime);
return true;
}
So once the parent GFX is started and ticking, sound playback “comes along for free” if a sound element exists in that GFX.
How the sound element selects what to play
There are two implementations selected at runtime:
GFXSOUNDIMP: classic “file-based SFX” that loadsSfx\\<filename>GFXSOUNDIMP22: “audio event” mode (newer sound system) that plays an event path
A3DGFXSound::SetupActiveImp() chooses which one is active:
Source: CElement/GfxCommon/A3DGFXSound.cpp
void A3DGFXSound::SetupActiveImp()
{
if (GFX_IsUseAudioEvent())
m_pActiveImp = &m_SoundImp22;
else
m_pActiveImp = &m_SoundImp;
}
A) Classic mode: SFX files under Sfx\\
A.1) The .gfx sound element stores 1+ file paths
The sound element loader reads either:
- a single
Path: <file>for old versions, or PathNum: N+ repeatedPath: <file>lines for newer versions.
Source: CElement/GfxCommon/A3DGFXSoundImp.cpp
bool GFXSOUNDIMP::Load(AFile* pFile, DWORD dwVersion)
{
...
// dwVersion < 88: one path
// dwVersion >= 88: multiple paths (PathNum + Path lines)
...
return m_ParamInfo.LoadSoundParamInfo(pFile);
}
A.2) When ticking, it loads & plays Sfx\\<file>
When conditions are met (distance checks, etc.), GFXSOUNDIMP loads either:
- looped sound:
AfxLoadLoopSound("Sfx\\" + file) - non-looped sound:
AfxLoadNonLoopSound("Sfx\\" + file, priority)
Source: CElement/GfxCommon/A3DGFXSoundImp.cpp
static const AString strPath = "Sfx\\";
if (pSound->IsInfinite())
{
AM3DSoundBuffer* pSfx = AfxLoadLoopSound(strPath + strFile);
...
m_pBuf = pSfx;
}
else
{
AM3DSoundBuffer* pSfx = AfxLoadNonLoopSound(strPath + strFile, pSound->GetParentGfx()->GetSfxPriority());
...
m_pBuf = pSfx;
}
This is the core “GFX → SFX” connection: the GFX’s sound element points at SFX asset names.
B) Audio-event mode (newer): plays an event path
If audio-event mode is enabled, GFXSOUNDIMP22 loads an “event path” and uses an audio engine event instance.
Source: CElement/GfxCommon/A3DGFXSoundImp.cpp
- It loads
Path: <event>plus optional custom distances (fordwVersion >= 96). - It creates an event instance via
CreateAudioEventInstance(m_strEventPath). - During tick, it calls
Start()and updates 3D attributes.
This means some .gfx files won’t reference Sfx\\*.wav-style filenames; they reference an engine-specific event path instead.
What this means for “SFX corresponding to each GFX”
There often isn’t a single global “table” mapping.
Instead, the mapping is usually data-driven inside the .gfx resource:
- If the
.gfxhas anID_ELE_TYPE_SOUNDelement, it will play the sound(s) configured in that element. - If it doesn’t, spawning that GFX will not automatically produce a sound.
How to trace a specific skill’s sound (practical checklist)
- Find the skill’s fly/hit GFX path (from the composer/skill gfx config that the skill uses).
- Locate the corresponding
.gfxresource file. - Inspect the
.gfxelement list for a Sound element and extract:- file path list (classic mode) → SFX asset name(s) under
Sfx\\ - or audio event path (audio-event mode)
- file path list (classic mode) → SFX asset name(s) under
- Confirm behavior in code paths above:
- element creation:
A3DGFXElement::CreateEmptyElement()createsA3DGFXSound - ticking:
A3DGFXSound::TickAnimation()callsTickSound() - playback:
GFXSOUNDIMP::ChangeSoundFile()loadsSfx\\...
- element creation:
Key files (C++ client)
-
Skill → GFX event flow
CElement/CElementClient/EC_ManAttacks.cpp(attack firing)CElement/CCommon/Gfx/A3DSkillGfxEvent2.cpp(skill gfx event state machine)CElement/CElementClient/EC_ManSkillGfx.cpp(hook positions + per-frame updates)CElement/CCommon/Gfx/A3DSkillGfxComposer2.*(composer data, Play/AddOneTarget)
-
GFX sound element
CElement/GfxCommon/A3DGFXElement.cpp(createsA3DGFXSoundforID_ELE_TYPE_SOUND)CElement/GfxCommon/A3DGFXSound.cpp(ticks sound as part of element animation)CElement/GfxCommon/A3DGFXSoundImp.cpp/.h(file-based SFX + audio-event implementation)
Notes / caveats
- Distance & 2D/3D rules: classic mode checks listener distance and may stop/release audio if too far (
CheckDist()). - Multiple sound paths:
PathNumallows random choice among multiple SFX files. - Loop vs non-loop: loop selection is tied to whether the sound element is considered “infinite” and to its parameters.