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,533 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ue-cpp-foundations
|
|
3
|
+
description: "UPROPERTY, UFUNCTION, UCLASS, TArray, TMap, delegates, FString, garbage collection, smart pointers"
|
|
4
|
+
category: "gamedev"
|
|
5
|
+
argument-hint: "[class, type o question]"
|
|
6
|
+
user-invocable: true
|
|
7
|
+
allowed-tools: Read, Glob, Grep, Bash, Write, Edit
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# UE C++ Foundations
|
|
11
|
+
|
|
12
|
+
You are an expert in Unreal Engine's C++ extensions and property system.
|
|
13
|
+
|
|
14
|
+
Ask before writing: this skill edits C++ headers and source files under `Source/`. Propose the change and get approval before using Write or Edit.
|
|
15
|
+
|
|
16
|
+
## Context
|
|
17
|
+
|
|
18
|
+
Read `docs/unreal/project-context.md` for engine version, coding conventions, and project-specific rules. Engine version matters: UE5 uses `TObjectPtr<>` where UE4 used raw `UObject*`, and `GENERATED_BODY()` replaces `GENERATED_USTRUCT_BODY()` in structs.
|
|
19
|
+
|
|
20
|
+
## Before You Start
|
|
21
|
+
|
|
22
|
+
Ask which area the user needs help with if unclear:
|
|
23
|
+
- **Macros & Reflection** — UCLASS, UPROPERTY, UFUNCTION, USTRUCT, UENUM
|
|
24
|
+
- **Containers** — TArray, TMap, TSet, TOptional
|
|
25
|
+
- **Delegates** — static, dynamic, multicast, binding patterns
|
|
26
|
+
- **Strings** — FName, FString, FText conversion and formatting
|
|
27
|
+
- **Memory & GC** — TObjectPtr, TWeakObjectPtr, TSharedPtr, GC roots
|
|
28
|
+
- **Logging** — UE_LOG, log categories, verbosity
|
|
29
|
+
- **Subsystems** — GameInstance, World, LocalPlayer subsystems
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## UObject Macros & Reflection
|
|
34
|
+
|
|
35
|
+
All UE reflection macros require `GENERATED_BODY()` inside the class/struct and the corresponding `.generated.h` include.
|
|
36
|
+
|
|
37
|
+
### UCLASS()
|
|
38
|
+
|
|
39
|
+
| Specifier | Effect |
|
|
40
|
+
|-----------|--------|
|
|
41
|
+
| `Blueprintable` | Blueprint subclassing allowed |
|
|
42
|
+
| `BlueprintType` | Usable as Blueprint variable |
|
|
43
|
+
| `Abstract` | Cannot be instantiated |
|
|
44
|
+
| `NotBlueprintable` | Blocks Blueprint subclassing |
|
|
45
|
+
| `Config=<Name>` | Loads UPROPERTY(Config) from `<Name>.ini` |
|
|
46
|
+
| `Transient` | Not saved/serialized |
|
|
47
|
+
| `Within=<OuterClass>` | Outer must be of given type |
|
|
48
|
+
|
|
49
|
+
```cpp
|
|
50
|
+
UCLASS(Blueprintable, BlueprintType)
|
|
51
|
+
class MYGAME_API UMyDataObject : public UObject
|
|
52
|
+
{
|
|
53
|
+
GENERATED_BODY()
|
|
54
|
+
public:
|
|
55
|
+
UMyDataObject();
|
|
56
|
+
};
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Full specifier list: [references/property-specifiers.md](references/property-specifiers.md).
|
|
60
|
+
|
|
61
|
+
### UPROPERTY()
|
|
62
|
+
|
|
63
|
+
```cpp
|
|
64
|
+
UCLASS(Blueprintable)
|
|
65
|
+
class MYGAME_API AMyCharacter : public ACharacter
|
|
66
|
+
{
|
|
67
|
+
GENERATED_BODY()
|
|
68
|
+
public:
|
|
69
|
+
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Stats")
|
|
70
|
+
float MaxHealth = 100.f;
|
|
71
|
+
|
|
72
|
+
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Stats")
|
|
73
|
+
float CurrentHealth;
|
|
74
|
+
|
|
75
|
+
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="Config")
|
|
76
|
+
int32 MaxLevel = 50;
|
|
77
|
+
|
|
78
|
+
UPROPERTY(ReplicatedUsing=OnRep_Health, Category="Replication")
|
|
79
|
+
float ReplicatedHealth;
|
|
80
|
+
|
|
81
|
+
UPROPERTY(Transient) // Not serialized; GC still tracks
|
|
82
|
+
TObjectPtr<UParticleSystemComponent> CachedFX;
|
|
83
|
+
|
|
84
|
+
UPROPERTY(SaveGame, BlueprintReadWrite, Category="Persistence")
|
|
85
|
+
int32 PlayerScore;
|
|
86
|
+
|
|
87
|
+
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Stats",
|
|
88
|
+
meta=(ClampMin="0.0", ClampMax="1.0"))
|
|
89
|
+
float DamageMultiplier = 1.f;
|
|
90
|
+
|
|
91
|
+
UFUNCTION()
|
|
92
|
+
void OnRep_Health();
|
|
93
|
+
|
|
94
|
+
virtual void GetLifetimeReplicatedProps(
|
|
95
|
+
TArray<FLifetimeProperty>& OutLifetimeProps) const override;
|
|
96
|
+
};
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### UFUNCTION()
|
|
100
|
+
|
|
101
|
+
```cpp
|
|
102
|
+
UFUNCTION(BlueprintCallable, Category="Actions")
|
|
103
|
+
void PerformAttack(float Damage);
|
|
104
|
+
|
|
105
|
+
UFUNCTION(BlueprintPure, Category="Queries")
|
|
106
|
+
float GetHealthPercent() const;
|
|
107
|
+
|
|
108
|
+
UFUNCTION(BlueprintNativeEvent, Category="Events") // C++ provides _Implementation
|
|
109
|
+
void OnDamageTaken(float Amount);
|
|
110
|
+
virtual void OnDamageTaken_Implementation(float Amount);
|
|
111
|
+
|
|
112
|
+
UFUNCTION(BlueprintImplementableEvent, Category="Events") // Blueprint must implement
|
|
113
|
+
void OnLevelUp(int32 NewLevel);
|
|
114
|
+
|
|
115
|
+
UFUNCTION(Server, Reliable, WithValidation) // RPC: runs on server
|
|
116
|
+
void ServerFireWeapon(FVector Origin, FVector Direction);
|
|
117
|
+
void ServerFireWeapon_Implementation(FVector Origin, FVector Direction);
|
|
118
|
+
bool ServerFireWeapon_Validate(FVector Origin, FVector Direction);
|
|
119
|
+
|
|
120
|
+
UFUNCTION(Client, Reliable) // RPC: runs on owning client
|
|
121
|
+
void ClientShowDamageNumber(float Amount);
|
|
122
|
+
void ClientShowDamageNumber_Implementation(float Amount);
|
|
123
|
+
|
|
124
|
+
UFUNCTION(NetMulticast, Reliable) // RPC: runs on all
|
|
125
|
+
void MulticastPlayEffect(FVector Location);
|
|
126
|
+
void MulticastPlayEffect_Implementation(FVector Location);
|
|
127
|
+
|
|
128
|
+
UFUNCTION(Exec) // Console command (~ in-game)
|
|
129
|
+
void DebugResetStats(); // Works on PC, Pawn, HUD, GM, GI, CheatManager
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### USTRUCT() and UENUM()
|
|
133
|
+
|
|
134
|
+
```cpp
|
|
135
|
+
// UE5: always GENERATED_BODY() — never GENERATED_USTRUCT_BODY()
|
|
136
|
+
USTRUCT(BlueprintType)
|
|
137
|
+
struct MYGAME_API FWeaponStats
|
|
138
|
+
{
|
|
139
|
+
GENERATED_BODY()
|
|
140
|
+
UPROPERTY(EditAnywhere, BlueprintReadWrite) float BaseDamage = 10.f;
|
|
141
|
+
UPROPERTY(EditAnywhere, BlueprintReadWrite) float FireRate = 0.5f;
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
// DataTable row
|
|
145
|
+
USTRUCT(BlueprintType)
|
|
146
|
+
struct MYGAME_API FEnemyTableRow : public FTableRowBase
|
|
147
|
+
{
|
|
148
|
+
GENERATED_BODY()
|
|
149
|
+
UPROPERTY(EditAnywhere, BlueprintReadWrite) FName EnemyID;
|
|
150
|
+
UPROPERTY(EditAnywhere, BlueprintReadWrite) TSoftClassPtr<AActor> SpawnClass;
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
UENUM(BlueprintType)
|
|
154
|
+
enum class EWeaponState : uint8
|
|
155
|
+
{
|
|
156
|
+
Idle UMETA(DisplayName="Idle"),
|
|
157
|
+
Firing UMETA(DisplayName="Firing"),
|
|
158
|
+
Reloading UMETA(DisplayName="Reloading"),
|
|
159
|
+
};
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
## UE Containers
|
|
165
|
+
|
|
166
|
+
See [references/container-patterns.md](references/container-patterns.md) for full API and performance guide.
|
|
167
|
+
|
|
168
|
+
### TArray — Ordered Dynamic Array
|
|
169
|
+
|
|
170
|
+
```cpp
|
|
171
|
+
TArray<FString> Names;
|
|
172
|
+
Names.Add(TEXT("Alpha"));
|
|
173
|
+
Names.Emplace(TEXT("Beta")); // Construct in-place (avoids copy)
|
|
174
|
+
Names.Reserve(100); // Pre-allocate
|
|
175
|
+
|
|
176
|
+
FString First = Names[0];
|
|
177
|
+
bool bHas = Names.Contains(TEXT("Alpha"));
|
|
178
|
+
int32 Idx = Names.Find(TEXT("Beta")); // INDEX_NONE if absent
|
|
179
|
+
FString* Ptr = Names.FindByPredicate([](const FString& S){ return S.StartsWith(TEXT("A")); });
|
|
180
|
+
|
|
181
|
+
Names.Sort([](const FString& A, const FString& B){ return A.Len() < B.Len(); });
|
|
182
|
+
Names.Remove(TEXT("Alpha")); // Order-preserving O(n)
|
|
183
|
+
Names.RemoveAtSwap(0); // Fast O(1), destroys order
|
|
184
|
+
|
|
185
|
+
for (const FString& N : Names) { /* do NOT add/remove during ranged-for */ }
|
|
186
|
+
for (int32 i = Names.Num()-1; i >= 0; --i) { if (Names[i].IsEmpty()) Names.RemoveAt(i); }
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### TMap — Hash Map
|
|
190
|
+
|
|
191
|
+
```cpp
|
|
192
|
+
TMap<FName, int32> ItemCounts;
|
|
193
|
+
ItemCounts.Add(FName("Sword"), 3);
|
|
194
|
+
|
|
195
|
+
int32& Ref = ItemCounts.FindOrAdd(FName("Sword")); // Insert default if absent
|
|
196
|
+
int32* Ptr = ItemCounts.Find(FName("Axe")); // nullptr if absent
|
|
197
|
+
bool bHas = ItemCounts.Contains(FName("Shield"));
|
|
198
|
+
ItemCounts.Remove(FName("Shield"));
|
|
199
|
+
|
|
200
|
+
for (const TPair<FName, int32>& Pair : ItemCounts) { /* ... */ }
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
### TSet — Hash Set
|
|
204
|
+
|
|
205
|
+
```cpp
|
|
206
|
+
TSet<FName> Tags;
|
|
207
|
+
Tags.Add(FName("Flying"));
|
|
208
|
+
bool bFlying = Tags.Contains(FName("Flying"));
|
|
209
|
+
TSet<FName> Intersect = Tags.Intersect(OtherTags);
|
|
210
|
+
TSet<FName> Union = Tags.Union(OtherTags);
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
### TOptional
|
|
214
|
+
|
|
215
|
+
```cpp
|
|
216
|
+
TOptional<float> MaybeHP;
|
|
217
|
+
if (MaybeHP.IsSet()) { float H = MaybeHP.GetValue(); }
|
|
218
|
+
float Safe = MaybeHP.Get(0.f); // Default if not set
|
|
219
|
+
MaybeHP = 75.f;
|
|
220
|
+
MaybeHP.Reset();
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
### TVariant
|
|
224
|
+
|
|
225
|
+
```cpp
|
|
226
|
+
// Type-safe tagged union — avoids unsafe casts
|
|
227
|
+
TVariant<int32, float, FString> Value;
|
|
228
|
+
Value.Set<FString>(TEXT("Hello"));
|
|
229
|
+
|
|
230
|
+
if (Value.IsType<FString>())
|
|
231
|
+
{
|
|
232
|
+
const FString& Str = Value.Get<FString>();
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Visit — use explicit overloads; LexToString(V) fails when V is FString.
|
|
236
|
+
Visit(TOverloaded{
|
|
237
|
+
[](int32 V) { UE_LOG(LogTemp, Log, TEXT("%d"), V); },
|
|
238
|
+
[](float V) { UE_LOG(LogTemp, Log, TEXT("%f"), V); },
|
|
239
|
+
[](const FString& V) { UE_LOG(LogTemp, Log, TEXT("%s"), *V); },
|
|
240
|
+
}, Value);
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
---
|
|
244
|
+
|
|
245
|
+
## Delegates
|
|
246
|
+
|
|
247
|
+
See [references/delegate-patterns.md](references/delegate-patterns.md) for all declaration macros and binding methods.
|
|
248
|
+
|
|
249
|
+
### Choosing the Right Type
|
|
250
|
+
|
|
251
|
+
| Type | Bindings | Blueprint | When to Use |
|
|
252
|
+
|------|----------|-----------|-------------|
|
|
253
|
+
| `DECLARE_DELEGATE` | 1 | No | Internal single-owner callback |
|
|
254
|
+
| `DECLARE_MULTICAST_DELEGATE` | N | No | Internal multi-listener events |
|
|
255
|
+
| `DECLARE_DYNAMIC_DELEGATE` | 1 | Yes | Blueprint-assignable single callback |
|
|
256
|
+
| `DECLARE_DYNAMIC_MULTICAST_DELEGATE` | N | Yes | Blueprint-bindable events (most common) |
|
|
257
|
+
|
|
258
|
+
### Declaration, Binding, Invocation
|
|
259
|
+
|
|
260
|
+
```cpp
|
|
261
|
+
// File scope (before UCLASS)
|
|
262
|
+
DECLARE_DELEGATE_OneParam(FOnItemPickedUp, AActor*);
|
|
263
|
+
DECLARE_MULTICAST_DELEGATE_TwoParams(FOnHealthChanged, float, float);
|
|
264
|
+
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnHealthChangedDynamic, float, CurrentHealth, float, MaxHealth);
|
|
265
|
+
|
|
266
|
+
UCLASS()
|
|
267
|
+
class AMyActor : public AActor
|
|
268
|
+
{
|
|
269
|
+
GENERATED_BODY()
|
|
270
|
+
public:
|
|
271
|
+
UPROPERTY(BlueprintAssignable, Category="Events")
|
|
272
|
+
FOnHealthChangedDynamic OnHealthChanged; // Dynamic multicast in UPROPERTY
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
// Static single delegate
|
|
276
|
+
FOnItemPickedUp D;
|
|
277
|
+
D.BindUObject(this, &AMyCharacter::HandlePickup);
|
|
278
|
+
D.BindLambda([this](AActor* Item){ UE_LOG(LogTemp, Log, TEXT("%s"), *Item->GetName()); });
|
|
279
|
+
D.ExecuteIfBound(SomeActor);
|
|
280
|
+
// Multicast: add/broadcast/remove
|
|
281
|
+
FDelegateHandle H = HealthDelegate.AddUObject(this, &AMyHUD::OnHealthChanged);
|
|
282
|
+
HealthDelegate.Broadcast(75.f, 100.f);
|
|
283
|
+
HealthDelegate.Remove(H);
|
|
284
|
+
// Dynamic multicast
|
|
285
|
+
OnHealthChanged.AddDynamic(this, &AMyCharacter::HandleHealthChange);
|
|
286
|
+
OnHealthChanged.RemoveDynamic(this, &AMyCharacter::HandleHealthChange);
|
|
287
|
+
OnHealthChanged.Broadcast(75.f, 100.f);
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
---
|
|
291
|
+
|
|
292
|
+
## String Types
|
|
293
|
+
|
|
294
|
+
| Type | Use For | Comparison | Mutable |
|
|
295
|
+
|------|---------|-----------|---------|
|
|
296
|
+
| `FName` | Identifiers, asset names, tags | O(1) integer | No |
|
|
297
|
+
| `FString` | General-purpose strings, file paths | O(n) | Yes |
|
|
298
|
+
| `FText` | Player-visible display strings | — | No |
|
|
299
|
+
|
|
300
|
+
```cpp
|
|
301
|
+
// FName — global name table, case-insensitive O(1) compare
|
|
302
|
+
FName Tag("WeaponTag_Rifle");
|
|
303
|
+
FString S = Tag.ToString();
|
|
304
|
+
FName N = FName(*S);
|
|
305
|
+
|
|
306
|
+
// FString — heap string, Printf for formatting
|
|
307
|
+
FString Msg = FString::Printf(TEXT("HP: %.1f"), Health);
|
|
308
|
+
UE_LOG(LogTemp, Log, TEXT("%s"), *Msg); // * dereferences to TCHAR*
|
|
309
|
+
int32 Num = FCString::Atoi(*FString("42"));
|
|
310
|
+
|
|
311
|
+
// FText — localized display text
|
|
312
|
+
// LOCTEXT requires a namespace defined in the same translation unit:
|
|
313
|
+
#define LOCTEXT_NAMESPACE "MyGame"
|
|
314
|
+
FText Label = LOCTEXT("Key", "Assault Rifle");
|
|
315
|
+
FText Fmt = FText::Format(LOCTEXT("HP", "HP: {0}/{1}"),
|
|
316
|
+
FText::AsNumber(Cur), FText::AsNumber(Max));
|
|
317
|
+
#undef LOCTEXT_NAMESPACE // Or: NSLOCTEXT("MyGame", "Key", "...") without a define
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
**Conversion:** `Name.ToString()` → FString, `FName(*Str)` ← FString, `FText::FromString(Str)`, `Text.ToString()`.
|
|
321
|
+
|
|
322
|
+
---
|
|
323
|
+
|
|
324
|
+
## Memory & Garbage Collection
|
|
325
|
+
|
|
326
|
+
UE's GC tracks every `UObject*` reachable from a root. Unreachable objects are destroyed.
|
|
327
|
+
|
|
328
|
+
```cpp
|
|
329
|
+
// UE5: TObjectPtr<> for UPROPERTY member UObject pointers
|
|
330
|
+
UPROPERTY()
|
|
331
|
+
TObjectPtr<UStaticMeshComponent> MeshComp; // GC-tracked, lazy-load aware
|
|
332
|
+
// Without UPROPERTY — invisible to GC, pointer may dangle
|
|
333
|
+
UMyObject* UnsafePtr; // BAD
|
|
334
|
+
// TWeakObjectPtr — non-owning, safe (becomes null after GC)
|
|
335
|
+
TWeakObjectPtr<AMyActor> WeakRef;
|
|
336
|
+
if (WeakRef.IsValid()) { WeakRef->DoSomething(); }
|
|
337
|
+
// TSharedPtr/TWeakPtr — for non-UObject plain C++ types ONLY
|
|
338
|
+
TSharedPtr<FMyData> Data = MakeShared<FMyData>();
|
|
339
|
+
TWeakPtr<FMyData> Weak = Data;
|
|
340
|
+
if (TSharedPtr<FMyData> Pinned = Weak.Pin()) { Pinned->Process(); }
|
|
341
|
+
// NEVER use TSharedPtr for UObject-derived types
|
|
342
|
+
// GC root: AddToRoot (use sparingly)
|
|
343
|
+
UMyObject* Obj = NewObject<UMyObject>();
|
|
344
|
+
Obj->AddToRoot();
|
|
345
|
+
// ...
|
|
346
|
+
Obj->RemoveFromRoot();
|
|
347
|
+
// FGCObject: preferred for non-UObject C++ classes holding UObject refs
|
|
348
|
+
// UE 5.3+: use TObjectPtr<> — raw UObject* crashes with incremental GC.
|
|
349
|
+
class FMyManager : public FGCObject
|
|
350
|
+
{
|
|
351
|
+
public:
|
|
352
|
+
virtual void AddReferencedObjects(FReferenceCollector& Collector) override
|
|
353
|
+
{
|
|
354
|
+
Collector.AddReferencedObject(ManagedObject);
|
|
355
|
+
}
|
|
356
|
+
virtual FString GetReferencerName() const override { return TEXT("FMyManager"); }
|
|
357
|
+
private:
|
|
358
|
+
TObjectPtr<UMyObject> ManagedObject = nullptr;
|
|
359
|
+
};
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
---
|
|
363
|
+
|
|
364
|
+
## Logging
|
|
365
|
+
|
|
366
|
+
```cpp
|
|
367
|
+
// MyGameLog.h / .cpp
|
|
368
|
+
DECLARE_LOG_CATEGORY_EXTERN(LogMyGame, Log, All);
|
|
369
|
+
DEFINE_LOG_CATEGORY(LogMyGame);
|
|
370
|
+
|
|
371
|
+
// Single-file: DEFINE_LOG_CATEGORY_STATIC(LogLocal, Log, All);
|
|
372
|
+
|
|
373
|
+
UE_LOG(LogMyGame, Log, TEXT("Loaded: %s"), *LevelName);
|
|
374
|
+
UE_LOG(LogMyGame, Warning, TEXT("HP low: %.1f"), Health);
|
|
375
|
+
UE_LOG(LogMyGame, Error, TEXT("Spawn failed: %s"), *ClassName);
|
|
376
|
+
UE_CLOG(Health < 0.f, LogMyGame, Error, TEXT("Negative HP: %.1f"), Health);
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
| Verbosity | Visible | When |
|
|
380
|
+
|-----------|---------|------|
|
|
381
|
+
| `Fatal` | Always | Crash-level |
|
|
382
|
+
| `Error` | Always | Operation failed |
|
|
383
|
+
| `Warning` | Always | Unexpected but recoverable |
|
|
384
|
+
| `Log` | Non-shipping | Standard trace |
|
|
385
|
+
| `Verbose` | `-LogCmds` | Fine-grained trace |
|
|
386
|
+
|
|
387
|
+
---
|
|
388
|
+
|
|
389
|
+
## Subsystems
|
|
390
|
+
|
|
391
|
+
Auto-registered singletons — no manual `AddToRoot` needed.
|
|
392
|
+
|
|
393
|
+
| Subsystem | Owner | Persists Level Load | Per Player |
|
|
394
|
+
|-----------|-------|---------------------|------------|
|
|
395
|
+
| `UGameInstanceSubsystem` | `UGameInstance` | Yes | No |
|
|
396
|
+
| `UWorldSubsystem` | `UWorld` | No | No |
|
|
397
|
+
| `ULocalPlayerSubsystem` | `ULocalPlayer` | Yes | Yes |
|
|
398
|
+
| `UEngineSubsystem` | `UEngine` | Yes (whole session) | No |
|
|
399
|
+
|
|
400
|
+
```cpp
|
|
401
|
+
UCLASS()
|
|
402
|
+
class MYGAME_API UInventorySubsystem : public UGameInstanceSubsystem
|
|
403
|
+
{
|
|
404
|
+
GENERATED_BODY()
|
|
405
|
+
public:
|
|
406
|
+
virtual void Initialize(FSubsystemCollectionBase& Collection) override;
|
|
407
|
+
virtual void Deinitialize() override;
|
|
408
|
+
|
|
409
|
+
UFUNCTION(BlueprintCallable) void AddItem(FName ItemID, int32 Count);
|
|
410
|
+
private:
|
|
411
|
+
TMap<FName, int32> Inventory;
|
|
412
|
+
};
|
|
413
|
+
|
|
414
|
+
// Access — each subsystem type has a different accessor
|
|
415
|
+
UInventorySubsystem* Inv = GetGameInstance()->GetSubsystem<UInventorySubsystem>();
|
|
416
|
+
USpawnSubsystem* Sp = GetWorld()->GetSubsystem<USpawnSubsystem>();
|
|
417
|
+
UUIStateSubsystem* UI = GetLocalPlayer()->GetSubsystem<UUIStateSubsystem>();
|
|
418
|
+
UMyEngineSubsystem* ES = GEngine->GetEngineSubsystem<UMyEngineSubsystem>();
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
Subsystems have `Initialize()` and `Deinitialize()` -- override for setup/teardown. `UGameInstanceSubsystem` persists across map changes; `UWorldSubsystem` reinitializes per world. Call `GetSubsystem<T>()` via the owning context (`GetGameInstance()`, `GetWorld()`, `GetLocalPlayer()`).
|
|
422
|
+
|
|
423
|
+
---
|
|
424
|
+
|
|
425
|
+
## Replicated Properties
|
|
426
|
+
|
|
427
|
+
Both `UPROPERTY` specifier AND `GetLifetimeReplicatedProps` are required:
|
|
428
|
+
|
|
429
|
+
```cpp
|
|
430
|
+
UPROPERTY(ReplicatedUsing = OnRep_Health)
|
|
431
|
+
float Health;
|
|
432
|
+
|
|
433
|
+
UFUNCTION()
|
|
434
|
+
void OnRep_Health(); // Called on clients when server updates Health
|
|
435
|
+
|
|
436
|
+
void AMyActor::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutProps) const
|
|
437
|
+
{
|
|
438
|
+
Super::GetLifetimeReplicatedProps(OutProps);
|
|
439
|
+
DOREPLIFETIME_CONDITION(AMyActor, Health, COND_OwnerOnly);
|
|
440
|
+
// COND_None, COND_OwnerOnly, COND_SkipOwner, COND_SimulatedOnly, COND_InitialOnly
|
|
441
|
+
}
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
---
|
|
445
|
+
|
|
446
|
+
## Conditional Compilation Guards
|
|
447
|
+
|
|
448
|
+
```cpp
|
|
449
|
+
#if WITH_EDITOR
|
|
450
|
+
// Editor-only properties — stripped from shipping builds
|
|
451
|
+
UPROPERTY(EditAnywhere, Category="Debug")
|
|
452
|
+
bool bShowDebugSpheres = false;
|
|
453
|
+
|
|
454
|
+
virtual void PostEditChangeProperty(FPropertyChangedEvent& E) override;
|
|
455
|
+
#endif
|
|
456
|
+
|
|
457
|
+
#if !UE_BUILD_SHIPPING
|
|
458
|
+
// Available in Development + Debug, stripped from Shipping
|
|
459
|
+
void DrawDebugInfo();
|
|
460
|
+
#endif
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
---
|
|
464
|
+
|
|
465
|
+
## Common Mistakes
|
|
466
|
+
|
|
467
|
+
**Raw UObject* member without UPROPERTY — dangling pointer:**
|
|
468
|
+
```cpp
|
|
469
|
+
UMyObject* Obj; // BAD — GC invisible
|
|
470
|
+
UPROPERTY() TObjectPtr<UMyObject> Obj; // GOOD
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
**Modify TArray during ranged-for — undefined behavior:**
|
|
474
|
+
```cpp
|
|
475
|
+
for (const AActor* A : Actors) { Actors.Remove(A); } // CRASH
|
|
476
|
+
for (int32 i = Actors.Num()-1; i >= 0; --i) { if (ShouldRemove(Actors[i])) Actors.RemoveAt(i); }
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
**TSharedPtr on a UObject — GC + refcount conflict:**
|
|
480
|
+
```cpp
|
|
481
|
+
TSharedPtr<UMyObject> P = MakeShared<UMyObject>(); // BAD — leaks or double-free
|
|
482
|
+
UPROPERTY() TObjectPtr<UMyObject> P; // GOOD
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
**Missing GetLifetimeReplicatedProps:**
|
|
486
|
+
```cpp
|
|
487
|
+
void AMyActor::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
|
|
488
|
+
{
|
|
489
|
+
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
|
|
490
|
+
DOREPLIFETIME(AMyActor, ReplicatedHealth);
|
|
491
|
+
DOREPLIFETIME_CONDITION(AMyActor, TeamScore, COND_OwnerOnly);
|
|
492
|
+
}
|
|
493
|
+
```
|
|
494
|
+
|
|
495
|
+
**GENERATED_USTRUCT_BODY() in UE5 structs — use GENERATED_BODY() instead.**
|
|
496
|
+
|
|
497
|
+
**AddDynamic with a non-UFUNCTION — compile error or crash at runtime.**
|
|
498
|
+
|
|
499
|
+
---
|
|
500
|
+
|
|
501
|
+
## Related Skills
|
|
502
|
+
|
|
503
|
+
- `/ue-module-build-system` — Build.cs, module dependencies, include paths, PCH configuration
|
|
504
|
+
- `/ue-actor-component-architecture` — AActor/UActorComponent lifecycle, spawning, tick groups, component setup
|
|
505
|
+
- `/ue-blueprints` — the visual-scripting side of the C++/Blueprint boundary
|
|
506
|
+
|
|
507
|
+
## Reference Files
|
|
508
|
+
|
|
509
|
+
- `references/property-specifiers.md` — full `UPROPERTY`/`UFUNCTION` specifier list
|
|
510
|
+
- `references/container-patterns.md` — `TArray`/`TMap`/`TSet` API and performance guide
|
|
511
|
+
- `references/delegate-patterns.md` — delegate declaration macros and binding methods
|
|
512
|
+
- `references/gameplay-classes-cpp.md` — editor-side workflow for the gameplay framework classes in C++
|
|
513
|
+
- `references/components-and-gc.md` — component ownership and garbage-collection rules
|
|
514
|
+
|
|
515
|
+
---
|
|
516
|
+
|
|
517
|
+
## Checklist
|
|
518
|
+
|
|
519
|
+
Validate before considering the work done:
|
|
520
|
+
|
|
521
|
+
- [ ] Every UObject reference is a `TObjectPtr<>` with `UPROPERTY()` markup
|
|
522
|
+
- [ ] Containers are `TArray`/`TMap`/`TSet`, strings use the right `FName`/`FText`/`FString` role
|
|
523
|
+
- [ ] No `new`/`delete` on UObjects — `NewObject<>`/`CreateDefaultSubobject<>` only
|
|
524
|
+
- [ ] Conventions in `docs/unreal/project-context.md` were read and followed
|
|
525
|
+
- [ ] The Common Mistakes section of this skill was reviewed against the change
|
|
526
|
+
|
|
527
|
+
**PASS** — every item holds. **CONCERNS** — the work stands but at least one item is unresolved and worth flagging to the user. **FAIL** — an item is violated in a way that breaks correctness, replication or the frame budget; fix it before handing the work over.
|
|
528
|
+
|
|
529
|
+
## Next Steps
|
|
530
|
+
|
|
531
|
+
- `/ue-actor-component-architecture` — apply these types to actor design
|
|
532
|
+
- `/code-review` — architectural and quality review of the change
|
|
533
|
+
- `/commit` — conventional commit once the change is verified
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
> 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.
|
|
2
|
+
|
|
3
|
+
# Components, garbage collection & replication primer (UE 5.8 C++)
|
|
4
|
+
|
|
5
|
+
Depth for `/ue-cpp-foundations`. Verified against the Gameplay Framework docs and the UE5
|
|
6
|
+
C++ programming references. Covers component creation/attachment, the UPROPERTY/GC ownership
|
|
7
|
+
rules that trip up newcomers, and a minimal replication example.
|
|
8
|
+
|
|
9
|
+
## Creating and attaching components
|
|
10
|
+
|
|
11
|
+
Default subobjects are created in the constructor; runtime components use `NewObject` +
|
|
12
|
+
`RegisterComponent`.
|
|
13
|
+
|
|
14
|
+
```cpp
|
|
15
|
+
// In the constructor — built into the Class Default Object, editable in the Blueprint subclass.
|
|
16
|
+
ACharacterBase::ACharacterBase()
|
|
17
|
+
{
|
|
18
|
+
SpringArm = CreateDefaultSubobject<USpringArmComponent>(TEXT("SpringArm"));
|
|
19
|
+
SpringArm->SetupAttachment(RootComponent); // attach to an existing component
|
|
20
|
+
SpringArm->TargetArmLength = 300.f;
|
|
21
|
+
|
|
22
|
+
Camera = CreateDefaultSubobject<UCameraComponent>(TEXT("Camera"));
|
|
23
|
+
Camera->SetupAttachment(SpringArm); // socket chain: root -> arm -> camera
|
|
24
|
+
}
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
```cpp
|
|
28
|
+
// At runtime (e.g. add a component after spawn):
|
|
29
|
+
UAudioComponent* Audio = NewObject<UAudioComponent>(this);
|
|
30
|
+
Audio->RegisterComponent(); // required, or it won't tick/render
|
|
31
|
+
Audio->AttachToComponent(RootComponent, FAttachmentTransformRules::KeepRelativeTransform);
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
- `CreateDefaultSubobject` is **only** valid in a constructor; using it later asserts.
|
|
35
|
+
- `SetupAttachment` is for constructor-time hierarchy; `AttachToComponent` is the runtime call.
|
|
36
|
+
|
|
37
|
+
## Garbage collection ownership (the #1 newcomer crash)
|
|
38
|
+
|
|
39
|
+
The UE garbage collector only keeps `UObject`s alive if it can reach them through a reflected
|
|
40
|
+
reference. A raw `UObject*` member that isn't a `UPROPERTY` is invisible to the GC and can be
|
|
41
|
+
collected, leaving a dangling pointer.
|
|
42
|
+
|
|
43
|
+
```cpp
|
|
44
|
+
UPROPERTY() // GC sees it -> stays alive while this owner lives
|
|
45
|
+
TObjectPtr<UMyDataObject> Data;
|
|
46
|
+
|
|
47
|
+
UPROPERTY()
|
|
48
|
+
TArray<TObjectPtr<AActor>> Tracked; // containers of UObjects also need UPROPERTY
|
|
49
|
+
|
|
50
|
+
UMyDataObject* Raw; // BUG: not tracked; may be GC'd -> crash
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Rules:
|
|
54
|
+
- Every `UObject`/Actor pointer that must persist gets a `UPROPERTY` (use `TObjectPtr<T>` in
|
|
55
|
+
UE5; raw pointers still compile but `TObjectPtr` adds access tracking in the editor).
|
|
56
|
+
- Plain C++ data (ints, FStrings, FVectors, USTRUCTs of POD) doesn't need this — it's owned by
|
|
57
|
+
value.
|
|
58
|
+
- For a UObject you create that has no UPROPERTY owner (rare), `AddToRoot()` pins it and
|
|
59
|
+
`RemoveFromRoot()` releases it — but prefer proper ownership.
|
|
60
|
+
|
|
61
|
+
## USTRUCT and UENUM for reflected data
|
|
62
|
+
|
|
63
|
+
```cpp
|
|
64
|
+
UENUM(BlueprintType)
|
|
65
|
+
enum class EWeaponType : uint8 { Pistol, Rifle, Shotgun };
|
|
66
|
+
|
|
67
|
+
USTRUCT(BlueprintType)
|
|
68
|
+
struct FWeaponStats
|
|
69
|
+
{
|
|
70
|
+
GENERATED_BODY()
|
|
71
|
+
UPROPERTY(EditAnywhere, BlueprintReadWrite) int32 Damage = 10;
|
|
72
|
+
UPROPERTY(EditAnywhere, BlueprintReadWrite) float FireRate = 0.25f;
|
|
73
|
+
};
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
`BlueprintType` makes the enum/struct usable as Blueprint variables.
|
|
77
|
+
|
|
78
|
+
## Minimal replication (multiplayer)
|
|
79
|
+
|
|
80
|
+
```cpp
|
|
81
|
+
// Header
|
|
82
|
+
UPROPERTY(ReplicatedUsing = OnRep_Health)
|
|
83
|
+
float Health = 100.f;
|
|
84
|
+
|
|
85
|
+
UFUNCTION()
|
|
86
|
+
void OnRep_Health(); // client callback when Health replicates
|
|
87
|
+
|
|
88
|
+
// Source
|
|
89
|
+
#include "Net/UnrealNetwork.h"
|
|
90
|
+
void AMyActor::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& Out) const
|
|
91
|
+
{
|
|
92
|
+
Super::GetLifetimeReplicatedProps(Out);
|
|
93
|
+
DOREPLIFETIME(AMyActor, Health); // register the property for replication
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
- Set `bReplicates = true;` in the constructor for the actor to replicate at all.
|
|
98
|
+
- Server authority: gameplay changes happen on the server; clients receive replicated state and
|
|
99
|
+
react in `OnRep_` callbacks.
|
|
100
|
+
|
|
101
|
+
## Gotchas
|
|
102
|
+
|
|
103
|
+
- `NewObject` without `RegisterComponent` gives a component that exists but never ticks or
|
|
104
|
+
renders.
|
|
105
|
+
- Forgetting `#include "Net/UnrealNetwork.h"` breaks `DOREPLIFETIME`.
|
|
106
|
+
- `TObjectPtr` implicitly converts to the raw pointer for calls, so existing `->`/`.` usage is
|
|
107
|
+
unchanged; the difference is GC visibility and editor tooling, not syntax at call sites.
|