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.
- package/CHANGELOG.md +15 -0
- package/README.md +54 -6
- package/agents/game/ue-blueprint-specialist.md +125 -0
- package/agents/game/ue-gas-specialist.md +133 -0
- package/agents/game/ue-umg-specialist.md +131 -0
- package/dist/cli.js +5 -0
- package/docs/SKILLS-CATALOG.md +42 -3
- package/docs/USER-GUIDE.md +2 -2
- package/hooks/detect-unreal.sh +90 -0
- package/package.json +1 -1
- package/profiles/unreal-dev.yaml +97 -0
- package/rules/gamedev/unreal-code.md +125 -0
- package/skills/install-mcp/SKILL.md +135 -46
- package/skills/install-mcp/references/unreal-operations.md +55 -0
- package/skills/ue-actor-component-architecture/SKILL.md +509 -0
- package/skills/ue-actor-component-architecture/references/actor-lifecycle.md +301 -0
- package/skills/ue-actor-component-architecture/references/component-types.md +592 -0
- package/skills/ue-ai-navigation/SKILL.md +526 -0
- package/skills/ue-ai-navigation/references/behavior-tree-patterns.md +392 -0
- package/skills/ue-ai-navigation/references/behavior-tree-workflow.md +107 -0
- package/skills/ue-ai-navigation/references/custom-bttask.md +104 -0
- package/skills/ue-ai-navigation/references/eqs-reference.md +423 -0
- package/skills/ue-animation-system/SKILL.md +521 -0
- package/skills/ue-animation-system/references/anim-notify-reference.md +486 -0
- package/skills/ue-animation-system/references/locomotion-setup.md +550 -0
- package/skills/ue-async-threading/SKILL.md +433 -0
- package/skills/ue-async-threading/references/thread-safety-guide.md +213 -0
- package/skills/ue-async-threading/references/threading-patterns.md +345 -0
- package/skills/ue-audio-system/SKILL.md +527 -0
- package/skills/ue-audio-system/references/audio-setup-patterns.md +700 -0
- package/skills/ue-blueprints/SKILL.md +134 -0
- package/skills/ue-blueprints/references/communication.md +82 -0
- package/skills/ue-character-movement/SKILL.md +475 -0
- package/skills/ue-character-movement/references/cmc-extension-patterns.md +335 -0
- package/skills/ue-character-movement/references/movement-pipeline.md +189 -0
- package/skills/ue-cpp-foundations/SKILL.md +533 -0
- package/skills/ue-cpp-foundations/references/components-and-gc.md +107 -0
- package/skills/ue-cpp-foundations/references/container-patterns.md +544 -0
- package/skills/ue-cpp-foundations/references/delegate-patterns.md +497 -0
- package/skills/ue-cpp-foundations/references/gameplay-classes-cpp.md +136 -0
- package/skills/ue-cpp-foundations/references/property-specifiers.md +306 -0
- package/skills/ue-data-assets-tables/SKILL.md +527 -0
- package/skills/ue-data-assets-tables/references/asset-loading-patterns.md +306 -0
- package/skills/ue-data-assets-tables/references/data-driven-design.md +378 -0
- package/skills/ue-editor-tools/SKILL.md +523 -0
- package/skills/ue-editor-tools/references/detail-customization-patterns.md +395 -0
- package/skills/ue-editor-tools/references/editor-module-setup.md +480 -0
- package/skills/ue-game-features/SKILL.md +477 -0
- package/skills/ue-game-features/references/experience-system.md +222 -0
- package/skills/ue-game-features/references/game-feature-patterns.md +308 -0
- package/skills/ue-gameplay-abilities/SKILL.md +518 -0
- package/skills/ue-gameplay-abilities/references/ability-task-reference.md +391 -0
- package/skills/ue-gameplay-abilities/references/gameplay-effect-reference.md +397 -0
- package/skills/ue-gameplay-abilities/references/gas-setup-patterns.md +369 -0
- package/skills/ue-gameplay-framework/SKILL.md +466 -0
- package/skills/ue-gameplay-framework/references/framework-class-map.md +243 -0
- package/skills/ue-input-system/SKILL.md +394 -0
- package/skills/ue-input-system/references/cpp-setup.md +131 -0
- package/skills/ue-input-system/references/enhanced-input-workflow.md +117 -0
- package/skills/ue-input-system/references/input-action-reference.md +731 -0
- package/skills/ue-mass-entity/SKILL.md +476 -0
- package/skills/ue-mass-entity/references/mass-entity-patterns.md +319 -0
- package/skills/ue-mass-entity/references/mass-fragment-reference.md +119 -0
- package/skills/ue-materials-rendering/SKILL.md +526 -0
- package/skills/ue-materials-rendering/references/material-parameter-reference.md +216 -0
- package/skills/ue-materials-rendering/references/post-process-settings.md +383 -0
- package/skills/ue-module-build-system/SKILL.md +487 -0
- package/skills/ue-module-build-system/references/build-cs-reference.md +418 -0
- package/skills/ue-module-build-system/references/common-build-errors.md +381 -0
- package/skills/ue-naming-conventions/SKILL.md +142 -0
- package/skills/ue-naming-conventions/references/asset-name-modifiers.md +233 -0
- package/skills/ue-naming-conventions/references/content-structure.md +83 -0
- package/skills/ue-networking-replication/SKILL.md +452 -0
- package/skills/ue-networking-replication/references/replication-patterns.md +526 -0
- package/skills/ue-networking-replication/references/rpc-decision-guide.md +393 -0
- package/skills/ue-niagara-effects/SKILL.md +531 -0
- package/skills/ue-niagara-effects/references/niagara-data-interfaces.md +491 -0
- package/skills/ue-niagara-effects/references/niagara-parameter-types.md +153 -0
- package/skills/ue-niagara-effects/references/niagara-workflow.md +115 -0
- package/skills/ue-packaging/SKILL.md +116 -0
- package/skills/ue-physics-collision/SKILL.md +423 -0
- package/skills/ue-physics-collision/references/collision-channel-setup.md +218 -0
- package/skills/ue-physics-collision/references/trace-patterns.md +499 -0
- package/skills/ue-procedural-generation/SKILL.md +505 -0
- package/skills/ue-procedural-generation/references/pcg-node-reference.md +381 -0
- package/skills/ue-procedural-generation/references/procedural-mesh-patterns.md +940 -0
- package/skills/ue-project-context/SKILL.md +305 -0
- package/skills/ue-sequencer-cinematics/SKILL.md +506 -0
- package/skills/ue-sequencer-cinematics/references/sequencer-patterns.md +629 -0
- package/skills/ue-serialization-savegames/SKILL.md +524 -0
- package/skills/ue-serialization-savegames/references/save-system-architecture.md +705 -0
- package/skills/ue-state-trees/SKILL.md +489 -0
- package/skills/ue-state-trees/references/state-tree-mass-integration.md +184 -0
- package/skills/ue-state-trees/references/state-tree-patterns.md +321 -0
- package/skills/ue-testing-debugging/SKILL.md +520 -0
- package/skills/ue-testing-debugging/references/automation-test-patterns.md +387 -0
- package/skills/ue-testing-debugging/references/profiling-commands.md +357 -0
- package/skills/ue-ui-umg-slate/SKILL.md +431 -0
- package/skills/ue-ui-umg-slate/references/common-ui-setup.md +484 -0
- package/skills/ue-ui-umg-slate/references/widget-types.md +487 -0
- package/skills/ue-world-level-streaming/SKILL.md +518 -0
- package/skills/ue-world-level-streaming/references/streaming-patterns.md +414 -0
- package/templates/gamedev/unreal-project-context.md +107 -0
|
@@ -0,0 +1,497 @@
|
|
|
1
|
+
# Delegate Patterns Reference
|
|
2
|
+
|
|
3
|
+
Complete reference for Unreal Engine's delegate system: declaration macros, binding methods, and invocation patterns. Source: `Engine/Source/Runtime/Core/Public/Delegates/DelegateCombinations.h` and `Delegate.h`.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Delegate Type Overview
|
|
8
|
+
|
|
9
|
+
| Type | Macro Family | Bindings | Serializable | Blueprint | Use Case |
|
|
10
|
+
|------|-------------|----------|--------------|-----------|----------|
|
|
11
|
+
| Single static | `DECLARE_DELEGATE` | 1 | No | No | Internal single-owner callbacks |
|
|
12
|
+
| Multicast static | `DECLARE_MULTICAST_DELEGATE` | N | No | No | Internal multi-listener events |
|
|
13
|
+
| Thread-safe multicast | `DECLARE_TS_MULTICAST_DELEGATE` | N | No | No | Cross-thread event broadcasting |
|
|
14
|
+
| Dynamic single | `DECLARE_DYNAMIC_DELEGATE` | 1 | Yes | Yes | Blueprint-assignable single callback |
|
|
15
|
+
| Dynamic multicast | `DECLARE_DYNAMIC_MULTICAST_DELEGATE` | N | Yes | Yes | Blueprint-bindable events (most common for gameplay) |
|
|
16
|
+
| Return-value single | `DECLARE_DELEGATE_RetVal` | 1 | No | No | Query callbacks with a return value |
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Declaration Macros
|
|
21
|
+
|
|
22
|
+
All delegate type declarations go at **file scope** (outside classes), except `DECLARE_DYNAMIC_*` which should also be at file scope before the UCLASS that uses them.
|
|
23
|
+
|
|
24
|
+
### Single Delegate (No Parameters)
|
|
25
|
+
|
|
26
|
+
```cpp
|
|
27
|
+
DECLARE_DELEGATE(FOnGamePaused);
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### Single Delegate with Parameters
|
|
31
|
+
|
|
32
|
+
```cpp
|
|
33
|
+
// One parameter
|
|
34
|
+
DECLARE_DELEGATE_OneParam(FOnItemCollected, AActor* /* Item */);
|
|
35
|
+
|
|
36
|
+
// Two parameters
|
|
37
|
+
DECLARE_DELEGATE_TwoParams(FOnDamageDealt, AActor* /* Target */, float /* Amount */);
|
|
38
|
+
|
|
39
|
+
// Three parameters
|
|
40
|
+
DECLARE_DELEGATE_ThreeParams(FOnAbilityUsed, FName /* AbilityID */, int32 /* Level */, float /* Cooldown */);
|
|
41
|
+
|
|
42
|
+
// Up to nine parameters supported
|
|
43
|
+
DECLARE_DELEGATE_FourParams(FOnWeaponFired, FVector /* Origin */, FVector /* Direction */, float /* Damage */, AActor* /* Shooter */);
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Single Delegate with Return Value
|
|
47
|
+
|
|
48
|
+
```cpp
|
|
49
|
+
DECLARE_DELEGATE_RetVal(bool, FOnShouldSpawn);
|
|
50
|
+
DECLARE_DELEGATE_RetVal_OneParam(float, FOnCalculateDamage, float /* BaseDamage */);
|
|
51
|
+
DECLARE_DELEGATE_RetVal_TwoParams(AActor*, FOnSelectTarget, FVector /* Origin */, float /* Range */);
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Multicast Delegate
|
|
55
|
+
|
|
56
|
+
```cpp
|
|
57
|
+
DECLARE_MULTICAST_DELEGATE(FOnLevelLoaded);
|
|
58
|
+
DECLARE_MULTICAST_DELEGATE_OneParam(FOnActorSpawned, AActor* /* SpawnedActor */);
|
|
59
|
+
DECLARE_MULTICAST_DELEGATE_TwoParams(FOnHealthChanged, float /* Current */, float /* Max */);
|
|
60
|
+
DECLARE_MULTICAST_DELEGATE_ThreeParams(FOnGameEvent, FName /* EventID */, UObject* /* Instigator */, const FString& /* Payload */);
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Thread-Safe Multicast
|
|
64
|
+
|
|
65
|
+
```cpp
|
|
66
|
+
// Use when broadcasting from non-game threads
|
|
67
|
+
DECLARE_TS_MULTICAST_DELEGATE_OneParam(FOnAsyncDataReady, const TArray<uint8>& /* Data */);
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Dynamic Single Delegate
|
|
71
|
+
|
|
72
|
+
```cpp
|
|
73
|
+
// Saveable, can be assigned in Blueprint
|
|
74
|
+
// Param names are required (used as Blueprint pin labels)
|
|
75
|
+
DECLARE_DYNAMIC_DELEGATE(FOnSimpleEvent);
|
|
76
|
+
DECLARE_DYNAMIC_DELEGATE_OneParam(FOnActorDestroyed, AActor*, DestroyedActor);
|
|
77
|
+
DECLARE_DYNAMIC_DELEGATE_TwoParams(FOnTransformChanged, FVector, NewLocation, FRotator, NewRotation);
|
|
78
|
+
|
|
79
|
+
// With return value
|
|
80
|
+
DECLARE_DYNAMIC_DELEGATE_RetVal(bool, FOnValidationCheck);
|
|
81
|
+
DECLARE_DYNAMIC_DELEGATE_RetVal_OneParam(float, FOnGetModifiedDamage, float, BaseDamage);
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Dynamic Multicast Delegate
|
|
85
|
+
|
|
86
|
+
```cpp
|
|
87
|
+
// Most common for UPROPERTY(BlueprintAssignable) events
|
|
88
|
+
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnGameStarted);
|
|
89
|
+
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnScoreChanged, int32, NewScore);
|
|
90
|
+
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnPlayerDied, APlayerController*, PlayerController, AActor*, KillerActor);
|
|
91
|
+
DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(
|
|
92
|
+
FOnAbilityActivated,
|
|
93
|
+
FName, AbilityID,
|
|
94
|
+
int32, Level,
|
|
95
|
+
APlayerState*, PlayerState);
|
|
96
|
+
|
|
97
|
+
// Four params
|
|
98
|
+
DECLARE_DYNAMIC_MULTICAST_DELEGATE_FourParams(
|
|
99
|
+
FOnHitResult,
|
|
100
|
+
FVector, HitLocation,
|
|
101
|
+
FVector, HitNormal,
|
|
102
|
+
float, Damage,
|
|
103
|
+
AActor*, HitActor);
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## Declaring in a UCLASS
|
|
109
|
+
|
|
110
|
+
```cpp
|
|
111
|
+
// Forward-declare or include delegate declarations before the class
|
|
112
|
+
|
|
113
|
+
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnHealthChanged, float, CurrentHealth, float, MaxHealth);
|
|
114
|
+
DECLARE_MULTICAST_DELEGATE_OneParam(FOnDeathNative, AActor* /* DeadActor */);
|
|
115
|
+
|
|
116
|
+
UCLASS(Blueprintable)
|
|
117
|
+
class MYGAME_API AMyCharacter : public ACharacter
|
|
118
|
+
{
|
|
119
|
+
GENERATED_BODY()
|
|
120
|
+
|
|
121
|
+
public:
|
|
122
|
+
// BlueprintAssignable — Blueprint can bind to this in event graph
|
|
123
|
+
UPROPERTY(BlueprintAssignable, Category="Events")
|
|
124
|
+
FOnHealthChanged OnHealthChanged;
|
|
125
|
+
|
|
126
|
+
// BlueprintCallable on a delegate property — Blueprint can call Broadcast
|
|
127
|
+
UPROPERTY(BlueprintAssignable, BlueprintCallable, Category="Events")
|
|
128
|
+
FOnHealthChanged OnHealthChangedCallable;
|
|
129
|
+
|
|
130
|
+
// Native-only multicast (no UPROPERTY needed, but no GC protection either)
|
|
131
|
+
FOnDeathNative OnDeathNative;
|
|
132
|
+
};
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## Binding Methods
|
|
138
|
+
|
|
139
|
+
### Single Static Delegate Binding
|
|
140
|
+
|
|
141
|
+
```cpp
|
|
142
|
+
FOnItemCollected Delegate;
|
|
143
|
+
|
|
144
|
+
// Bind to a UObject member function (safe: auto-unbinds on UObject destruction)
|
|
145
|
+
Delegate.BindUObject(this, &AMyCharacter::HandleItemCollected);
|
|
146
|
+
|
|
147
|
+
// Bind to a raw C++ pointer (unsafe: does NOT unbind automatically)
|
|
148
|
+
Delegate.BindRaw(RawPtr, &FMyHelper::HandleItem);
|
|
149
|
+
|
|
150
|
+
// Bind to a static function (no instance)
|
|
151
|
+
Delegate.BindStatic(&UMyLibrary::StaticHandleItem);
|
|
152
|
+
|
|
153
|
+
// Bind to a TSharedPtr owner (safe: weak-ref checked on invocation)
|
|
154
|
+
Delegate.BindSP(SharedOwner, &FMyOwner::Handle);
|
|
155
|
+
|
|
156
|
+
// Bind to a lambda
|
|
157
|
+
Delegate.BindLambda([this](AActor* Item)
|
|
158
|
+
{
|
|
159
|
+
UE_LOG(LogMyGame, Log, TEXT("Collected: %s"), *Item->GetName());
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
// Bind with extra payload parameters (up to 4 bound "payload" vars)
|
|
163
|
+
float ScoreBonus = 1.5f;
|
|
164
|
+
Delegate.BindUObject(this, &AMyCharacter::HandleItemWithBonus, ScoreBonus);
|
|
165
|
+
|
|
166
|
+
// Check binding state
|
|
167
|
+
if (Delegate.IsBound())
|
|
168
|
+
{
|
|
169
|
+
Delegate.Execute(SomeActor);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// ExecuteIfBound — no-op if not bound (use for optional callbacks)
|
|
173
|
+
Delegate.ExecuteIfBound(SomeActor);
|
|
174
|
+
|
|
175
|
+
// Unbind
|
|
176
|
+
Delegate.Unbind();
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### Multicast Static Delegate Binding
|
|
180
|
+
|
|
181
|
+
```cpp
|
|
182
|
+
FOnHealthChanged HealthDelegate;
|
|
183
|
+
|
|
184
|
+
// Add binding — returns a handle for later removal
|
|
185
|
+
FDelegateHandle Handle = HealthDelegate.AddUObject(this, &AMyHUD::OnHealthChanged);
|
|
186
|
+
|
|
187
|
+
// Lambda binding
|
|
188
|
+
FDelegateHandle LambdaHandle = HealthDelegate.AddLambda([](float Current, float Max)
|
|
189
|
+
{
|
|
190
|
+
// Update UI
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
// Raw pointer binding
|
|
194
|
+
FDelegateHandle RawHandle = HealthDelegate.AddRaw(RawPtr, &FMyClass::Handle);
|
|
195
|
+
|
|
196
|
+
// SP binding
|
|
197
|
+
FDelegateHandle SpHandle = HealthDelegate.AddSP(SharedOwner, &FMyClass::Handle);
|
|
198
|
+
|
|
199
|
+
// Broadcast — calls all bound functions
|
|
200
|
+
HealthDelegate.Broadcast(75.f, 100.f);
|
|
201
|
+
|
|
202
|
+
// Remove by handle (precise)
|
|
203
|
+
HealthDelegate.Remove(Handle);
|
|
204
|
+
HealthDelegate.Remove(LambdaHandle);
|
|
205
|
+
|
|
206
|
+
// Remove by UObject — removes all bindings from that object
|
|
207
|
+
HealthDelegate.RemoveAll(this);
|
|
208
|
+
|
|
209
|
+
// Check if any bindings exist
|
|
210
|
+
bool bHasListeners = HealthDelegate.IsBound();
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
### Dynamic Single Delegate Binding
|
|
214
|
+
|
|
215
|
+
```cpp
|
|
216
|
+
FOnActorDestroyed Delegate;
|
|
217
|
+
|
|
218
|
+
// Bind to a UFUNCTION on a UObject (the function MUST be a UFUNCTION)
|
|
219
|
+
Delegate.BindDynamic(this, &AMyCharacter::HandleActorDestroyed);
|
|
220
|
+
|
|
221
|
+
// Check
|
|
222
|
+
bool bBound = Delegate.IsBound();
|
|
223
|
+
|
|
224
|
+
// Execute
|
|
225
|
+
Delegate.ExecuteIfBound(SomeActor);
|
|
226
|
+
|
|
227
|
+
// Unbind
|
|
228
|
+
Delegate.Unbind();
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
### Dynamic Multicast Delegate Binding
|
|
232
|
+
|
|
233
|
+
```cpp
|
|
234
|
+
// In UCLASS that declares OnHealthChanged:
|
|
235
|
+
OnHealthChanged.AddDynamic(this, &AMyHUD::HandleHealthChanged);
|
|
236
|
+
|
|
237
|
+
// Remove specific binding
|
|
238
|
+
OnHealthChanged.RemoveDynamic(this, &AMyHUD::HandleHealthChanged);
|
|
239
|
+
|
|
240
|
+
// Remove all bindings from an object
|
|
241
|
+
OnHealthChanged.RemoveAll(this);
|
|
242
|
+
|
|
243
|
+
// Broadcast
|
|
244
|
+
OnHealthChanged.Broadcast(NewHealth, MaxHealth);
|
|
245
|
+
|
|
246
|
+
// Check
|
|
247
|
+
bool bHasBindings = OnHealthChanged.IsBound();
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
---
|
|
251
|
+
|
|
252
|
+
## Complete Example: Full Delegate Workflow
|
|
253
|
+
|
|
254
|
+
### Component-based health with events
|
|
255
|
+
|
|
256
|
+
```cpp
|
|
257
|
+
// HealthComponent.h
|
|
258
|
+
|
|
259
|
+
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnHealthChangedSignature, float, CurrentHealth, float, MaxHealth);
|
|
260
|
+
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnDeathSignature, AActor*, KillerActor);
|
|
261
|
+
DECLARE_MULTICAST_DELEGATE_OneParam(FOnHealthChangedNative, float /* NewHealthFraction */);
|
|
262
|
+
|
|
263
|
+
UCLASS(ClassGroup=Combat, meta=(BlueprintSpawnableComponent))
|
|
264
|
+
class MYGAME_API UHealthComponent : public UActorComponent
|
|
265
|
+
{
|
|
266
|
+
GENERATED_BODY()
|
|
267
|
+
|
|
268
|
+
public:
|
|
269
|
+
// Blueprint-assignable events
|
|
270
|
+
UPROPERTY(BlueprintAssignable, Category="Health")
|
|
271
|
+
FOnHealthChangedSignature OnHealthChanged;
|
|
272
|
+
|
|
273
|
+
UPROPERTY(BlueprintAssignable, Category="Health")
|
|
274
|
+
FOnDeathSignature OnDeath;
|
|
275
|
+
|
|
276
|
+
// Native-only event for C++ subsystems (faster, no reflection overhead)
|
|
277
|
+
FOnHealthChangedNative OnHealthChangedNative;
|
|
278
|
+
|
|
279
|
+
UFUNCTION(BlueprintCallable, Category="Health")
|
|
280
|
+
void ApplyDamage(float DamageAmount, AActor* DamageInstigator);
|
|
281
|
+
|
|
282
|
+
UFUNCTION(BlueprintPure, Category="Health")
|
|
283
|
+
float GetHealthFraction() const { return CurrentHealth / MaxHealth; }
|
|
284
|
+
|
|
285
|
+
private:
|
|
286
|
+
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="Health",
|
|
287
|
+
meta=(AllowPrivateAccess="true", ClampMin="1.0"))
|
|
288
|
+
float MaxHealth = 100.f;
|
|
289
|
+
|
|
290
|
+
UPROPERTY(Transient)
|
|
291
|
+
float CurrentHealth;
|
|
292
|
+
|
|
293
|
+
virtual void BeginPlay() override;
|
|
294
|
+
};
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
```cpp
|
|
298
|
+
// HealthComponent.cpp
|
|
299
|
+
|
|
300
|
+
void UHealthComponent::BeginPlay()
|
|
301
|
+
{
|
|
302
|
+
Super::BeginPlay();
|
|
303
|
+
CurrentHealth = MaxHealth;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
void UHealthComponent::ApplyDamage(float DamageAmount, AActor* DamageInstigator)
|
|
307
|
+
{
|
|
308
|
+
if (CurrentHealth <= 0.f) return;
|
|
309
|
+
|
|
310
|
+
CurrentHealth = FMath::Clamp(CurrentHealth - DamageAmount, 0.f, MaxHealth);
|
|
311
|
+
|
|
312
|
+
// Fire both native and dynamic events
|
|
313
|
+
OnHealthChanged.Broadcast(CurrentHealth, MaxHealth);
|
|
314
|
+
OnHealthChangedNative.Broadcast(CurrentHealth / MaxHealth);
|
|
315
|
+
|
|
316
|
+
if (CurrentHealth <= 0.f)
|
|
317
|
+
{
|
|
318
|
+
OnDeath.Broadcast(DamageInstigator);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
```cpp
|
|
324
|
+
// MyCharacter.cpp — binding to the component's delegates
|
|
325
|
+
|
|
326
|
+
void AMyCharacter::BeginPlay()
|
|
327
|
+
{
|
|
328
|
+
Super::BeginPlay();
|
|
329
|
+
|
|
330
|
+
UHealthComponent* HC = FindComponentByClass<UHealthComponent>();
|
|
331
|
+
if (HC)
|
|
332
|
+
{
|
|
333
|
+
// Dynamic: Blueprint can also bind this in its event graph
|
|
334
|
+
HC->OnHealthChanged.AddDynamic(this, &AMyCharacter::HandleHealthChanged);
|
|
335
|
+
HC->OnDeath.AddDynamic(this, &AMyCharacter::HandleDeath);
|
|
336
|
+
|
|
337
|
+
// Native: C++ subsystem listens too
|
|
338
|
+
HC->OnHealthChangedNative.AddUObject(this, &AMyCharacter::HandleHealthChangedNative);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
void AMyCharacter::EndPlay(const EEndPlayReason::Type EndPlayReason)
|
|
343
|
+
{
|
|
344
|
+
UHealthComponent* HC = FindComponentByClass<UHealthComponent>();
|
|
345
|
+
if (HC)
|
|
346
|
+
{
|
|
347
|
+
HC->OnHealthChanged.RemoveDynamic(this, &AMyCharacter::HandleHealthChanged);
|
|
348
|
+
HC->OnDeath.RemoveDynamic(this, &AMyCharacter::HandleDeath);
|
|
349
|
+
HC->OnHealthChangedNative.RemoveAll(this);
|
|
350
|
+
}
|
|
351
|
+
Super::EndPlay(EndPlayReason);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
void AMyCharacter::HandleHealthChanged(float CurrentHealth, float MaxHealth)
|
|
355
|
+
{
|
|
356
|
+
UE_LOG(LogMyGame, Log, TEXT("[%s] Health: %.1f / %.1f"), *GetName(), CurrentHealth, MaxHealth);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
void AMyCharacter::HandleDeath(AActor* KillerActor)
|
|
360
|
+
{
|
|
361
|
+
UE_LOG(LogMyGame, Warning, TEXT("[%s] Died. Killer: %s"), *GetName(),
|
|
362
|
+
KillerActor ? *KillerActor->GetName() : TEXT("Unknown"));
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
void AMyCharacter::HandleHealthChangedNative(float HealthFraction)
|
|
366
|
+
{
|
|
367
|
+
// Update UI or animation state without Blueprint overhead
|
|
368
|
+
}
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
---
|
|
372
|
+
|
|
373
|
+
## Binding Method Quick Reference
|
|
374
|
+
|
|
375
|
+
| Method | Owner Safety | Auto-Unbind | Use With |
|
|
376
|
+
|--------|-------------|-------------|----------|
|
|
377
|
+
| `BindUObject` / `AddUObject` | Yes (weak ref) | On UObject destroy | UObject-derived classes |
|
|
378
|
+
| `BindSP` / `AddSP` | Yes (weak ptr) | On TSharedPtr destruction | Non-UObject with TSharedPtr |
|
|
379
|
+
| `BindRaw` / `AddRaw` | No | Never | Raw pointers (manual unbind required) |
|
|
380
|
+
| `BindStatic` / `AddStatic` | N/A | Never | Global/static functions |
|
|
381
|
+
| `BindLambda` / `AddLambda` | No (capture manually) | Never | Short-lived, inline callbacks |
|
|
382
|
+
| `BindDynamic` / `AddDynamic` | Yes (UFUNCTION) | On UObject destroy | Dynamic delegates only |
|
|
383
|
+
|
|
384
|
+
> **Safety rule:** Always `RemoveAll(this)` in `EndPlay` or destructor when using `AddLambda` or `AddRaw` to avoid calling into destroyed objects.
|
|
385
|
+
|
|
386
|
+
---
|
|
387
|
+
|
|
388
|
+
## Common Mistakes
|
|
389
|
+
|
|
390
|
+
**Calling Execute() on an unbound delegate:**
|
|
391
|
+
```cpp
|
|
392
|
+
// BAD — asserts in Debug if not bound
|
|
393
|
+
PickupDelegate.Execute(SomeActor);
|
|
394
|
+
|
|
395
|
+
// GOOD
|
|
396
|
+
PickupDelegate.ExecuteIfBound(SomeActor);
|
|
397
|
+
// Or:
|
|
398
|
+
if (PickupDelegate.IsBound()) { PickupDelegate.Execute(SomeActor); }
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
**Binding lambda to multicast without storing the handle:**
|
|
402
|
+
```cpp
|
|
403
|
+
// BAD — cannot be removed later
|
|
404
|
+
OnHealthChanged.AddLambda([this](float C, float M) { ... });
|
|
405
|
+
|
|
406
|
+
// GOOD — store the handle
|
|
407
|
+
FDelegateHandle Handle = OnHealthChanged.AddLambda([this](float C, float M) { ... });
|
|
408
|
+
// Later:
|
|
409
|
+
OnHealthChanged.Remove(Handle);
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
**Using AddDynamic with a non-UFUNCTION:**
|
|
413
|
+
```cpp
|
|
414
|
+
// BAD — compile error or undefined behavior
|
|
415
|
+
OnHealthChanged.AddDynamic(this, &AMyCharacter::NonUFunction);
|
|
416
|
+
|
|
417
|
+
// GOOD — the function must have UFUNCTION()
|
|
418
|
+
UFUNCTION()
|
|
419
|
+
void HandleHealthChanged(float Current, float Max);
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
**Forgetting to RemoveDynamic in EndPlay:**
|
|
423
|
+
```cpp
|
|
424
|
+
// BAD — dangling binding, crash when broadcast fires after object destruction
|
|
425
|
+
void AMyActor::BeginPlay()
|
|
426
|
+
{
|
|
427
|
+
Super::BeginPlay();
|
|
428
|
+
OtherActor->OnEvent.AddDynamic(this, &AMyActor::HandleEvent);
|
|
429
|
+
// Never removed!
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// GOOD
|
|
433
|
+
void AMyActor::EndPlay(const EEndPlayReason::Type EndPlayReason)
|
|
434
|
+
{
|
|
435
|
+
if (IsValid(OtherActor))
|
|
436
|
+
{
|
|
437
|
+
OtherActor->OnEvent.RemoveDynamic(this, &AMyActor::HandleEvent);
|
|
438
|
+
}
|
|
439
|
+
Super::EndPlay(EndPlayReason);
|
|
440
|
+
}
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
**Dynamic multicast delegate not in UPROPERTY:**
|
|
444
|
+
```cpp
|
|
445
|
+
// BAD — not serialized; Blueprint bindings lost between sessions
|
|
446
|
+
FOnScoreChanged OnScoreChanged;
|
|
447
|
+
|
|
448
|
+
// GOOD
|
|
449
|
+
UPROPERTY(BlueprintAssignable, Category="Events")
|
|
450
|
+
FOnScoreChanged OnScoreChanged;
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
---
|
|
454
|
+
|
|
455
|
+
## Return-Value Delegate Pattern
|
|
456
|
+
|
|
457
|
+
Single static delegates support return values. Multicast and dynamic delegates do not (which binding's return value would you use?).
|
|
458
|
+
|
|
459
|
+
```cpp
|
|
460
|
+
DECLARE_DELEGATE_RetVal_OneParam(bool, FOnValidatePickup, AActor* /* Item */);
|
|
461
|
+
|
|
462
|
+
FOnValidatePickup ValidationDelegate;
|
|
463
|
+
|
|
464
|
+
ValidationDelegate.BindLambda([](AActor* Item) -> bool
|
|
465
|
+
{
|
|
466
|
+
return IsValid(Item) && Item->ActorHasTag(FName("Pickup"));
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
if (ValidationDelegate.IsBound())
|
|
470
|
+
{
|
|
471
|
+
bool bValid = ValidationDelegate.Execute(PotentialPickup);
|
|
472
|
+
if (bValid) { CollectItem(PotentialPickup); }
|
|
473
|
+
}
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
---
|
|
477
|
+
|
|
478
|
+
## Payload Parameters (Bound Extras)
|
|
479
|
+
|
|
480
|
+
Single delegate bind methods accept up to 4 "payload" values that are forwarded after the delegate's declared parameters.
|
|
481
|
+
|
|
482
|
+
```cpp
|
|
483
|
+
DECLARE_DELEGATE_OneParam(FOnItemPickedUp, AActor* /* Item */);
|
|
484
|
+
|
|
485
|
+
float BonusMultiplier = 1.5f;
|
|
486
|
+
FName PickupSound = FName("SFX_Pickup");
|
|
487
|
+
|
|
488
|
+
// BonusMultiplier and PickupSound are captured at bind time
|
|
489
|
+
FOnItemPickedUp Delegate;
|
|
490
|
+
Delegate.BindUObject(this, &AMyCharacter::HandlePickupWithExtras, BonusMultiplier, PickupSound);
|
|
491
|
+
|
|
492
|
+
// The bound function signature must match declared params + payload params
|
|
493
|
+
void AMyCharacter::HandlePickupWithExtras(AActor* Item, float Bonus, FName Sound)
|
|
494
|
+
{
|
|
495
|
+
// Bonus == 1.5f, Sound == "SFX_Pickup"
|
|
496
|
+
}
|
|
497
|
+
```
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# Gameplay Classes in C++ — Editor-Side Workflow
|
|
2
|
+
|
|
3
|
+
> Adapted from the `unreal-cpp-gameplay` skill in [awesome-gamedev-agent-skills](https://github.com/gamedev-skills/awesome-gamedev-agent-skills) (Apache-2.0). Cross-skill references were remapped to Arcane slash commands.
|
|
4
|
+
|
|
5
|
+
Write correct UE5 gameplay C++: the reflection macros that connect C++ to the editor and
|
|
6
|
+
Blueprints, the Gameplay Framework class roles, and module dependencies. Targets **UE 5.8**.
|
|
7
|
+
|
|
8
|
+
## When to use
|
|
9
|
+
|
|
10
|
+
- Use when creating C++ gameplay classes (`AActor`, `APawn`, `ACharacter`, `AGameModeBase`,
|
|
11
|
+
`UActorComponent`), exposing properties/functions with `UPROPERTY`/`UFUNCTION`, setting up a
|
|
12
|
+
GameMode's default classes, or adding a module dependency in `*.Build.cs`.
|
|
13
|
+
- Use when the project has a `Source/` tree with `*.h`/`*.cpp` using `UCLASS`, and `*.Build.cs`.
|
|
14
|
+
|
|
15
|
+
**When *not* to use:** designer-facing visual logic → `/ue-blueprints`. Player input
|
|
16
|
+
binding details → `/ue-input-system`. AI logic → `/ue-ai-navigation`. This skill owns
|
|
17
|
+
the C++ class/reflection foundation those build on.
|
|
18
|
+
|
|
19
|
+
## Core workflow
|
|
20
|
+
|
|
21
|
+
1. **Name with the right prefix.** `A` = Actor-derived, `U` = `UObject`/component-derived,
|
|
22
|
+
`F` = plain struct, `E` = enum, `I` = interface. The prefix must match the base class.
|
|
23
|
+
2. **Declare the class with reflection macros.** `UCLASS()` above the class, `GENERATED_BODY()`
|
|
24
|
+
as the first line in the body, and `#include "ClassName.generated.h"` as the **last**
|
|
25
|
+
include in the header.
|
|
26
|
+
3. **Expose data with `UPROPERTY`** (editor/Blueprint visibility *and* garbage-collection
|
|
27
|
+
tracking) and behaviour with `UFUNCTION` (`BlueprintCallable`, etc.).
|
|
28
|
+
4. **Create components in the constructor** with `CreateDefaultSubobject<T>(TEXT("Name"))` and
|
|
29
|
+
set the `RootComponent`.
|
|
30
|
+
5. **Know the framework roles:** `AGameModeBase` sets the rules + default classes; `APawn`/
|
|
31
|
+
`ACharacter` is the controllable body; `APlayerController` is the player's will;
|
|
32
|
+
`UActorComponent` is reusable behaviour.
|
|
33
|
+
6. **Add module dependencies** to `*.Build.cs` (e.g. `EnhancedInput`) or unresolved-symbol
|
|
34
|
+
link errors follow.
|
|
35
|
+
7. **Verify** by compiling (Live Coding `Ctrl+Alt+F11` for function bodies; full rebuild for
|
|
36
|
+
header/UPROPERTY changes) and checking the class/properties appear in the editor.
|
|
37
|
+
|
|
38
|
+
## Patterns
|
|
39
|
+
|
|
40
|
+
### 1. Minimal Actor class (header + source)
|
|
41
|
+
|
|
42
|
+
```cpp
|
|
43
|
+
// Pickup.h
|
|
44
|
+
#pragma once
|
|
45
|
+
#include "CoreMinimal.h"
|
|
46
|
+
#include "GameFramework/Actor.h"
|
|
47
|
+
#include "Pickup.generated.h" // MUST be the last include
|
|
48
|
+
|
|
49
|
+
UCLASS()
|
|
50
|
+
class MYGAME_API APickup : public AActor // MYGAME_API = your module's export macro
|
|
51
|
+
{
|
|
52
|
+
GENERATED_BODY()
|
|
53
|
+
public:
|
|
54
|
+
APickup();
|
|
55
|
+
|
|
56
|
+
// EditAnywhere = tweak per-instance & on the CDO; BlueprintReadWrite = BP get/set.
|
|
57
|
+
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Pickup")
|
|
58
|
+
int32 ScoreValue = 10;
|
|
59
|
+
|
|
60
|
+
// UPROPERTY on a UObject* pointer is what keeps it from being garbage-collected.
|
|
61
|
+
UPROPERTY(VisibleAnywhere)
|
|
62
|
+
TObjectPtr<UStaticMeshComponent> Mesh; // UE5: TObjectPtr instead of raw UStaticMeshComponent*
|
|
63
|
+
|
|
64
|
+
UFUNCTION(BlueprintCallable, Category = "Pickup")
|
|
65
|
+
void Collect();
|
|
66
|
+
|
|
67
|
+
protected:
|
|
68
|
+
virtual void BeginPlay() override;
|
|
69
|
+
};
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
```cpp
|
|
73
|
+
// Pickup.cpp
|
|
74
|
+
#include "Pickup.h"
|
|
75
|
+
#include "Components/StaticMeshComponent.h"
|
|
76
|
+
|
|
77
|
+
APickup::APickup()
|
|
78
|
+
{
|
|
79
|
+
Mesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Mesh"));
|
|
80
|
+
RootComponent = Mesh; // the mesh is this actor's root
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
void APickup::BeginPlay() { Super::BeginPlay(); } // always call Super
|
|
84
|
+
void APickup::Collect() { Destroy(); }
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### 2. GameMode wiring its default classes
|
|
88
|
+
|
|
89
|
+
```cpp
|
|
90
|
+
// MyGameMode.cpp — set in the constructor so the engine spawns your classes.
|
|
91
|
+
AMyGameMode::AMyGameMode()
|
|
92
|
+
{
|
|
93
|
+
DefaultPawnClass = AMyCharacter::StaticClass();
|
|
94
|
+
PlayerControllerClass = AMyPlayerController::StaticClass();
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### 3. Module dependency in Build.cs
|
|
99
|
+
|
|
100
|
+
```csharp
|
|
101
|
+
// MyGame.Build.cs
|
|
102
|
+
PublicDependencyModuleNames.AddRange(new string[]
|
|
103
|
+
{
|
|
104
|
+
"Core", "CoreUObject", "Engine", "InputCore", "EnhancedInput"
|
|
105
|
+
});
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Pitfalls
|
|
109
|
+
|
|
110
|
+
- **`generated.h` not last / missing** — compile errors like "Cannot find generated header" or
|
|
111
|
+
"Expected an include". It must be the final include in the header.
|
|
112
|
+
- **Forgetting `GENERATED_BODY()`** — UHT (Unreal Header Tool) errors; it must be the first
|
|
113
|
+
thing inside the class body.
|
|
114
|
+
- **Raw `UObject*` without `UPROPERTY`** — the garbage collector doesn't see it and may destroy
|
|
115
|
+
it out from under you. Track every UObject pointer with `UPROPERTY` (use `TObjectPtr` in UE5).
|
|
116
|
+
- **Header/UPROPERTY edits with Live Coding** — Live Coding handles function bodies, but
|
|
117
|
+
changes to `UCLASS`/`UPROPERTY`/headers need a full editor restart + rebuild.
|
|
118
|
+
- **Wrong class prefix** — naming an Actor `UFoo` (or a component `AFoo`) breaks UHT; match the
|
|
119
|
+
prefix to the base type.
|
|
120
|
+
- **Unresolved external symbol at link** — the module providing the API isn't in `Build.cs`
|
|
121
|
+
`PublicDependencyModuleNames`.
|
|
122
|
+
- **Not calling `Super::`** in overridden `BeginPlay`/`Tick`/etc. skips engine setup.
|
|
123
|
+
|
|
124
|
+
## References
|
|
125
|
+
|
|
126
|
+
- For `UActorComponent` creation/attachment, the `UPROPERTY` garbage-collection ownership rules
|
|
127
|
+
(`TObjectPtr`, `TArray<TObjectPtr<>>`, `AddToRoot`), and a replication primer, read
|
|
128
|
+
`references/components-and-gc.md`.
|
|
129
|
+
- Primary docs: "Unreal Engine CPP Quick Start" and "Gameplay Framework"
|
|
130
|
+
(`https://dev.epicgames.com/documentation/en-us/unreal-engine/gameplay-framework-in-unreal-engine`).
|
|
131
|
+
|
|
132
|
+
## Related skills
|
|
133
|
+
|
|
134
|
+
- `/ue-blueprints` — exposing C++ to designers; BP/C++ interop.
|
|
135
|
+
- `/ue-input-system` — binding input in a C++ Pawn/Character.
|
|
136
|
+
- `/ue-ai-navigation` — C++ AI tasks driven from a behaviour tree.
|