🎯 Lesson 10.4: Replicating a Gameplay Feature
Time to assemble everything. We'll build a complete, correctly-networked health pickup — from overlap to healing to every client's HUD updating — combining authority, replication, RepNotify, and RPCs. This is the pattern you'll reuse for every multiplayer feature you ever write.
🎯 Learning Objectives
By the end of this lesson, you will be able to:
- Plan a networked feature by deciding what's authoritative, replicated, and cosmetic
- Handle overlap on the server only and heal authoritatively
- Replicate the consumed state and destroy correctly for all clients
- Broadcast a cosmetic pickup effect to everyone
- Trace the full data flow across server and clients
Estimated Time: 75 minutes
Engine Version: Unreal Engine 5.8
In This Lesson
Planning the Feature
Before writing a networked feature, categorize each part. This planning step prevents almost every multiplayer bug:
| Part of the feature | Category | Tool |
|---|---|---|
| Detecting the overlap & deciding to heal | Authoritative | Server-only + HasAuthority() |
| The player's new health | Replicated state | ReplicatedUsing (RepNotify) |
| The pickup being consumed / gone | Replicated state | Replicated bool + Destroy() on server |
| Heal particle & sound | Cosmetic | NetMulticast RPC |
📖 The three-category test
Ask of every piece: is it authoritative (a real decision → server only), state that others must know (→ replicate), or cosmetic (→ multicast)? Getting these categories right up front is 90% of networking. The health pickup you built way back in Lesson 5.1 was single-player; now we network it by sorting its parts into these three buckets.
Server-Side Overlap
The overlap fires on all machines, but only the server should decide the heal. Guard it immediately.
// HealthPickup.cpp
AHealthPickup::AHealthPickup()
{
bReplicates = true; // networked actor (Lesson 10.2)
Collision = CreateDefaultSubobject<USphereComponent>(TEXT("Collision"));
RootComponent = Collision;
Collision->SetCollisionProfileName(TEXT("OverlapAllDynamic"));
}
void AHealthPickup::BeginPlay()
{
Super::BeginPlay();
// Bind overlap on all machines; we'll authority-guard inside the handler.
Collision->OnComponentBeginOverlap.AddDynamic(this, &AHealthPickup::OnBeginOverlap);
}
void AHealthPickup::OnBeginOverlap(UPrimitiveComponent* Comp, AActor* Other,
UPrimitiveComponent* OtherComp, int32 BodyIndex, bool bFromSweep, const FHitResult& Sweep)
{
// ONLY the server decides the pickup is consumed (Lesson 10.1).
if (!HasAuthority() || bConsumed) { return; }
if (!IsValid(Other) || Other == this) { return; }
// Authoritative heal (server-side, via the replicated health component).
if (UHealthComponent* HC = Other->FindComponentByClass<UHealthComponent>())
{
HC->Heal(HealAmount); // changes replicated CurrentHealth (Lesson 10.2)
bConsumed = true; // replicated state (next section)
MulticastPlayPickupFX(); // cosmetic for everyone (Lesson 10.3)
SetLifeSpan(0.1f); // let the multicast reach clients, then destroy
}
}
⚠️ Why SetLifeSpan instead of instant Destroy()?
If the server Destroy()s the pickup the same frame, the MulticastPlayPickupFX RPC may not reach clients before the actor is gone (RPCs on a destroyed actor are dropped). A tiny SetLifeSpan (Lesson 3.4) gives the multicast a moment to propagate, then the actor is destroyed on the server and that destruction replicates to clients. This ordering subtlety is exactly the kind of thing that "works in Standalone" hides.
Replicated Consumed State
bConsumed is replicated so late-joining or lagging clients don't see an already-taken pickup as still available, and so we can hide it consistently.
// HealthPickup.h
UPROPERTY(ReplicatedUsing = OnRep_Consumed)
bool bConsumed = false;
UFUNCTION()
void OnRep_Consumed();
// HealthPickup.cpp
void AHealthPickup::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& Out) const
{
Super::GetLifetimeReplicatedProps(Out);
DOREPLIFETIME(AHealthPickup, bConsumed); // register (Lesson 10.2)
}
// Runs on clients when bConsumed replicates true — hide it immediately
// (before the Destroy replication arrives, for a snappy feel).
void AHealthPickup::OnRep_Consumed()
{
if (bConsumed)
{
Collision->SetVisibility(false);
SetActorEnableCollision(false);
}
}
✅ Belt and suspenders
The actor's Destroy() (via lifespan) already removes it everywhere. Replicating bConsumed and hiding in OnRep_Consumed makes the pickup appear taken instantly on clients, without waiting for the destroy to propagate — smoother feel, and correct for anyone whose destroy message is delayed. This is the health/door replication pattern from Lesson 10.2 applied again.
Cosmetic Multicast
The heal sparkle and sound should play on every machine — a textbook NetMulticast (Lesson 10.3), Unreliable because it's purely cosmetic.
// HealthPickup.h
UFUNCTION(NetMulticast, Unreliable)
void MulticastPlayPickupFX();
// HealthPickup.cpp — runs on server + all clients.
void AHealthPickup::MulticastPlayPickupFX_Implementation()
{
// Spawn the particle and play the sound at our location — everyone sees it.
// UGameplayStatics::SpawnEmitterAtLocation(...); PlaySoundAtLocation(...);
UE_LOG(LogTemp, Log, TEXT("Pickup FX played on this machine"));
}
📖 Why multicast the FX but replicate the state?
The heal amount and bConsumed are state everyone must agree on — replicated. The sparkle is a transient cosmetic that just needs to play once everywhere — multicast. Mixing these up (multicasting state, or replicating a one-shot effect) is the classic design error the categorization step (planning section) prevents.
The Complete Flow
Here is the entire feature, every machine accounted for:
(fires on all machines)"] --> A{"HasAuthority?"} A -->|"No (client)"| Skip["Ignore — wait for server"] A -->|"Yes (server)"| Heal["Server: Heal() → CurrentHealth changes"] Heal --> Rep1["CurrentHealth replicates → clients' HUD updates (10.2 + 9.2)"] Heal --> Cons["Server: bConsumed = true"] Cons --> Rep2["OnRep_Consumed on clients → hide pickup"] Heal --> MC["Server: MulticastPlayPickupFX"] MC --> FX["Sparkle + sound on server + all clients"] Heal --> Life["SetLifeSpan → Destroy replicates to all"]
✅ You just networked the whole course
This one feature uses collision (5.1), FindComponentByClass (3.5), the health component (3.3), SetLifeSpan/Destroy (3.4), authority (10.1), replication + RepNotify (10.2), and a Multicast RPC (10.3) — and the health replication drives the HUD from Module 9. Every module you've built now cooperates across the network. Multiplayer isn't a separate skill bolted on; it's the same patterns, categorized into authoritative / replicated / cosmetic.
Hands-on Exercise & Quiz
🏋️ Exercise: Network a score pickup
Objective: Apply the three-category plan to a new feature.
- A coin pickup grants points to the overlapping player's
PlayerStateand plays a chime everywhere. - For each part — overlap decision, the score value, the coin disappearing, the chime — assign a category (authoritative / replicated / cosmetic) and the tool.
- Where does the score live so it survives the player's pawn dying (recall Lesson 3.1)?
- Write the authority-guard line that belongs at the top of the overlap handler.
✅ Answers
- Overlap decision → authoritative (server +
HasAuthority()); score value → replicated property onPlayerState; coin gone → serverDestroy()(+ optional replicatedbConsumed); chime →NetMulticast, Unreliable. - On the PlayerState — it's replicated and outlives the pawn (Lesson 3.1).
if (!HasAuthority() || bConsumed) { return; }
🎯 Quick Quiz
Question 1: The overlap handler runs on all machines. What guards the actual heal?
Question 2: The heal sparkle effect should be sent via:
Question 3: Why SetLifeSpan instead of instant Destroy() after multicasting?
Summary
🎉 Key Takeaways
- Plan every networked feature by categorizing parts as authoritative (server), replicated state, or cosmetic (multicast).
- Overlap/decisions run on the server (
HasAuthority()); the heal goes through the replicated health component. - Replicate
bConsumedwith RepNotify to hide the pickup instantly on clients; useSetLifeSpanso a multicast can land beforeDestroy. - Send the sparkle/sound via a
NetMulticast, UnreliableRPC — cosmetic, everyone, drop-tolerant. - The feature composes the entire course across the network — multiplayer is the same patterns, correctly categorized.
📚 Additional Resources
🚀 What's Next?
Module 10 is complete — you can build correct multiplayer gameplay in C++. Module 11 tackles making it all run fast: async & multithreading, and profiling with Unreal Insights.
🎉 Module 10 complete!
Your gameplay works in multiplayer. Let's make it fast.