claude-code-arcane 2.2.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 +37 -0
- package/README.md +104 -55
- package/agents/career/career-strategist.md +88 -0
- package/agents/career/cv-reviewer.md +81 -0
- package/agents/career/hiring-manager.md +68 -0
- package/agents/career/mock-interviewer.md +76 -0
- package/agents/freelance/client-screener.md +84 -0
- package/agents/freelance/discovery-call.md +65 -0
- package/agents/freelance/pipeline-strategist.md +99 -0
- package/agents/freelance/proposal-reviewer.md +82 -0
- 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 +57 -13
- package/docs/RELEASE-SETUP.md +1 -1
- package/docs/SKILLS-CATALOG.md +67 -4
- package/docs/USER-GUIDE.md +77 -72
- package/docs/agent-hierarchy.md +21 -1
- package/docs/presentations/build_arcane_deck.py +2 -2
- package/docs/presentations/build_arcane_deck_12.py +2 -2
- package/hooks/detect-unreal.sh +90 -0
- package/package.json +2 -2
- package/profiles/freelance.yaml +43 -0
- package/profiles/job-hunt.yaml +5 -2
- package/profiles/unreal-dev.yaml +97 -0
- package/rules/drafter-reviewer.md +16 -3
- package/rules/freelance-guardrails.md +53 -0
- package/rules/gamedev/unreal-code.md +125 -0
- package/skills/career-registry/SKILL.md +95 -0
- package/skills/career-registry/scripts/career_registry.py +488 -0
- package/skills/client-screen/SKILL.md +83 -0
- package/skills/client-screen/references/red-flags.md +95 -0
- package/skills/cover-letter/SKILL.md +4 -3
- package/skills/cv-tailor/SKILL.md +11 -2
- package/skills/freelance-deliver/SKILL.md +90 -0
- package/skills/freelance-hunt/SKILL.md +126 -0
- package/skills/freelance-hunt/references/templates/Connects.md +39 -0
- package/skills/freelance-hunt/references/templates/Contrato.md +60 -0
- package/skills/freelance-hunt/references/templates/Propuesta.md +66 -0
- package/skills/freelance-kickoff/SKILL.md +94 -0
- package/skills/freelance-pipeline/SKILL.md +109 -0
- package/skills/freelance-pipeline/scripts/import_history.py +425 -0
- package/skills/freelance-profile/SKILL.md +93 -0
- package/skills/freelance-proposal/SKILL.md +112 -0
- package/skills/freelance-scan/SKILL.md +196 -0
- package/skills/freelance-scan/references/dedup.md +35 -0
- package/skills/freelance-scan/references/platforms.md +167 -0
- package/skills/freelance-scan/scripts/freelance_search.py +1077 -0
- package/skills/install-mcp/SKILL.md +135 -46
- package/skills/install-mcp/references/unreal-operations.md +55 -0
- package/skills/interview-prep/SKILL.md +11 -3
- package/skills/job-aplicar/SKILL.md +5 -1
- package/skills/job-hunt/SKILL.md +17 -0
- package/skills/job-outcome/SKILL.md +15 -1
- package/skills/job-upskill/SKILL.md +10 -2
- 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,526 @@
|
|
|
1
|
+
# Replication Patterns
|
|
2
|
+
|
|
3
|
+
Common patterns for replicating game state in Unreal Engine multiplayer projects.
|
|
4
|
+
Each pattern includes the header declarations, implementation, and client callback.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Pattern 1: Health with OnRep Callback
|
|
9
|
+
|
|
10
|
+
The most common pattern. Health is authoritative on the server. Clients receive the
|
|
11
|
+
updated value and react via `OnRep_Health` (update HUD, play hurt effects).
|
|
12
|
+
|
|
13
|
+
```cpp
|
|
14
|
+
// AMyCharacter.h
|
|
15
|
+
UPROPERTY(ReplicatedUsing = OnRep_Health)
|
|
16
|
+
float Health;
|
|
17
|
+
|
|
18
|
+
UPROPERTY(ReplicatedUsing = OnRep_MaxHealth)
|
|
19
|
+
float MaxHealth;
|
|
20
|
+
|
|
21
|
+
UFUNCTION()
|
|
22
|
+
void OnRep_Health(float PreviousHealth);
|
|
23
|
+
|
|
24
|
+
UFUNCTION()
|
|
25
|
+
void OnRep_MaxHealth(float PreviousMaxHealth);
|
|
26
|
+
|
|
27
|
+
// Server RPC — clients request damage (usually called by server game logic instead)
|
|
28
|
+
UFUNCTION(Server, Reliable, WithValidation)
|
|
29
|
+
void ServerApplyHealing(float Amount);
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
```cpp
|
|
33
|
+
// AMyCharacter.cpp
|
|
34
|
+
#include "Net/UnrealNetwork.h"
|
|
35
|
+
|
|
36
|
+
void AMyCharacter::GetLifetimeReplicatedProps(
|
|
37
|
+
TArray<FLifetimeProperty>& OutLifetimeProps) const
|
|
38
|
+
{
|
|
39
|
+
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
|
|
40
|
+
DOREPLIFETIME(AMyCharacter, Health);
|
|
41
|
+
DOREPLIFETIME(AMyCharacter, MaxHealth);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
void AMyCharacter::OnRep_Health(float PreviousHealth)
|
|
45
|
+
{
|
|
46
|
+
// Called on clients only — update UI and effects
|
|
47
|
+
OnHealthChanged.Broadcast(PreviousHealth, Health);
|
|
48
|
+
if (Health < PreviousHealth)
|
|
49
|
+
{
|
|
50
|
+
PlayHurtMontage();
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
void AMyCharacter::OnRep_MaxHealth(float PreviousMaxHealth)
|
|
55
|
+
{
|
|
56
|
+
OnMaxHealthChanged.Broadcast(PreviousMaxHealth, MaxHealth);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Called by game logic on server only
|
|
60
|
+
void AMyCharacter::ApplyDamage_Authority(float Damage)
|
|
61
|
+
{
|
|
62
|
+
if (!HasAuthority()) return;
|
|
63
|
+
Health = FMath::Clamp(Health - Damage, 0.f, MaxHealth);
|
|
64
|
+
if (Health <= 0.f)
|
|
65
|
+
{
|
|
66
|
+
Die_Authority();
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
void AMyCharacter::ServerApplyHealing_Implementation(float Amount)
|
|
71
|
+
{
|
|
72
|
+
// Validate amount is positive and actor is alive
|
|
73
|
+
Health = FMath::Clamp(Health + Amount, 0.f, MaxHealth);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
bool AMyCharacter::ServerApplyHealing_Validate(float Amount)
|
|
77
|
+
{
|
|
78
|
+
return Amount > 0.f && Amount <= 500.f; // reject absurd values
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## Pattern 2: Team Data (Owner-Only Private + All-Clients Public)
|
|
85
|
+
|
|
86
|
+
Split information based on who needs it. Score is public; private currency is owner-only.
|
|
87
|
+
|
|
88
|
+
```cpp
|
|
89
|
+
// AMyPlayerState.h (PlayerState replicates to all clients)
|
|
90
|
+
UPROPERTY(ReplicatedUsing = OnRep_TeamId)
|
|
91
|
+
int32 TeamId;
|
|
92
|
+
|
|
93
|
+
UPROPERTY(Replicated)
|
|
94
|
+
int32 Score;
|
|
95
|
+
|
|
96
|
+
UPROPERTY(Replicated)
|
|
97
|
+
int32 Kills;
|
|
98
|
+
|
|
99
|
+
// Private to owning player only
|
|
100
|
+
UPROPERTY(ReplicatedUsing = OnRep_Currency)
|
|
101
|
+
int32 Currency;
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
```cpp
|
|
105
|
+
// AMyPlayerState.cpp
|
|
106
|
+
void AMyPlayerState::GetLifetimeReplicatedProps(
|
|
107
|
+
TArray<FLifetimeProperty>& OutLifetimeProps) const
|
|
108
|
+
{
|
|
109
|
+
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
|
|
110
|
+
|
|
111
|
+
DOREPLIFETIME(AMyPlayerState, TeamId);
|
|
112
|
+
DOREPLIFETIME(AMyPlayerState, Score);
|
|
113
|
+
DOREPLIFETIME(AMyPlayerState, Kills);
|
|
114
|
+
|
|
115
|
+
// Only the owning client receives their own currency
|
|
116
|
+
DOREPLIFETIME_CONDITION(AMyPlayerState, Currency, COND_OwnerOnly);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
void AMyPlayerState::OnRep_TeamId()
|
|
120
|
+
{
|
|
121
|
+
// Update team color, nameplate, etc. on all clients
|
|
122
|
+
OnTeamChanged.Broadcast(TeamId);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
void AMyPlayerState::OnRep_Currency()
|
|
126
|
+
{
|
|
127
|
+
// Update shop UI — only fires on the owning client
|
|
128
|
+
OnCurrencyChanged.Broadcast(Currency);
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## Pattern 3: Inventory Using FFastArraySerializer
|
|
135
|
+
|
|
136
|
+
Efficient replicated inventory — only changed items are sent over the network,
|
|
137
|
+
not the entire array.
|
|
138
|
+
|
|
139
|
+
```cpp
|
|
140
|
+
// InventoryTypes.h
|
|
141
|
+
USTRUCT(BlueprintType)
|
|
142
|
+
struct FInventoryItem : public FFastArraySerializerItem
|
|
143
|
+
{
|
|
144
|
+
GENERATED_BODY()
|
|
145
|
+
|
|
146
|
+
UPROPERTY()
|
|
147
|
+
int32 ItemId = 0;
|
|
148
|
+
|
|
149
|
+
UPROPERTY()
|
|
150
|
+
int32 Quantity = 0;
|
|
151
|
+
|
|
152
|
+
UPROPERTY()
|
|
153
|
+
float Durability = 1.0f;
|
|
154
|
+
|
|
155
|
+
void PreReplicatedRemove(const struct FInventoryList& InArraySerializer);
|
|
156
|
+
void PostReplicatedAdd(const struct FInventoryList& InArraySerializer);
|
|
157
|
+
void PostReplicatedChange(const struct FInventoryList& InArraySerializer);
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
USTRUCT()
|
|
161
|
+
struct FInventoryList : public FFastArraySerializer
|
|
162
|
+
{
|
|
163
|
+
GENERATED_BODY()
|
|
164
|
+
|
|
165
|
+
UPROPERTY()
|
|
166
|
+
TArray<FInventoryItem> Items;
|
|
167
|
+
|
|
168
|
+
bool NetDeltaSerialize(FNetDeltaSerializeInfo& DeltaParms)
|
|
169
|
+
{
|
|
170
|
+
return FFastArraySerializer::FastArrayDeltaSerialize<FInventoryItem, FInventoryList>(
|
|
171
|
+
Items, DeltaParms, *this);
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
template<>
|
|
176
|
+
struct TStructOpsTypeTraits<FInventoryList>
|
|
177
|
+
: public TStructOpsTypeTraitsBase2<FInventoryList>
|
|
178
|
+
{
|
|
179
|
+
enum { WithNetDeltaSerializer = true };
|
|
180
|
+
};
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
```cpp
|
|
184
|
+
// UMyInventoryComponent.h
|
|
185
|
+
UCLASS()
|
|
186
|
+
class UMyInventoryComponent : public UActorComponent
|
|
187
|
+
{
|
|
188
|
+
GENERATED_BODY()
|
|
189
|
+
|
|
190
|
+
public:
|
|
191
|
+
virtual void GetLifetimeReplicatedProps(
|
|
192
|
+
TArray<FLifetimeProperty>& OutLifetimeProps) const override;
|
|
193
|
+
|
|
194
|
+
// Only the owning player sees their own inventory
|
|
195
|
+
UPROPERTY(ReplicatedUsing = OnRep_Inventory)
|
|
196
|
+
FInventoryList Inventory;
|
|
197
|
+
|
|
198
|
+
UFUNCTION()
|
|
199
|
+
void OnRep_Inventory();
|
|
200
|
+
|
|
201
|
+
// Server-side mutation
|
|
202
|
+
void AddItem_Authority(int32 ItemId, int32 Quantity);
|
|
203
|
+
void RemoveItem_Authority(int32 ItemId, int32 Quantity);
|
|
204
|
+
};
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
```cpp
|
|
208
|
+
// UMyInventoryComponent.cpp
|
|
209
|
+
void UMyInventoryComponent::GetLifetimeReplicatedProps(
|
|
210
|
+
TArray<FLifetimeProperty>& OutLifetimeProps) const
|
|
211
|
+
{
|
|
212
|
+
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
|
|
213
|
+
DOREPLIFETIME_CONDITION(UMyInventoryComponent, Inventory, COND_OwnerOnly);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
void UMyInventoryComponent::AddItem_Authority(int32 ItemId, int32 Quantity)
|
|
217
|
+
{
|
|
218
|
+
if (!GetOwner()->HasAuthority()) return;
|
|
219
|
+
|
|
220
|
+
// Find existing stack
|
|
221
|
+
for (FInventoryItem& Item : Inventory.Items)
|
|
222
|
+
{
|
|
223
|
+
if (Item.ItemId == ItemId)
|
|
224
|
+
{
|
|
225
|
+
Item.Quantity += Quantity;
|
|
226
|
+
Inventory.MarkItemDirty(Item); // tell FastArray this item changed
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Add new stack
|
|
232
|
+
FInventoryItem& NewItem = Inventory.Items.AddDefaulted_GetRef();
|
|
233
|
+
NewItem.ItemId = ItemId;
|
|
234
|
+
NewItem.Quantity = Quantity;
|
|
235
|
+
Inventory.MarkItemDirty(NewItem);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Callbacks on client
|
|
239
|
+
void FInventoryItem::PostReplicatedAdd(const FInventoryList& InArraySerializer)
|
|
240
|
+
{
|
|
241
|
+
// New item appeared in inventory — update UI
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
void FInventoryItem::PostReplicatedChange(const FInventoryList& InArraySerializer)
|
|
245
|
+
{
|
|
246
|
+
// Existing item quantity or durability changed — update slot UI
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
void FInventoryItem::PreReplicatedRemove(const FInventoryList& InArraySerializer)
|
|
250
|
+
{
|
|
251
|
+
// Item is about to be removed — clear UI slot
|
|
252
|
+
}
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
---
|
|
256
|
+
|
|
257
|
+
## Pattern 4: Ability State (InitialOnly + Runtime Flags)
|
|
258
|
+
|
|
259
|
+
Ability unlocks are set once; cooldowns and charges update dynamically.
|
|
260
|
+
|
|
261
|
+
```cpp
|
|
262
|
+
// UMyAbilityComponent.h
|
|
263
|
+
// Which abilities are unlocked — sent once at spawn, never changes
|
|
264
|
+
UPROPERTY(Replicated)
|
|
265
|
+
TArray<int32> UnlockedAbilityIds;
|
|
266
|
+
|
|
267
|
+
// Current cooldown end times per ability slot
|
|
268
|
+
UPROPERTY(ReplicatedUsing = OnRep_Cooldowns)
|
|
269
|
+
TArray<float> CooldownEndTimes;
|
|
270
|
+
|
|
271
|
+
// Active ability flags bitmask
|
|
272
|
+
UPROPERTY(ReplicatedUsing = OnRep_ActiveFlags)
|
|
273
|
+
uint8 ActiveAbilityFlags;
|
|
274
|
+
|
|
275
|
+
UFUNCTION()
|
|
276
|
+
void OnRep_Cooldowns();
|
|
277
|
+
|
|
278
|
+
UFUNCTION()
|
|
279
|
+
void OnRep_ActiveFlags(uint8 PreviousFlags);
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
```cpp
|
|
283
|
+
// UMyAbilityComponent.cpp
|
|
284
|
+
void UMyAbilityComponent::GetLifetimeReplicatedProps(
|
|
285
|
+
TArray<FLifetimeProperty>& OutLifetimeProps) const
|
|
286
|
+
{
|
|
287
|
+
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
|
|
288
|
+
|
|
289
|
+
// Unlocked abilities: all clients see which abilities a player has
|
|
290
|
+
DOREPLIFETIME_CONDITION(UMyAbilityComponent, UnlockedAbilityIds, COND_InitialOnly);
|
|
291
|
+
|
|
292
|
+
// Cooldowns: only the owning client needs exact cooldown timers
|
|
293
|
+
DOREPLIFETIME_CONDITION(UMyAbilityComponent, CooldownEndTimes, COND_OwnerOnly);
|
|
294
|
+
|
|
295
|
+
// Active flags: all clients see which abilities are currently active (for animation)
|
|
296
|
+
DOREPLIFETIME(UMyAbilityComponent, ActiveAbilityFlags);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
void UMyAbilityComponent::OnRep_Cooldowns()
|
|
300
|
+
{
|
|
301
|
+
// Refresh cooldown bar UI for owning player only
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
void UMyAbilityComponent::OnRep_ActiveFlags(uint8 PreviousFlags)
|
|
305
|
+
{
|
|
306
|
+
// Determine which bits changed and trigger corresponding animations/effects
|
|
307
|
+
uint8 ChangedBits = ActiveAbilityFlags ^ PreviousFlags;
|
|
308
|
+
for (int32 i = 0; i < 8; ++i)
|
|
309
|
+
{
|
|
310
|
+
if (ChangedBits & (1 << i))
|
|
311
|
+
{
|
|
312
|
+
bool bNowActive = (ActiveAbilityFlags & (1 << i)) != 0;
|
|
313
|
+
OnAbilityActivationChanged.Broadcast(i, bNowActive);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
---
|
|
320
|
+
|
|
321
|
+
## Pattern 5: Replicated Object Subobject (UE 5.1+ API)
|
|
322
|
+
|
|
323
|
+
A `UObject` subobject with its own replicated properties, registered via the modern API.
|
|
324
|
+
|
|
325
|
+
```cpp
|
|
326
|
+
// UMyQuestData.h
|
|
327
|
+
UCLASS()
|
|
328
|
+
class UMyQuestData : public UObject
|
|
329
|
+
{
|
|
330
|
+
GENERATED_BODY()
|
|
331
|
+
|
|
332
|
+
public:
|
|
333
|
+
virtual void GetLifetimeReplicatedProps(
|
|
334
|
+
TArray<FLifetimeProperty>& OutLifetimeProps) const override;
|
|
335
|
+
|
|
336
|
+
virtual bool IsSupportedForNetworking() const override { return true; }
|
|
337
|
+
|
|
338
|
+
UPROPERTY(Replicated)
|
|
339
|
+
int32 QuestId;
|
|
340
|
+
|
|
341
|
+
UPROPERTY(Replicated)
|
|
342
|
+
int32 Progress;
|
|
343
|
+
|
|
344
|
+
UPROPERTY(Replicated)
|
|
345
|
+
bool bCompleted;
|
|
346
|
+
};
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
```cpp
|
|
350
|
+
// AMyPlayerController.cpp — register the subobject during BeginPlay on server
|
|
351
|
+
void AMyPlayerController::BeginPlay()
|
|
352
|
+
{
|
|
353
|
+
Super::BeginPlay();
|
|
354
|
+
if (HasAuthority())
|
|
355
|
+
{
|
|
356
|
+
ActiveQuest = NewObject<UMyQuestData>(this);
|
|
357
|
+
ActiveQuest->QuestId = 42;
|
|
358
|
+
// Register with COND_OwnerOnly so only this player's client receives it
|
|
359
|
+
AddReplicatedSubObject(ActiveQuest, COND_OwnerOnly);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
void AMyPlayerController::EndPlay(const EEndPlayReason::Type EndPlayReason)
|
|
364
|
+
{
|
|
365
|
+
if (HasAuthority() && ActiveQuest)
|
|
366
|
+
{
|
|
367
|
+
RemoveReplicatedSubObject(ActiveQuest);
|
|
368
|
+
}
|
|
369
|
+
Super::EndPlay(EndPlayReason);
|
|
370
|
+
}
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
```cpp
|
|
374
|
+
// UMyQuestData.cpp
|
|
375
|
+
void UMyQuestData::GetLifetimeReplicatedProps(
|
|
376
|
+
TArray<FLifetimeProperty>& OutLifetimeProps) const
|
|
377
|
+
{
|
|
378
|
+
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
|
|
379
|
+
DOREPLIFETIME(UMyQuestData, QuestId);
|
|
380
|
+
DOREPLIFETIME(UMyQuestData, Progress);
|
|
381
|
+
DOREPLIFETIME(UMyQuestData, bCompleted);
|
|
382
|
+
}
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
---
|
|
386
|
+
|
|
387
|
+
## Pattern 6: Replicated Actor Movement (FRepMovement)
|
|
388
|
+
|
|
389
|
+
For actors that move but do not use `UCharacterMovementComponent`, replicate movement
|
|
390
|
+
via `bReplicateMovement` and the built-in `FRepMovement` struct.
|
|
391
|
+
|
|
392
|
+
```cpp
|
|
393
|
+
// AMyVehicle.h
|
|
394
|
+
AMyVehicle()
|
|
395
|
+
{
|
|
396
|
+
bReplicates = true;
|
|
397
|
+
SetReplicateMovement(true); // enables FRepMovement replication via OnRep_ReplicatedMovement
|
|
398
|
+
SetNetUpdateFrequency(50.f);
|
|
399
|
+
NetPriority = 2.5f;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// Override to apply custom interpolation when movement data arrives on simulated proxies
|
|
403
|
+
virtual void OnRep_ReplicatedMovement() override;
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
```cpp
|
|
407
|
+
// AMyVehicle.cpp
|
|
408
|
+
void AMyVehicle::OnRep_ReplicatedMovement()
|
|
409
|
+
{
|
|
410
|
+
Super::OnRep_ReplicatedMovement(); // applies FRepMovement to root component
|
|
411
|
+
|
|
412
|
+
// Additional logic: apply physics state, snap wheels, etc.
|
|
413
|
+
SyncWheelPositions();
|
|
414
|
+
}
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
For physics-simulated actors, set `bRepPhysics = true` on the `FRepMovement` to
|
|
418
|
+
include linear and angular velocity, allowing clients to simulate physics correctly.
|
|
419
|
+
|
|
420
|
+
---
|
|
421
|
+
|
|
422
|
+
## Pattern 7: Custom Relevancy Override
|
|
423
|
+
|
|
424
|
+
Override `IsNetRelevantFor` to implement game-specific relevancy (team-based, zone-based, etc.).
|
|
425
|
+
|
|
426
|
+
```cpp
|
|
427
|
+
// AMyActor.h
|
|
428
|
+
virtual bool IsNetRelevantFor(
|
|
429
|
+
const AActor* RealViewer,
|
|
430
|
+
const AActor* ViewTarget,
|
|
431
|
+
const FVector& SrcLocation) const override;
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
```cpp
|
|
435
|
+
// AMyActor.cpp — only replicate to players on the same team
|
|
436
|
+
bool AMyActor::IsNetRelevantFor(
|
|
437
|
+
const AActor* RealViewer,
|
|
438
|
+
const AActor* ViewTarget,
|
|
439
|
+
const FVector& SrcLocation) const
|
|
440
|
+
{
|
|
441
|
+
// Always relevant to owner
|
|
442
|
+
if (RealViewer == GetOwner()) return true;
|
|
443
|
+
|
|
444
|
+
// Check team
|
|
445
|
+
const AMyPlayerController* PC = Cast<AMyPlayerController>(RealViewer);
|
|
446
|
+
if (PC && PC->GetTeamId() == TeamId)
|
|
447
|
+
{
|
|
448
|
+
// Also respect distance — call default distance check
|
|
449
|
+
return Super::IsNetRelevantFor(RealViewer, ViewTarget, SrcLocation);
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
return false;
|
|
453
|
+
}
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
---
|
|
457
|
+
|
|
458
|
+
## Pattern 8: Dormancy for Rarely-Updated Actors
|
|
459
|
+
|
|
460
|
+
Actors like pickups and map objectives update infrequently. Use dormancy to skip
|
|
461
|
+
replication checks when nothing changes.
|
|
462
|
+
|
|
463
|
+
```cpp
|
|
464
|
+
// AMyPickup.h
|
|
465
|
+
AMyPickup()
|
|
466
|
+
{
|
|
467
|
+
bReplicates = true;
|
|
468
|
+
NetDormancy = DORM_DormantAll; // start dormant
|
|
469
|
+
SetNetUpdateFrequency(1.f);
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
void PickupCollected(ACharacter* Collector);
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
```cpp
|
|
476
|
+
// AMyPickup.cpp
|
|
477
|
+
void AMyPickup::PickupCollected(ACharacter* Collector)
|
|
478
|
+
{
|
|
479
|
+
if (!HasAuthority()) return;
|
|
480
|
+
|
|
481
|
+
bPickedUp = true;
|
|
482
|
+
SetActorHiddenInGame(true);
|
|
483
|
+
SetActorEnableCollision(false);
|
|
484
|
+
|
|
485
|
+
// Wake up for one replication update to push bPickedUp to clients, then re-dormant
|
|
486
|
+
FlushNetDormancy();
|
|
487
|
+
|
|
488
|
+
// Schedule respawn
|
|
489
|
+
GetWorldTimerManager().SetTimer(
|
|
490
|
+
RespawnTimer, this, &AMyPickup::Respawn_Authority, RespawnTime, false);
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
void AMyPickup::Respawn_Authority()
|
|
494
|
+
{
|
|
495
|
+
bPickedUp = false;
|
|
496
|
+
SetActorHiddenInGame(false);
|
|
497
|
+
SetActorEnableCollision(true);
|
|
498
|
+
FlushNetDormancy(); // push respawn state, then return to dormant
|
|
499
|
+
}
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
---
|
|
503
|
+
|
|
504
|
+
## Bandwidth Estimation Guide
|
|
505
|
+
|
|
506
|
+
| Data Type | Approximate Bits per Update |
|
|
507
|
+
|---------------------------------|-----------------------------|
|
|
508
|
+
| `bool` (replicated) | 1 bit + overhead (~8 bits) |
|
|
509
|
+
| `uint8` | 8 bits |
|
|
510
|
+
| `int32` | 32 bits |
|
|
511
|
+
| `float` | 32 bits |
|
|
512
|
+
| `FVector` (full precision) | 96 bits |
|
|
513
|
+
| `FVector_NetQuantize` | ~30-48 bits |
|
|
514
|
+
| `FVector_NetQuantize10` | ~30-48 bits (1/10 cm) |
|
|
515
|
+
| `FRotator` (full) | 96 bits |
|
|
516
|
+
| `FRotator` compressed | ~24 bits |
|
|
517
|
+
| `FRepMovement` | ~100-200 bits |
|
|
518
|
+
| RPC call overhead | ~80-120 bits + params |
|
|
519
|
+
|
|
520
|
+
Use `FVector_NetQuantize` variants for world positions and directions to halve
|
|
521
|
+
bandwidth compared to raw `FVector`. The precision tiers are:
|
|
522
|
+
|
|
523
|
+
- `FVector_NetQuantize` — 1 cm precision
|
|
524
|
+
- `FVector_NetQuantize10` — 0.1 cm precision
|
|
525
|
+
- `FVector_NetQuantize100` — 0.01 cm precision
|
|
526
|
+
- `FVector_NetQuantizeNormal` — unit vector, ~16 bits
|