You want to walk around the same world with a friend. When one of you presses a button, the door should open on the other person's screen too. Multiplayer sounds intimidating, but UE has multiplayer built in from the start, and Blueprint alone gets you to "two people seeing the same thing."
That said, approaching it with single-player instincts will trip you up every time. You hit the wall of "it works on my screen but doesn't reach my friend." That wall is really a gap in understanding who owns the game state. This article covers the basic multiplayer model and walks through building a shared door, including how to fix that classic bug. We won't touch advanced online optimization; we'll take our time getting to "two people see the same thing."
What You'll Learn
- The model where the server is authoritative and clients just display
- Testing two-player with two PIE windows
- Distributing values to everyone with Replicated variables and RepNotify
- Who's executing what: Has Authority and Server RPCs
- Hands-on: a button two players can press and a door they share
- How to fix the classic "it only moves on my screen" bug
The Server Is Authoritative, Clients Just Display
The first mental switch in multiplayer is this one. The real game state lives on the server, and clients are only showing a reflection of it.
Whether the door is open, how much health the enemy has: those "real values" are on the server. Each player's screen (the client) is only displaying values the server sent. If a client changes a value on its own, that's a lie visible only on that screen, and nobody else sees it.

The easiest setup for testing is a Listen Server, where one of the players also acts as the server. You don't need a dedicated server machine; one person hosts and the other joins.
Once this model clicks, most multiplayer bugs start looking like the same single cause: a value was changed on the client without going through the server.
Testing with Two PIE Windows
You can test two-player right inside the editor. There are two settings.
- Number of Players: how many players launch at once. Set it to 2.
- Net Mode: what configuration to launch in. Set it to Play As Listen Server.

Configure these in the menu next to the Play button (the three-dot menu) and hit Play, and two windows launch. One is the server and a player (the host), the other is a client. Put them side by side and develop while watching whether what happens in one shows up in the other.
For multiplayer, this "compare two windows" workflow is the basic verification method. Build with only one window open and you won't notice things failing to sync.
Syncing Variables: Replicated and RepNotify
Replication is the mechanism that distributes server-owned values to all clients.
First, make the actor itself eligible for syncing. Check Replicates in Class Defaults. For actors that move (characters and the like), also check Replicate Movement to sync their position automatically.
Next, configure each variable you want distributed. Pick Replication in the variable's details.
- Replicated: whenever the value changes, it's automatically distributed to all clients.
- RepNotify: on top of being distributed, a designated function (
OnRep_VariableName) is called on the client side.

RepNotify is handy for anything where "when the value changes, the visuals should update too," like a health bar. OnRep_Health is called on each client the instant the value arrives, so redrawing the bar there keeps everyone's screen in sync.
The important part is that only the server may change a Replicated variable. Change it on a client and nothing gets distributed.
Who's Executing: Authority and RPCs
In multiplayer Blueprints, the same graph runs on both the server and clients. So you have to stay aware of which side is running right now.
Has Authority (or Switch Has Authority) tells you. Having Authority means the server; not having it means a client. When you want something to run only on the server, branch on Has Authority.
So how do you get an input that happened on a client (a button press) to the server? That's what RPCs (Remote Procedure Calls) are for. An RPC is a way to have a function executed on the other side, and there are three kinds.
| Kind | Where it runs | Purpose |
|---|---|---|
| Server | On the server | Sends client input to the server |
| Client | On one specific client | Notifies just that player from the server |
| Multicast | On the server and all clients | Shows effects or sounds to everyone |

RPCs also come in Reliable / Unreliable flavors. Reliable means "guaranteed to arrive"; Unreliable means "may get dropped, but it's cheap." Make important communication you can't afford to lose, like opening a door, Reliable. Use Unreliable for light information sent every frame.
🚨 Call Server RPCs on an Actor You Own
This is the condition everyone trips over first. You can't call a Server RPC from just any Actor.

A Server RPC from a client only reaches the server when the Actor you called it on is owned by that client. Call it on an Actor with no owner and it's silently discarded — no error, nothing.
| Called on | Owner | Does the Server RPC arrive? |
|---|---|---|
| Your own Character / PlayerController | That client | ⭕ Yes |
| A door or switch placed in the level | Nobody | ❌ Discarded |
| Another player's Character | A different client | ❌ Discarded |
Which means creating Server_OpenDoor inside the door's Blueprint won't work. You have to route it through the character you're controlling.
When the symptom is "nothing happens, and there's no error either," check this first. The hands-on in this article is built to satisfy this condition from the start.
The basic flow is this. The client asks the server via a Server RPC on its own character → the server changes a Replicated variable → the change is distributed to everyone. Keep to that one-way street and syncing behaves.
Hands-On: A Door Two Players Share
Let's build a concrete door that two players share.
A switch in a co-op puzzle, a shared door in a sandbox, a gimmick in an online co-op action game. "One player presses it and it opens on both screens" has the same structure in all of them. Here we'll build a case where the player steps on a button and the door opens on both screens.
Here's what it looks like running. Step on the button in either of the two windows and the door opens in both. Conversely, write the variable directly without going through a Server RPC and it only opens for the person who pressed it. That contrast is how you check your understanding of the model.

