Standalone Project Multiplayer & Systems Design

Co-Op Objectives

This project demonstrates a listen-server co-op prototype. One player hosts, others browse a server list and drop in. Tombs across the map wake when you step inside their radius and begin spitting out enemies on a loop - and they never stop until someone lands a grenade in the mouth of the crypt. Nobody owns the objective. Whoever throws it, everybody's counter moves.

⚙ Unreal Engine 5
🌐 Listen Server
🎯 Objective Design
⚔ Gameplay Ability System
🌐 What This Project Is
A playable drop-in 4 player co-op session prototype built end to end: main menu, session hosting, a browsable server list, and a shared objective loop that any player in the session can advance. It runs on a listen server - the host is also a player - so every system had to be written twice-minded: once for the authority that owns the truth, and once for the client that only ever sees a copy of it.
Host and client clearing a tomb together - activation, spawn loop, grenade, shared counter.
Why Build This
Almost everything else in this journal is single-player systems work. I've build this project to close that gap deliberately. Multiplayer forces a different discipline: you cannot cheat with local state, you cannot assume the thing you're looking at is the thing that's actually true, and every design decision have multiple questions that needs to be answered. Who decides the tomb is dead? Who decides the enemy took damage? The answers shape the design, not just the code.
Multiplayer Design Replication Session Management Objective Design Gameplay Ability System Blueprints
🎯 The Three Design Goals
🚪
Frictionless entry
Menu → server list → in the fight. No lobby, no ready-up, no negotiation. A player who joins late is immediately useful, because the tombs are still spawning.
🤝
Shared credit, not split credit
One counter for the whole session. Whoever throws the grenade, everybody's objective advances. Nobody is farming their own progress bar.
Pressure that doesn't wait
A woken tomb spawns forever. The enemy stream is the timer. You can't grind the encounter down - you have to solve it at the source.
🚪 Menu to Firefight
The session layer is the least glamorous part of the project and the part that took the longest to get right. It's the difference between "a multiplayer level" and "a game two people can actually play together."
1
Main Menu
Two doors: Host or Find Session. Hosting creates a session and server-travels the host into the map. There is no lobby stage - the host is playing within a few seconds of clicking.
2
Server List
A search populates a scrollable list of open sessions, each row showing the host name and current player count. Selecting a row joins that session directly. The list is a UMG widget bound to the search results delegate, so it fills in as results arrive rather than blocking on a full search.
3
Client Travel & Drop-In
The joining client travels into the host's live map. Crucially, the world is already running - tombs may already be awake, enemies may already be mid-fight, and the objective counter may already be partially filled. The new arrival inherits the session's current state via replication rather than starting a fresh one.
4
Shared Objective HUD
Every player sees the same tombs-remaining counter, sourced from the same replicated value. There is no per-player scoreboard, because there is no per-player objective.
Host
Host / Find Session.
Client
Open sessions with host name and player count.
Why Listen Server
A dedicated server would have been architecturally cleaner - no host is also a player, so there's no privileged client. But for a small drop-in co-op prototype, listen server is the honest choice: it's what a session-based co-op game of this scale actually ships with, it requires no hosting infrastructure, and it forces me to confront the awkward case that a dedicated server hides - the fact that the host's client and the server are the same machine, and it's very easy to write logic that accidentally only works for them.
⚠ The listen-server trap
The single most common bug in this project: something works perfectly when you test it as the host, then does nothing on the client. Nearly every time, the cause was the same - logic running on the wrong side. On a listen server the host has authority, so anything you write naively "just works" for them. The client is the only real test. I ended up doing all my first-pass testing as the client, precisely because it's the side that tells the truth.
⚰ The Tomb
The tomb is the whole objective system compressed into a single actor. It holds its own state, runs its own spawn loop, and reports its own death. There is no central objective manager - the objective is the actor, and the world's remaining count is just a tally of how many are still alive.
🔄 The Lifecycle
1
Dormant
The tomb sits inert. It has a sphere trigger - its activation radius - and it does nothing at all until a player crosses it. This means the map can be approached in any order; the players decide which fight they're picking, and when.
2
Activation
A player entering the radius wakes the tomb. Activation is checked on the server only - the client's overlap is not trusted - and the resulting awake state is replicated out so every player sees the tomb light up at the same moment.
3
The Spawn Loop
Once awake, a looping timer fires every X seconds and spawns an enemy at the tomb's mouth, up to a configured live cap. This continues indefinitely. There is no wave count, no "clear the last enemy and it stops." The tomb is a faucet, and the only fix is turning it off.
4
Destruction
A grenade thrown into the tomb destroys it. On detonation the server validates the hit, clears the spawn timer, marks the tomb dead, and decrements the shared counter. Any player can do this - host or client, it's the same code path, because the server is the one running it either way.
5
Session Complete
When the last tomb dies, the counter hits zero and the objective is complete for everyone simultaneously.
🎛 The Tuning Fields
The tomb is designed to be re-tuned entirely from its details panel, without touching the Blueprint graph. Every field below is exposed and per-instance, so two tombs in the same map can behave completely differently.
Field Owner What It Controls
Activation Radius Server How close a player must get before the tomb wakes. Wide radius = the fight starts before you're ready. Tight radius = the players choose the engagement.
Spawn Interval Server Seconds between spawns. The single most important pacing dial in the project - it defines how long the players can afford to spend not solving the tomb.
Live Enemy Cap Server Ceiling on simultaneous enemies from this tomb. Stops an ignored tomb from turning into an unwinnable and unshippable performance disaster.
Enemy Type Server Which enemy class this tomb produces. Lets one tomb pressure with numbers and another pressure with threat.
Is Active / Is Destroyed Replicated The tomb's state, replicated to all clients so the visual and audio feedback fire in sync for everyone.
Objective Radius
Objective is added to the player when entering the radius
Objective Destroyed
The tomb is destroyed when the player throws the grenade
Objective's Design Decision
The tomb could have been destroyed by shooting it. But, I did not want to sacrifice the fast-paced action that will be happening with the enemy spawns and change the players attention to the tombs.
Hence, The design decision to destroy the tombs with the grenades while the players can continue to engage with the spawned enemies.
⚔ The Enemies
The enemies here are deliberately simple. They spawn, they find the nearest player, they close, they attack. This is not the layered State Tree AI from Project Whimsical, and it isn't trying to be - the interesting problem in this project lives in the networking and the objective, and a complex AI would have been noise. The enemy's job is to generate pressure, not to be a puzzle.
📍
Nearest-player targeting
On spawn and on a re-evaluation tick, the enemy scans the player array and picks the closest. Simple, but it produces the emergent behaviour that matters in co-op: the crowd naturally splits between players.
🧭
Close and attack
Navigate to the target, attack in range, re-evaluate if the target dies or another player gets closer. No patrol, no search, no investigate - the tomb already told them where you are.
🖥
Server-side brain
All AI logic runs on the server. Clients only ever see the replicated movement and the cosmetic results of abilities - they never make a decision on the enemy's behalf.
The Split Emerges For Free
Nearest-player targeting was chosen for its simplicity, but it turned out to carry real design weight. Because enemies continuously re-evaluate, a player who steps away from the group to throw a grenade starts pulling aggro toward themselves - and a player who steps forward to hold the line naturally soaks it. The AI didn't need a threat system or a taunt mechanic to produce the classic tank-and-objective dynamic. It fell out of proximity alone. That's the cheapest design win in the whole project.
🧬 Combat via GAS
Health, damage, and death all run through the Gameplay Ability System. This was not the shortest path - a replicated float and a damage event would have worked for a prototype this size - but it is the correct one, because GAS was built for the prediction and authority problems that multiplayer creates.
Element Runs On Detail
Attribute Set Replicated Health / Max Health on both players and enemies. The Attribute Set replicates natively, so the health bar above an enemy's head is correct on every machine without a single custom RepNotify.
Damage Server Applied as a Gameplay Effect. The server is the only thing that ever mutates an attribute - a client cannot decide it dealt damage, it can only ask.
Abilities Predicted Player attacks and the grenade throw are Gameplay Abilities. The local client plays the animation immediately for responsiveness; the server confirms the outcome.
Death Server Health reaching zero is detected server-side, which then triggers the death state and cleans up the actor. Clients react to the replicated result.
Cosmetic FX Client Hit reactions, impact VFX, and audio are fired via Gameplay Cues, which multicast - every player sees the hit, but no player's machine decides whether it happened.
AI Changing the target
Nearest-player targeting producing an unscripted division of the crowd.
📡 Who Owns What
This is the tab that would not exist if this were a single-player project. Every system above had to answer one question before a single node was placed: who is allowed to be right?
The Rule I Followed
Client requests Server decides All observe
A client never changes state. It asks for a change, and then it watches the result arrive.
🔁 The Grenade, Traced End to End
The clearest example is the objective verb itself. Here is the full round trip when a client - not the host - destroys a tomb.
1
Input (Client)
The client presses throw. The Gameplay Ability activates locally and plays the animation immediately - this is the only thing the client is allowed to do unilaterally, and it exists purely so the throw feels instant rather than laggy.
2
Request (Client → Server)
A Server RPC carries the throw request and its parameters. The client is now finished making decisions.
3
Spawn & Simulate (Server)
The server spawns the grenade as a replicated actor. It simulates on the server; clients receive its movement. There is exactly one grenade in the world, and it belongs to the server.
4
Detonation & Validation (Server)
The server evaluates the blast. If a tomb is in range and awake, the server sets Is Destroyed, clears the spawn timer, and decrements the shared counter. This is the moment the objective actually advances, and it happens in exactly one place regardless of who threw.
5
Broadcast (Server → All)
The destroyed state replicates, and a Multicast fires the explosion VFX, the camera shake, and the audio. The updated counter replicates to every HUD. The thrower and the bystander get the confirmation on the same tick.
Where the Objective State Actually Lives
Each tomb owns its own state - there's no separate objective manager holding a list of things it doesn't control. The advantage is that a tomb is a self-contained, drag-and-droppable objective: place five in a level and you have a five-tomb map with no other setup. The shared counter lives at session level, replicated to everyone, and every tomb decrements it on death. Objective state is a tally of actor state, not a duplicate of it.
⚠ The honest cost of that decision
Putting the state in the actor was the fastest way to a working prototype, but it does not scale to objective types. Right now the session only knows how to count one thing: dead tombs. If I wanted a map that mixed "destroy the tombs" with "escort the cart" and "hold the ground for 60 seconds," the counter would have to become something a lot smarter than an integer, and I would need the objective manager I currently don't have. That's the first thing I'd rebuild.
Grenade Blueprint
Grenade is triggered on multicast level.
🎨 The Design Argument
Co-op is not single-player with a second body in it. A co-op encounter only earns the name if it produces something two players can do that one player cannot. That was the design brief I held this project to, and it's the reason each of the choices below looks the way it does.
Split the verbs
Fighting and objective-solving use different actions. If both were "shoot the thing," two players would simply shoot it twice as fast. Because the grenade is a commitment, one player must cover the other.
Make the pressure infinite
Finite waves let a competent team just grind. An endless spawn loop removes attrition as a strategy and forces the players toward the actual solution - the tomb.
📊
One counter, no scoreboard
Shared progress removes the incentive to compete for kills. There is no personal best here. There's a tomb, and it's still spawning.
🚪
Latecomers are welcome
Because the world is a live, replicated state rather than a script the host started, dropping in mid-session is not an edge case. It's just a player arriving.
The Activation Radius Is the Difficulty Curve
The single most powerful design lever in the level isn't the spawn interval - it's where the tombs are and how far their radii reach. Two tombs with overlapping radii mean one careless step wakes both, and now two faucets are running while the players have only one grenade window at a time. Two tombs placed far apart mean a team can choose to split up and solve them in parallel, gambling that each player can survive alone. Neither of those encounters needed new code. They're both authored purely by placement, which is exactly what a designer-facing system should let you do.
Tuning Levers
The entire encounter feel is re-tunable from the tomb's exposed fields. Spawn Interval is the aggression dial - it defines how much time the players are given to not be solving the objective. Live Enemy Cap is the safety rail, keeping an ignored tomb from becoming unwinnable. Activation Radius decides whether the players choose the fight or the fight chooses them. And Enemy Type lets one tomb pressure with numbers and another pressure with threat, without any new logic.
Reference Study
The tomb owes an obvious debt to the Nests of Left 4 Dead's director and, more directly, to the Breeder Tumors of Gears of War and the Hive/nest objectives that recur across co-op shooters - a source of infinite enemies that must be closed rather than outfought. The drop-in session model is the one used by session-based co-op broadly: browse, join, play, leave, with no lobby ceremony in between.
📚 What I Learned
This was my first project built network-first rather than networked afterwards. The lessons were mostly about discipline.
🖥
Test as the client, always
The host lies to you. On a listen server, sloppy logic works fine for the host and silently fails for everyone else. Making the client my default test seat caught more bugs than any other single habit.
🔒
Authority is a design decision
"Who owns this?" is not an engineering afterthought - it changes what the mechanic is. Deciding the server owns the counter is what made shared credit possible in the first place.
🧬
GAS earns its overhead in MP
In single-player, GAS often feels like ceremony for what a float could do. In multiplayer, the prediction and authority model it hands you for free is the reason to use it.
🎯
Simple AI, good encounter
Nearest-player targeting is about as basic as AI gets, and it produced a genuine tank-and-objective split with zero threat logic. Encounter design did the work the AI didn't have to.
🚫 Things I Do Not Like
Honest critiques - the parts I'd rebuild given another pass.
⚠ The objective system doesn't generalise
The tomb owns its own state, and the session just counts dead tombs. That's clean for this objective and useless for any other. There is currently no way to express "hold this point" or "escort this thing" without writing a parallel system from scratch. A proper objective interface - with a manager that tracks heterogeneous objectives - is what this needs, and I built the shortcut instead.
⚠ Enemy variety is a single archetype
Every enemy runs the same behaviour: find nearest, close, attack. That's enough to create pressure, but not enough to create decisions. A ranged enemy would punish players who stand still to throw; a shielded enemy would force the team to actually co-ordinate rather than just point in the same direction. Right now the enemies are a tax, not a puzzle.
⚠ No downed / revive state
The most obvious missing co-op verb. Without a downed state, a player death has no cost that the other player has to respond to - and the entire genre's most reliable co-op moment ("I'll get you, cover me") isn't available. This is the biggest single design gap and the first thing on the list below.
🧭 Next Steps
1
Downed & revive
Add a downed state with a bleed-out timer and a revive interaction. This turns a teammate's death into a decision for the survivor - abandon the grenade run and go get them, or finish the tomb and hope the timer holds. That trade is the co-op fantasy.
2
A real objective interface
Replace the tomb-counts-as-objective shortcut with an objective interface and a session-level manager, so a map can mix destruction, holding, and escort objectives without new bespoke systems.
3
Enemy archetypes
Introduce a ranged and a shielded enemy so the crowd asks a question rather than just applying weight - and so the players have a reason to talk to each other.
4
Player-count scaling
Scale spawn interval and enemy cap against the live player count, so a tomb woken by a solo player and a tomb woken by four are the same encounter, not the same numbers.