claude-code-arcane 2.3.0 → 2.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/README.md +54 -6
  3. package/agents/game/ue-blueprint-specialist.md +125 -0
  4. package/agents/game/ue-gas-specialist.md +133 -0
  5. package/agents/game/ue-umg-specialist.md +131 -0
  6. package/dist/cli.js +5 -0
  7. package/docs/SKILLS-CATALOG.md +42 -3
  8. package/docs/USER-GUIDE.md +2 -2
  9. package/hooks/detect-unreal.sh +90 -0
  10. package/package.json +1 -1
  11. package/profiles/unreal-dev.yaml +97 -0
  12. package/rules/gamedev/unreal-code.md +125 -0
  13. package/skills/install-mcp/SKILL.md +135 -46
  14. package/skills/install-mcp/references/unreal-operations.md +55 -0
  15. package/skills/ue-actor-component-architecture/SKILL.md +509 -0
  16. package/skills/ue-actor-component-architecture/references/actor-lifecycle.md +301 -0
  17. package/skills/ue-actor-component-architecture/references/component-types.md +592 -0
  18. package/skills/ue-ai-navigation/SKILL.md +526 -0
  19. package/skills/ue-ai-navigation/references/behavior-tree-patterns.md +392 -0
  20. package/skills/ue-ai-navigation/references/behavior-tree-workflow.md +107 -0
  21. package/skills/ue-ai-navigation/references/custom-bttask.md +104 -0
  22. package/skills/ue-ai-navigation/references/eqs-reference.md +423 -0
  23. package/skills/ue-animation-system/SKILL.md +521 -0
  24. package/skills/ue-animation-system/references/anim-notify-reference.md +486 -0
  25. package/skills/ue-animation-system/references/locomotion-setup.md +550 -0
  26. package/skills/ue-async-threading/SKILL.md +433 -0
  27. package/skills/ue-async-threading/references/thread-safety-guide.md +213 -0
  28. package/skills/ue-async-threading/references/threading-patterns.md +345 -0
  29. package/skills/ue-audio-system/SKILL.md +527 -0
  30. package/skills/ue-audio-system/references/audio-setup-patterns.md +700 -0
  31. package/skills/ue-blueprints/SKILL.md +134 -0
  32. package/skills/ue-blueprints/references/communication.md +82 -0
  33. package/skills/ue-character-movement/SKILL.md +475 -0
  34. package/skills/ue-character-movement/references/cmc-extension-patterns.md +335 -0
  35. package/skills/ue-character-movement/references/movement-pipeline.md +189 -0
  36. package/skills/ue-cpp-foundations/SKILL.md +533 -0
  37. package/skills/ue-cpp-foundations/references/components-and-gc.md +107 -0
  38. package/skills/ue-cpp-foundations/references/container-patterns.md +544 -0
  39. package/skills/ue-cpp-foundations/references/delegate-patterns.md +497 -0
  40. package/skills/ue-cpp-foundations/references/gameplay-classes-cpp.md +136 -0
  41. package/skills/ue-cpp-foundations/references/property-specifiers.md +306 -0
  42. package/skills/ue-data-assets-tables/SKILL.md +527 -0
  43. package/skills/ue-data-assets-tables/references/asset-loading-patterns.md +306 -0
  44. package/skills/ue-data-assets-tables/references/data-driven-design.md +378 -0
  45. package/skills/ue-editor-tools/SKILL.md +523 -0
  46. package/skills/ue-editor-tools/references/detail-customization-patterns.md +395 -0
  47. package/skills/ue-editor-tools/references/editor-module-setup.md +480 -0
  48. package/skills/ue-game-features/SKILL.md +477 -0
  49. package/skills/ue-game-features/references/experience-system.md +222 -0
  50. package/skills/ue-game-features/references/game-feature-patterns.md +308 -0
  51. package/skills/ue-gameplay-abilities/SKILL.md +518 -0
  52. package/skills/ue-gameplay-abilities/references/ability-task-reference.md +391 -0
  53. package/skills/ue-gameplay-abilities/references/gameplay-effect-reference.md +397 -0
  54. package/skills/ue-gameplay-abilities/references/gas-setup-patterns.md +369 -0
  55. package/skills/ue-gameplay-framework/SKILL.md +466 -0
  56. package/skills/ue-gameplay-framework/references/framework-class-map.md +243 -0
  57. package/skills/ue-input-system/SKILL.md +394 -0
  58. package/skills/ue-input-system/references/cpp-setup.md +131 -0
  59. package/skills/ue-input-system/references/enhanced-input-workflow.md +117 -0
  60. package/skills/ue-input-system/references/input-action-reference.md +731 -0
  61. package/skills/ue-mass-entity/SKILL.md +476 -0
  62. package/skills/ue-mass-entity/references/mass-entity-patterns.md +319 -0
  63. package/skills/ue-mass-entity/references/mass-fragment-reference.md +119 -0
  64. package/skills/ue-materials-rendering/SKILL.md +526 -0
  65. package/skills/ue-materials-rendering/references/material-parameter-reference.md +216 -0
  66. package/skills/ue-materials-rendering/references/post-process-settings.md +383 -0
  67. package/skills/ue-module-build-system/SKILL.md +487 -0
  68. package/skills/ue-module-build-system/references/build-cs-reference.md +418 -0
  69. package/skills/ue-module-build-system/references/common-build-errors.md +381 -0
  70. package/skills/ue-naming-conventions/SKILL.md +142 -0
  71. package/skills/ue-naming-conventions/references/asset-name-modifiers.md +233 -0
  72. package/skills/ue-naming-conventions/references/content-structure.md +83 -0
  73. package/skills/ue-networking-replication/SKILL.md +452 -0
  74. package/skills/ue-networking-replication/references/replication-patterns.md +526 -0
  75. package/skills/ue-networking-replication/references/rpc-decision-guide.md +393 -0
  76. package/skills/ue-niagara-effects/SKILL.md +531 -0
  77. package/skills/ue-niagara-effects/references/niagara-data-interfaces.md +491 -0
  78. package/skills/ue-niagara-effects/references/niagara-parameter-types.md +153 -0
  79. package/skills/ue-niagara-effects/references/niagara-workflow.md +115 -0
  80. package/skills/ue-packaging/SKILL.md +116 -0
  81. package/skills/ue-physics-collision/SKILL.md +423 -0
  82. package/skills/ue-physics-collision/references/collision-channel-setup.md +218 -0
  83. package/skills/ue-physics-collision/references/trace-patterns.md +499 -0
  84. package/skills/ue-procedural-generation/SKILL.md +505 -0
  85. package/skills/ue-procedural-generation/references/pcg-node-reference.md +381 -0
  86. package/skills/ue-procedural-generation/references/procedural-mesh-patterns.md +940 -0
  87. package/skills/ue-project-context/SKILL.md +305 -0
  88. package/skills/ue-sequencer-cinematics/SKILL.md +506 -0
  89. package/skills/ue-sequencer-cinematics/references/sequencer-patterns.md +629 -0
  90. package/skills/ue-serialization-savegames/SKILL.md +524 -0
  91. package/skills/ue-serialization-savegames/references/save-system-architecture.md +705 -0
  92. package/skills/ue-state-trees/SKILL.md +489 -0
  93. package/skills/ue-state-trees/references/state-tree-mass-integration.md +184 -0
  94. package/skills/ue-state-trees/references/state-tree-patterns.md +321 -0
  95. package/skills/ue-testing-debugging/SKILL.md +520 -0
  96. package/skills/ue-testing-debugging/references/automation-test-patterns.md +387 -0
  97. package/skills/ue-testing-debugging/references/profiling-commands.md +357 -0
  98. package/skills/ue-ui-umg-slate/SKILL.md +431 -0
  99. package/skills/ue-ui-umg-slate/references/common-ui-setup.md +484 -0
  100. package/skills/ue-ui-umg-slate/references/widget-types.md +487 -0
  101. package/skills/ue-world-level-streaming/SKILL.md +518 -0
  102. package/skills/ue-world-level-streaming/references/streaming-patterns.md +414 -0
  103. package/templates/gamedev/unreal-project-context.md +107 -0