Setup
We'll use two Blueprints. Because of the ownership condition from the previous section, the RPC lives on the player's character.
BP_SharedDoor (the door's Actor Blueprint):
| Item | Setting |
|---|---|
| Class Defaults | Replicates = true |
Variable bDoorOpen | Type Boolean, Replication = RepNotify, default false |
| Button | Box Collision (reacts on touch) |
DoorMesh | Static Mesh (a Cube scaled into a thin door). Opening is expressed as this mesh's relative rotation |
Function ApplyDoorState | Reads bDoorOpen and sets DoorMesh's relative rotation (the only place visuals update) |
Function OpenDoor | On the server, sets bDoorOpen = true and calls ApplyDoorState |
BP_PlayerCharacter (the player's character):
| Item | Setting |
|---|---|
Custom event Server_RequestOpenDoor | Replicates = Run on Server, Reliable on. Input Door (type BP_SharedDoor) |
Setting bDoorOpen to RepNotify automatically creates the OnRep_bDoorOpen function.
Why funnel visuals through
ApplyDoorState.OnRep_bDoorOpenisn't necessarily called on the server itself (the server wrote the value, so it needs no notification). Calling the same function from both "right after writing the value" and "OnRep" keeps the visuals consistent on every machine. Rather than memorizing "OnRep is client-only," just adopt this shape.
Building the Graph

Here's how it's wired.
- On
BP_SharedDoor's On Component Begin Overlap (touching the button), runOther ActorthroughCast To BP_PlayerCharacter - Check that character with
Is Locally Controlledin aBranch(only send for the character you're controlling. Skip this and you'll also try to send for other players' characters, and those get discarded) - On True, call that character's
Server_RequestOpenDoor(Door = the door itself). The character is yours, so this reaches the server - Inside
Server_RequestOpenDoor(running on the server), check thatDoorpassesIs Valid, and that the distance to the door is close enough - If both hold, call the door's
OpenDoor, which setsbDoorOpen = trueand runsApplyDoorState - Since
bDoorOpenis RepNotify, the value is distributed to all clients and each one'sOnRep_bDoorOpencallsApplyDoorState
Read out as pseudocode, it looks like this.
BP_SharedDoor
On Component Begin Overlap (Button)
→ chara = Cast To BP_PlayerCharacter(Other Actor)
→ Branch (chara.IsLocallyControlled) // only your own character
True → chara.Server_RequestOpenDoor(Door = self)
OpenDoor(): // only ever called on the server
bDoorOpen = true // only the server writes Replicated variables
ApplyDoorState() // update the server's own visuals too
OnRep_bDoorOpen(): // called on clients when the value arrives
ApplyDoorState()
ApplyDoorState(): // the single place visuals update
Set Relative Rotation(
Target = DoorMesh,
New Rotation = bDoorOpen ? (0, 0, 90) : (0, 0, 0))
BP_PlayerCharacter
Server_RequestOpenDoor(Door) // Run on Server / Reliable
→ if Is Valid(Door) is false, do nothing
→ distance check: (Door.GetActorLocation() - GetActorLocation()).Length() < 300.0
→ Door.OpenDoor()
Checking the distance on the server is the point. Not "the client asked, so open it" but the server confirming the conditions itself before opening. That one extra step is your first line of defense against cheating.
Verifying
Play with Number of Players = 2 and Net Mode = Listen Server. Move a player onto the button in one window, and success means the door opens in both windows. Stepping on it from the other window does the same.
Troubleshooting:
- It only opens on the side that pressed it → You're writing
bDoorOpendirectly from Overlap without a Server RPC. Values written on a client aren't distributed - Nothing happens at all, and there's no error → You're failing the ownership condition. Did you create
Server_RequestOpenDooron the door (BP_SharedDoor)? The RPC goes on the character - It doesn't open on the server side (the first window) → You forgot to call
ApplyDoorStateinsideOpenDoor.OnRepisn't necessarily called on the server - It doesn't open on the client side → The actor's
Replicatesis false, or the variable's Replication isn't set
Two things to take away.
- Call Server RPCs on something you own: doors and switches placed in the level have no owner, so a Server RPC placed there is silently discarded. Always route input through your own Character or PlayerController
- Funnel visual updates into one place: create
ApplyDoorStateand call it both right after the server writes the value and from the client'sOnRep. With the same function running on every machine, you never get the one-side-only bug
Calculations that decide outcomes, like damage, follow the same idea: damage should be calculated on the server. Clients only receive the result and display it.
Bonus: Good to Know for Later
The Game Framework is the foundation for server/client roles. Which classes exist once on the server and which exist per player is decided by GameMode / GameState / PlayerState. Properties like "GameMode only exists on the server" carry straight over into multiplayer.
A Listen Server is plenty to start. Dedicated servers are something to consider once your scale grows. At the solo-development entry point, learning the syncing model with one player hosting comes first.
Steam integration and matchmaking are a separate topic. Connecting to a friend over the internet requires an Online Subsystem (like Steam) and session management. That's a different layer from Replication, so finish getting syncing right with two windows on one PC first, then tackle it as the next step.
Summary
Multiplayer rests on a single principle. The server owns the state, and clients display it. So client input goes to the server via a Server RPC, the server writes a Replicated variable, and RepNotify distributes it to everyone. Stick to that one-way street and "two people see the same thing" comes together cleanly.
What's the first thing you want two players to share in your game? A door, a score, anything: put it on this one-way street and it will line up on everyone's screen.