Files
test/Documentation/C++_SkillGfx_Sfx_Play_Flow.md
2026-04-10 16:35:07 +07:00

8.1 KiB
Raw Permalink Blame History

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 skills GFX is spawned/started.

The key idea is:

  • Skills dont usually “play sound” directly.
  • Instead, the skill system triggers GFX playback (fly/hit effects).
  • The .gfx resource 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 loads Sfx\\<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 + repeated Path: <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 GFXs 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 (for dwVersion >= 96).
  • It creates an event instance via CreateAudioEventInstance(m_strEventPath).
  • During tick, it calls Start() and updates 3D attributes.

This means some .gfx files wont reference Sfx\\*.wav-style filenames; they reference an engine-specific event path instead.


What this means for “SFX corresponding to each GFX”

There often isnt a single global “table” mapping.

Instead, the mapping is usually data-driven inside the .gfx resource:

  • If the .gfx has an ID_ELE_TYPE_SOUND element, it will play the sound(s) configured in that element.
  • If it doesnt, spawning that GFX will not automatically produce a sound.

How to trace a specific skills sound (practical checklist)

  1. Find the skills fly/hit GFX path (from the composer/skill gfx config that the skill uses).
  2. Locate the corresponding .gfx resource file.
  3. Inspect the .gfx element 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)
  4. Confirm behavior in code paths above:
    • element creation: A3DGFXElement::CreateEmptyElement() creates A3DGFXSound
    • ticking: A3DGFXSound::TickAnimation() calls TickSound()
    • playback: GFXSOUNDIMP::ChangeSoundFile() loads Sfx\\...

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 (creates A3DGFXSound for ID_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: PathNum allows 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.