@@ -0,0 +1,700 @@
1
+ # Audio Setup Patterns
2
+
3
+ Common audio architecture patterns for Unreal Engine projects. All patterns use real UE API
4
+ as found in `AudioComponent.h`, `GameplayStatics.h`, `SoundSubmix.h`, `SoundConcurrency.h`,
5
+ and `SoundAttenuation.h`.
6
+
7
+ ---
8
+
9
+ ## Pattern 1: Music System with Crossfading
10
+
11
+ A persistent music manager that crossfades between tracks using two `UAudioComponent` instances
12
+ — one active, one incoming — with `FadeIn`/`FadeOut` on both.
13
+
14
+ ### MusicManager.h
15
+
16
+ ```cpp
17
+ #pragma once
18
+ #include "CoreMinimal.h"
19
+ #include "GameFramework/Actor.h"
20
+ #include "Components/AudioComponent.h"
21
+ #include "MusicManager.generated.h"
22
+
23
+ UCLASS(BlueprintType)
24
+ class MYGAME_API AMusicManager : public AActor
25
+ {
26
+ GENERATED_BODY()
27
+
28
+ public:
29
+ AMusicManager();
30
+
31
+ UFUNCTION(BlueprintCallable, Category = "Audio|Music")
32
+ void PlayMusic(USoundBase* NewTrack, float CrossfadeDuration = 1.0f);
33
+
34
+ UFUNCTION(BlueprintCallable, Category = "Audio|Music")
35
+ void StopMusic(float FadeOutDuration = 1.0f);
36
+
37
+ UFUNCTION(BlueprintCallable, Category = "Audio|Music")
38
+ void SetMusicVolume(float Volume);
39
+
40
+ protected:
41
+ virtual void BeginPlay() override;
42
+ virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
43
+
44
+ UFUNCTION()
45
+ void OnTrackFinished();
46
+
47
+ private:
48
+ // Slot A: currently playing track
49
+ UPROPERTY()
50
+ TObjectPtr<UAudioComponent> TrackA;
51
+
52
+ // Slot B: incoming track during crossfade
53
+ UPROPERTY()
54
+ TObjectPtr<UAudioComponent> TrackB;
55
+
56
+ // Which slot is active
57
+ bool bSlotAIsActive = true;
58
+
59
+ // Volume scalar applied to both tracks (separate from crossfade)
60
+ float MasterMusicVolume = 1.0f;
61
+
62
+ // Submix all music routes through — set in editor
63
+ UPROPERTY(EditDefaultsOnly, Category = "Audio|Music")
64
+ TObjectPtr<USoundSubmix> MusicSubmix;
65
+ };
66
+ ```
67
+
68
+ ### MusicManager.cpp
69
+
70
+ ```cpp
71
+ #include "MusicManager.h"
72
+ #include "Kismet/GameplayStatics.h"
73
+ #include "Sound/SoundSubmix.h"
74
+
75
+ AMusicManager::AMusicManager()
76
+ {
77
+ PrimaryActorTick.bCanEverTick = false;
78
+
79
+ // Two permanent audio components — no sound assigned at construction
80
+ TrackA = CreateDefaultSubobject<UAudioComponent>(TEXT("TrackA"));
81
+ TrackA->SetupAttachment(RootComponent);
82
+ TrackA->bAutoActivate = false;
83
+ TrackA->bIsUISound = true; // non-spatialized music
84
+
85
+ TrackB = CreateDefaultSubobject<UAudioComponent>(TEXT("TrackB"));
86
+ TrackB->SetupAttachment(RootComponent);
87
+ TrackB->bAutoActivate = false;
88
+ TrackB->bIsUISound = true;
89
+ }
90
+
91
+ void AMusicManager::BeginPlay()
92
+ {
93
+ Super::BeginPlay();
94
+ TrackA->OnAudioFinished.AddDynamic(this, &AMusicManager::OnTrackFinished);
95
+ TrackB->OnAudioFinished.AddDynamic(this, &AMusicManager::OnTrackFinished);
96
+ }
97
+
98
+ void AMusicManager::EndPlay(const EEndPlayReason::Type EndPlayReason)
99
+ {
100
+ TrackA->OnAudioFinished.RemoveAll(this);
101
+ TrackB->OnAudioFinished.RemoveAll(this);
102
+ Super::EndPlay(EndPlayReason);
103
+ }
104
+
105
+ void AMusicManager::PlayMusic(USoundBase* NewTrack, float CrossfadeDuration)
106
+ {
107
+ if (!NewTrack) { return; }
108
+ if (GetNetMode() == NM_DedicatedServer) { return; }
109
+
110
+ UAudioComponent* ActiveSlot = bSlotAIsActive ? TrackA : TrackB;
111
+ UAudioComponent* IncomingSlot = bSlotAIsActive ? TrackB : TrackA;
112
+
113
+ // Fade out the currently playing slot
114
+ if (ActiveSlot->GetPlayState() == EAudioComponentPlayState::Playing ||
115
+ ActiveSlot->GetPlayState() == EAudioComponentPlayState::FadingIn)
116
+ {
117
+ ActiveSlot->FadeOut(CrossfadeDuration, 0.0f, EAudioFaderCurve::Linear);
118
+ }
119
+
120
+ // Configure and fade in the incoming slot
121
+ IncomingSlot->SetSound(NewTrack);
122
+ IncomingSlot->FadeIn(CrossfadeDuration, MasterMusicVolume, 0.0f, EAudioFaderCurve::Linear);
123
+
124
+ bSlotAIsActive = !bSlotAIsActive;
125
+ }
126
+
127
+ void AMusicManager::StopMusic(float FadeOutDuration)
128
+ {
129
+ TrackA->FadeOut(FadeOutDuration, 0.0f, EAudioFaderCurve::Linear);
130
+ TrackB->FadeOut(FadeOutDuration, 0.0f, EAudioFaderCurve::Linear);
131
+ }
132
+
133
+ void AMusicManager::SetMusicVolume(float Volume)
134
+ {
135
+ MasterMusicVolume = FMath::Clamp(Volume, 0.0f, 1.0f);
136
+ // Alternatively, control via submix:
137
+ if (MusicSubmix)
138
+ {
139
+ MusicSubmix->SetSubmixOutputVolume(this, MasterMusicVolume);
140
+ }
141
+ }
142
+
143
+ void AMusicManager::OnTrackFinished()
144
+ {
145
+ // Looping tracks should have a looping SoundCue or SoundWave —
146
+ // this callback fires only for one-shot music tracks.
147
+ }
148
+ ```
149
+
150
+ ---
151
+
152
+ ## Pattern 2: Ambient Soundscape System
153
+
154
+ An actor that manages a set of looping ambient sounds (wind, birds, insects, water) with
155
+ distance-based crossfading driven by the player's position relative to defined zones.
156
+
157
+ ### AmbientSoundscape.h
158
+
159
+ ```cpp
160
+ #pragma once
161
+ #include "CoreMinimal.h"
162
+ #include "GameFramework/Actor.h"
163
+ #include "Components/AudioComponent.h"
164
+ #include "AmbientSoundscape.generated.h"
165
+
166
+ USTRUCT(BlueprintType)
167
+ struct FAmbientLayer
168
+ {
169
+ GENERATED_BODY()
170
+
171
+ UPROPERTY(EditAnywhere, BlueprintReadWrite)
172
+ TObjectPtr<USoundBase> Sound;
173
+
174
+ UPROPERTY(EditAnywhere, BlueprintReadWrite, meta=(ClampMin="0.0", ClampMax="1.0"))
175
+ float MaxVolume = 1.0f;
176
+
177
+ // Component assigned at runtime
178
+ UPROPERTY(Transient)
179
+ TObjectPtr<UAudioComponent> Component;
180
+ };
181
+
182
+ UCLASS(BlueprintType)
183
+ class MYGAME_API AAmbientSoundscape : public AActor
184
+ {
185
+ GENERATED_BODY()
186
+
187
+ public:
188
+ AAmbientSoundscape();
189
+
190
+ UFUNCTION(BlueprintCallable, Category = "Audio|Ambient")
191
+ void SetLayerVolume(int32 LayerIndex, float NormalizedVolume, float BlendTime = 0.5f);
192
+
193
+ UFUNCTION(BlueprintCallable, Category = "Audio|Ambient")
194
+ void FadeOutAll(float FadeDuration = 1.0f);
195
+
196
+ UFUNCTION(BlueprintCallable, Category = "Audio|Ambient")
197
+ void FadeInAll(float FadeDuration = 1.0f);
198
+
199
+ protected:
200
+ virtual void BeginPlay() override;
201
+
202
+ UPROPERTY(EditAnywhere, Category = "Audio|Ambient")
203
+ TArray<FAmbientLayer> Layers;
204
+
205
+ // Attenuation for all ambient layers (sphere, no occlusion for ambient)
206
+ UPROPERTY(EditDefaultsOnly, Category = "Audio|Ambient")
207
+ TObjectPtr<USoundAttenuation> AmbientAttenuation;
208
+ };
209
+ ```
210
+
211
+ ### AmbientSoundscape.cpp
212
+
213
+ ```cpp
214
+ #include "AmbientSoundscape.h"
215
+ #include "Kismet/GameplayStatics.h"
216
+
217
+ AAmbientSoundscape::AAmbientSoundscape()
218
+ {
219
+ PrimaryActorTick.bCanEverTick = false;
220
+ }
221
+
222
+ void AAmbientSoundscape::BeginPlay()
223
+ {
224
+ Super::BeginPlay();
225
+
226
+ if (GetNetMode() == NM_DedicatedServer) { return; }
227
+
228
+ for (FAmbientLayer& Layer : Layers)
229
+ {
230
+ if (!Layer.Sound) { continue; }
231
+
232
+ // SpawnSoundAttached creates a component attached to this actor
233
+ UAudioComponent* Comp = UGameplayStatics::SpawnSoundAttached(
234
+ Layer.Sound,
235
+ GetRootComponent(),
236
+ NAME_None,
237
+ FVector::ZeroVector,
238
+ FRotator::ZeroRotator,
239
+ EAttachLocation::SnapToTarget,
240
+ /*bStopWhenAttachedToDestroyed=*/true,
241
+ 0.0f, // start at volume 0
242
+ 1.0f, // pitch
243
+ 0.0f, // start time
244
+ AmbientAttenuation,
245
+ nullptr, // concurrency (ambient loops don't need concurrency)
246
+ /*bAutoDestroy=*/false
247
+ );
248
+
249
+ if (Comp)
250
+ {
251
+ Layer.Component = Comp;
252
+ // Fade in over 2 seconds to MaxVolume
253
+ Comp->FadeIn(2.0f, Layer.MaxVolume, 0.0f, EAudioFaderCurve::Linear);
254
+ }
255
+ }
256
+ }
257
+
258
+ void AAmbientSoundscape::SetLayerVolume(int32 LayerIndex, float NormalizedVolume, float BlendTime)
259
+ {
260
+ if (!Layers.IsValidIndex(LayerIndex)) { return; }
261
+ FAmbientLayer& Layer = Layers[LayerIndex];
262
+ if (!Layer.Component) { return; }
263
+
264
+ const float TargetVolume = Layer.MaxVolume * FMath::Clamp(NormalizedVolume, 0.0f, 1.0f);
265
+ if (BlendTime <= 0.0f)
266
+ {
267
+ Layer.Component->SetVolumeMultiplier(TargetVolume);
268
+ }
269
+ else
270
+ {
271
+ Layer.Component->AdjustVolume(BlendTime, TargetVolume);
272
+ }
273
+ }
274
+
275
+ void AAmbientSoundscape::FadeOutAll(float FadeDuration)
276
+ {
277
+ for (FAmbientLayer& Layer : Layers)
278
+ {
279
+ if (Layer.Component)
280
+ {
281
+ Layer.Component->FadeOut(FadeDuration, 0.0f, EAudioFaderCurve::Linear);
282
+ }
283
+ }
284
+ }
285
+
286
+ void AAmbientSoundscape::FadeInAll(float FadeDuration)
287
+ {
288
+ for (FAmbientLayer& Layer : Layers)
289
+ {
290
+ if (Layer.Component)
291
+ {
292
+ Layer.Component->FadeIn(FadeDuration, Layer.MaxVolume, 0.0f, EAudioFaderCurve::Linear);
293
+ }
294
+ }
295
+ }
296
+ ```
297
+
298
+ ---
299
+
300
+ ## Pattern 3: Gameplay Audio Component (Weapon SFX)
301
+
302
+ A `UActorComponent` managing a weapon's audio: fire, reload, empty-click, and mechanical
303
+ state sounds. Demonstrates proper server guarding and concurrency usage.
304
+
305
+ ### WeaponAudioComponent.h
306
+
307
+ ```cpp
308
+ #pragma once
309
+ #include "CoreMinimal.h"
310
+ #include "Components/ActorComponent.h"
311
+ #include "Sound/SoundConcurrency.h"
312
+ #include "WeaponAudioComponent.generated.h"
313
+
314
+ UCLASS(ClassGroup=Audio, meta=(BlueprintSpawnableComponent))
315
+ class MYGAME_API UWeaponAudioComponent : public UActorComponent
316
+ {
317
+ GENERATED_BODY()
318
+
319
+ public:
320
+ UWeaponAudioComponent();
321
+
322
+ UFUNCTION(BlueprintCallable, Category = "Audio|Weapon")
323
+ void PlayFireSound();
324
+
325
+ UFUNCTION(BlueprintCallable, Category = "Audio|Weapon")
326
+ void PlayReloadSound();
327
+
328
+ UFUNCTION(BlueprintCallable, Category = "Audio|Weapon")
329
+ void PlayEmptyClickSound();
330
+
331
+ UFUNCTION(BlueprintCallable, Category = "Audio|Weapon")
332
+ void StartMechanicalLoop();
333
+
334
+ UFUNCTION(BlueprintCallable, Category = "Audio|Weapon")
335
+ void StopMechanicalLoop(float FadeTime = 0.15f);
336
+
337
+ protected:
338
+ virtual void BeginPlay() override;
339
+ virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
340
+
341
+ // One-shot sounds — use SpawnSoundAtLocation each play
342
+ UPROPERTY(EditDefaultsOnly, Category = "Audio")
343
+ TObjectPtr<USoundBase> FireSound;
344
+
345
+ UPROPERTY(EditDefaultsOnly, Category = "Audio")
346
+ TObjectPtr<USoundBase> ReloadSound;
347
+
348
+ UPROPERTY(EditDefaultsOnly, Category = "Audio")
349
+ TObjectPtr<USoundBase> EmptyClickSound;
350
+
351
+ // Looping sound — permanent component
352
+ UPROPERTY(EditDefaultsOnly, Category = "Audio")
353
+ TObjectPtr<USoundBase> MechanicalLoopSound;
354
+
355
+ // Attenuation for world-space weapon sounds
356
+ UPROPERTY(EditDefaultsOnly, Category = "Audio")
357
+ TObjectPtr<USoundAttenuation> WeaponAttenuation;
358
+
359
+ // Concurrency: max 4 fire sounds simultaneously, stop farthest if over
360
+ UPROPERTY(EditDefaultsOnly, Category = "Audio")
361
+ TObjectPtr<USoundConcurrency> FireConcurrency;
362
+
363
+ private:
364
+ UPROPERTY()
365
+ TObjectPtr<UAudioComponent> MechanicalLoopComp;
366
+
367
+ bool bIsOnServer() const;
368
+ FVector GetOwnerLocation() const;
369
+ };
370
+ ```
371
+
372
+ ### WeaponAudioComponent.cpp
373
+
374
+ ```cpp
375
+ #include "WeaponAudioComponent.h"
376
+ #include "GameFramework/Actor.h"
377
+ #include "Kismet/GameplayStatics.h"
378
+ #include "Components/AudioComponent.h"
379
+
380
+ UWeaponAudioComponent::UWeaponAudioComponent()
381
+ {
382
+ PrimaryComponentTick.bCanEverTick = false;
383
+ }
384
+
385
+ void UWeaponAudioComponent::BeginPlay()
386
+ {
387
+ Super::BeginPlay();
388
+
389
+ if (bIsOnServer() || !MechanicalLoopSound) { return; }
390
+
391
+ // Create the loop component but don't play yet
392
+ MechanicalLoopComp = UGameplayStatics::SpawnSoundAttached(
393
+ MechanicalLoopSound,
394
+ GetOwner()->GetRootComponent(),
395
+ NAME_None,
396
+ FVector::ZeroVector,
397
+ FRotator::ZeroRotator,
398
+ EAttachLocation::SnapToTarget,
399
+ /*bStopWhenAttachedToDestroyed=*/true,
400
+ 1.0f, 1.0f, 0.0f,
401
+ WeaponAttenuation,
402
+ nullptr,
403
+ /*bAutoDestroy=*/false
404
+ );
405
+
406
+ if (MechanicalLoopComp)
407
+ {
408
+ MechanicalLoopComp->Stop(); // ensure not auto-playing
409
+ }
410
+ }
411
+
412
+ void UWeaponAudioComponent::EndPlay(const EEndPlayReason::Type EndPlayReason)
413
+ {
414
+ if (MechanicalLoopComp)
415
+ {
416
+ MechanicalLoopComp->Stop();
417
+ }
418
+ Super::EndPlay(EndPlayReason);
419
+ }
420
+
421
+ void UWeaponAudioComponent::PlayFireSound()
422
+ {
423
+ if (bIsOnServer() || !FireSound) { return; }
424
+
425
+ // Fire-and-forget: attenuation + concurrency prevent stacking
426
+ UGameplayStatics::PlaySoundAtLocation(
427
+ this,
428
+ FireSound,
429
+ GetOwnerLocation(),
430
+ FRotator::ZeroRotator,
431
+ 1.0f, 1.0f, 0.0f,
432
+ WeaponAttenuation,
433
+ FireConcurrency, // USoundConcurrency: MaxCount=4, StopFarthestThenOldest
434
+ GetOwner()
435
+ );
436
+ }
437
+
438
+ void UWeaponAudioComponent::PlayReloadSound()
439
+ {
440
+ if (bIsOnServer() || !ReloadSound) { return; }
441
+ UGameplayStatics::PlaySoundAtLocation(
442
+ this, ReloadSound, GetOwnerLocation(),
443
+ FRotator::ZeroRotator, 1.0f, 1.0f, 0.0f,
444
+ WeaponAttenuation
445
+ );
446
+ }
447
+
448
+ void UWeaponAudioComponent::PlayEmptyClickSound()
449
+ {
450
+ if (bIsOnServer() || !EmptyClickSound) { return; }
451
+ UGameplayStatics::PlaySoundAtLocation(
452
+ this, EmptyClickSound, GetOwnerLocation(),
453
+ FRotator::ZeroRotator, 1.0f, 1.0f, 0.0f,
454
+ WeaponAttenuation
455
+ );
456
+ }
457
+
458
+ void UWeaponAudioComponent::StartMechanicalLoop()
459
+ {
460
+ if (bIsOnServer() || !MechanicalLoopComp) { return; }
461
+ MechanicalLoopComp->FadeIn(0.1f, 1.0f, 0.0f, EAudioFaderCurve::Linear);
462
+ }
463
+
464
+ void UWeaponAudioComponent::StopMechanicalLoop(float FadeTime)
465
+ {
466
+ if (!MechanicalLoopComp) { return; }
467
+ MechanicalLoopComp->FadeOut(FadeTime, 0.0f, EAudioFaderCurve::Linear);
468
+ }
469
+
470
+ bool UWeaponAudioComponent::bIsOnServer() const
471
+ {
472
+ const AActor* Owner = GetOwner();
473
+ return Owner && Owner->GetNetMode() == NM_DedicatedServer;
474
+ }
475
+
476
+ FVector UWeaponAudioComponent::GetOwnerLocation() const
477
+ {
478
+ const AActor* Owner = GetOwner();
479
+ return Owner ? Owner->GetActorLocation() : FVector::ZeroVector;
480
+ }
481
+ ```
482
+
483
+ ---
484
+
485
+ ## Pattern 4: MetaSound Parameter-Driven Engine Audio
486
+
487
+ An engine sound that uses a `UMetaSoundSource` with a `RPM` float input and `Engaged` bool input,
488
+ driven from a vehicle's tick.
489
+
490
+ ```cpp
491
+ // In vehicle header:
492
+ UPROPERTY(EditDefaultsOnly, Category = "Audio")
493
+ TObjectPtr<UMetaSoundSource> EngineSoundAsset; // UMetaSoundSource derives USoundBase
494
+
495
+ UPROPERTY()
496
+ TObjectPtr<UAudioComponent> EngineAudioComp;
497
+
498
+ // In BeginPlay:
499
+ void AMyVehicle::BeginPlay()
500
+ {
501
+ Super::BeginPlay();
502
+ if (GetNetMode() == NM_DedicatedServer) { return; }
503
+
504
+ EngineAudioComp = UGameplayStatics::SpawnSoundAttached(
505
+ EngineSoundAsset,
506
+ GetMesh(),
507
+ TEXT("AudioSocket"),
508
+ FVector::ZeroVector,
509
+ FRotator::ZeroRotator,
510
+ EAttachLocation::SnapToTarget,
511
+ true, 1.0f, 1.0f, 0.0f,
512
+ EngineAttenuation,
513
+ nullptr,
514
+ /*bAutoDestroy=*/false
515
+ );
516
+ }
517
+
518
+ // In Tick — update MetaSound parameters:
519
+ void AMyVehicle::Tick(float DeltaTime)
520
+ {
521
+ Super::Tick(DeltaTime);
522
+ if (!EngineAudioComp) { return; }
523
+
524
+ const float CurrentRPM = GetCurrentRPM(); // your vehicle RPM value
525
+ const bool bIsEngaged = IsTransmissionEngaged();
526
+
527
+ // ISoundParameterControllerInterface methods on UAudioComponent:
528
+ EngineAudioComp->SetFloatParameter(FName("RPM"), CurrentRPM);
529
+ EngineAudioComp->SetBoolParameter(FName("Engaged"), bIsEngaged);
530
+ }
531
+
532
+ // On gear shift event:
533
+ void AMyVehicle::OnGearShift(int32 NewGear)
534
+ {
535
+ if (EngineAudioComp)
536
+ {
537
+ EngineAudioComp->SetIntParameter(FName("GearIndex"), NewGear);
538
+ EngineAudioComp->SetTriggerParameter(FName("OnGearShift"));
539
+ }
540
+ }
541
+ ```
542
+
543
+ ---
544
+
545
+ ## Pattern 5: Submix-Based Volume Control (Settings Menu)
546
+
547
+ Typical volume slider implementation using `USoundSubmix::SetSubmixOutputVolume`.
548
+
549
+ ```cpp
550
+ // In game settings subsystem or GameUserSettings subclass:
551
+
552
+ UPROPERTY(config)
553
+ float MasterVolume = 1.0f;
554
+
555
+ UPROPERTY(config)
556
+ float MusicVolume = 1.0f;
557
+
558
+ UPROPERTY(config)
559
+ float SFXVolume = 1.0f;
560
+
561
+ // References to submix assets loaded via soft references or direct UPROPERTY:
562
+ UPROPERTY(EditDefaultsOnly, Category = "Audio")
563
+ TObjectPtr<USoundSubmix> MasterSubmix;
564
+
565
+ UPROPERTY(EditDefaultsOnly, Category = "Audio")
566
+ TObjectPtr<USoundSubmix> MusicSubmix;
567
+
568
+ UPROPERTY(EditDefaultsOnly, Category = "Audio")
569
+ TObjectPtr<USoundSubmix> SFXSubmix;
570
+
571
+ void UMyGameUserSettings::ApplyAudioSettings()
572
+ {
573
+ if (UWorld* World = GetWorld())
574
+ {
575
+ if (MasterSubmix)
576
+ {
577
+ MasterSubmix->SetSubmixOutputVolume(World, MasterVolume);
578
+ }
579
+ if (MusicSubmix)
580
+ {
581
+ MusicSubmix->SetSubmixOutputVolume(World, MusicVolume);
582
+ }
583
+ if (SFXSubmix)
584
+ {
585
+ SFXSubmix->SetSubmixOutputVolume(World, SFXVolume);
586
+ }
587
+ }
588
+ }
589
+
590
+ void UMyGameUserSettings::SetMasterVolume(float NewVolume)
591
+ {
592
+ MasterVolume = FMath::Clamp(NewVolume, 0.0f, 1.0f);
593
+ ApplyAudioSettings();
594
+ SaveConfig();
595
+ }
596
+ ```
597
+
598
+ ---
599
+
600
+ ## Pattern 6: Beat-Reactive Visual (Spectrum Analysis)
601
+
602
+ Drive a material parameter or visual effect from submix spectral analysis.
603
+
604
+ ```cpp
605
+ // In an actor that reacts to music beats:
606
+
607
+ void ABeaconLight::BeginPlay()
608
+ {
609
+ Super::BeginPlay();
610
+ if (GetNetMode() == NM_DedicatedServer || !MusicSubmix) { return; }
611
+
612
+ // Start FFT on the music submix
613
+ MusicSubmix->StartSpectralAnalysis(
614
+ this,
615
+ EFFTSize::Medium,
616
+ EFFTPeakInterpolationMethod::Linear,
617
+ EFFTWindowType::Hann,
618
+ 0.0f,
619
+ EAudioSpectrumType::MagnitudeSpectrum
620
+ );
621
+
622
+ // Low (bass) and mid bands
623
+ TArray<FSoundSubmixSpectralAnalysisBandSettings> Bands;
624
+ FSoundSubmixSpectralAnalysisBandSettings Bass;
625
+ Bass.BandFrequency = 80.f;
626
+ Bass.AttackTimeMsec = 5.f;
627
+ Bass.ReleaseTimeMsec = 80.f;
628
+ Bands.Add(Bass);
629
+
630
+ FSoundSubmixSpectralAnalysisBandSettings Mid;
631
+ Mid.BandFrequency = 800.f;
632
+ Mid.AttackTimeMsec = 10.f;
633
+ Mid.ReleaseTimeMsec = 120.f;
634
+ Bands.Add(Mid);
635
+
636
+ FOnSubmixSpectralAnalysisBP SpectralDelegate;
637
+ SpectralDelegate.BindDynamic(this, &ABeaconLight::OnSpectralData);
638
+
639
+ MusicSubmix->AddSpectralAnalysisDelegate(
640
+ this, Bands, SpectralDelegate,
641
+ 30.f, // UpdateRate Hz
642
+ -40.f, // DecibelNoiseFloor
643
+ true, // bDoNormalize (output 0..1)
644
+ false // bDoAutoRange
645
+ );
646
+ }
647
+
648
+ UFUNCTION()
649
+ void ABeaconLight::OnSpectralData(const TArray<float>& Magnitudes)
650
+ {
651
+ // Magnitudes[0] = bass band (0..1 normalized), Magnitudes[1] = mid band
652
+ const float BassMag = Magnitudes.IsValidIndex(0) ? Magnitudes[0] : 0.0f;
653
+ const float MidMag = Magnitudes.IsValidIndex(1) ? Magnitudes[1] : 0.0f;
654
+
655
+ // Drive a dynamic material instance parameter
656
+ if (LightMaterial)
657
+ {
658
+ LightMaterial->SetScalarParameterValue(TEXT("BassIntensity"), BassMag);
659
+ LightMaterial->SetScalarParameterValue(TEXT("MidIntensity"), MidMag);
660
+ }
661
+
662
+ // Scale a point light radius
663
+ if (PointLight)
664
+ {
665
+ const float BaseRadius = 200.0f;
666
+ PointLight->SetAttenuationRadius(BaseRadius + BassMag * 800.0f);
667
+ }
668
+ }
669
+
670
+ void ABeaconLight::EndPlay(const EEndPlayReason::Type EndPlayReason)
671
+ {
672
+ if (MusicSubmix)
673
+ {
674
+ MusicSubmix->StopSpectralAnalysis(this);
675
+ }
676
+ Super::EndPlay(EndPlayReason);
677
+ }
678
+ ```
679
+
680
+ ---
681
+
682
+ ## Asset Setup Checklist
683
+
684
+ For every 3D sound in the project:
685
+
686
+ - [ ] `AttenuationSettings` assigned — sphere falloff, `FalloffDistance` tuned to gameplay scale
687
+ - [ ] `SoundClassObject` set — SFX, Music, Voice, or Ambient
688
+ - [ ] `SoundSubmixObject` set — routes audio to the correct mixer bus
689
+ - [ ] `ConcurrencySet` populated — at least one `USoundConcurrency` for high-frequency sounds
690
+ - [ ] `Priority` value set — 1.0 default; increase for critical gameplay sounds
691
+ - [ ] `bAttenuate` and `bSpatialize` both enabled in the attenuation asset
692
+ - [ ] `bEnableOcclusion` considered — enable for interior/exterior transitions
693
+ - [ ] Streaming behavior — set `LoadingBehavior` on long SoundWave assets to `LoadOnDemand`
694
+
695
+ For MetaSound sources:
696
+
697
+ - [ ] All gameplay-driven inputs declared with clear names matching C++ `FName` strings
698
+ - [ ] Default parameter values set in the MetaSound graph
699
+ - [ ] `USoundConcurrency` assigned on the `UMetaSoundSource` asset
700
+ - [ ] Attenuation assigned if used in world space