[UE5] Multiplayer Basics: A Shared Door for Two Players with RPC and RepNotify

Created: 2026-07-20Last updated: 2026-09-07

The door opened on your screen but stayed shut on theirs. Sorts UE5's RPC and Replication into request, decision, and shared state. Builds a door that rises in Blueprint, shows how to verify it on two screens, and diagrams ownership and RepNotify pitfalls.

The door opened on your screen but stayed shut on your friend's. Even in the same game, simply moving a mesh the way you would in single player does not necessarily change their screen.

In multiplayer, the game runs in each player's environment. Between them you exchange the request "please open" and the state "it is currently open" to align the worlds people see.

This article builds a door that rises when you step into a zone in front of it. The player asks, the server decides, and the resulting state is shared. Let's confirm that flow in Blueprint across two game windows.

One player steps on the marker and the same door opens on both the host and client screens

What You'll Learn

  • How work is divided between server and clients
  • The difference between requests via RPC and shared state via Replication
  • Opening a shared door by going through your own Character
  • Testing on two screens and investigating how far the logic reached

Sponsored

The same world runs in each environment

The server decides shared game state such as door open/closed and enemy HP. Clients connect and play. They take input, send the requests they need, and reflect arriving state on their screens.

Server and clients each have a door Actor. Changing one Actor's variable does not directly rewrite the Actor in another environment. A mechanism carrying state over the network is required.

Door Actors exist in the server's and client's worlds, sharing the bDoorOpen state

Clients also process cameras, UI, and input-driven motion. For a shared door, rather than treating one person's view as correct, gather the decision about whether it may open on the server and things stay organized.

We test with a Listen Server , where one participant is both server and player. That person is the host. The rest connect as clients.

First, walk around on two screens

Use the Blueprint Third Person template with the standard BP_ThirdPersonCharacter . If you already use your own Character, substitute its name in the Cast later.

UE's PIE (Play In Editor) runs the game from the editor. Set the following in the menu beside Play. If entries are missing, open Multiplayer Options from "Advanced Settings".

EntrySettingMeaning
Play display modeNew Editor Window (PIE)Opens the game in a separate window
Number of Players2Launches two instances of the game
Net ModePlay As Listen ServerMakes one the host and the other a client
Setting Number of Players to 2 and Net Mode to Play As Listen Server to launch two windows

Place two Player Starts in the level without overlapping. Play and the host and client windows open. Distinguish them by the Server / Client labels in the window titles.

Click one, move that character, and confirm the same character moves in the other. Shift + F1 returns mouse control to the editor, making the other window easier to select.

Getting this far means the two games are connected. Template character movement is synced by UE's own mechanism, but the door we build decides its shared state ourselves.

RPC sends requests; Replication sends state

For the door, split two roles.

What you conveyMechanism usedDoor example
Work you want done on the other sideRPCPlease open this door
Current state you want sharedReplicationThis door is open

An RPC calls logic on the other side of the network. Here a Server RPC requests from a client to the server. The server checks conditions and, if fine, sets the door's bDoorOpen to true.

Replication carries that value to clients. Clients read the received value and open the door in their environment.

Sending the client's request via Server RPC, the server checking distance, and returning the open state via Replication

Configure both the Actor and the variable

Turning on Replicates in an Actor's Class Defaults makes it a target for replication and syncing. Then configure Replication per shared variable.

Variable settingWhat happens
ReplicatedThe server's value is reflected on clients
RepNotifyReflects the value and calls a notify function on receiving a change

RepNotify is the entry for "the value arrived, so update the appearance too". Making bDoorOpen RepNotify creates a function OnRep_bDoorOpen . We update the door's appearance from there.

In Blueprint, setting that variable with a normal Set node calls RepNotify on that side as well. Our hands-on uses that so the server side and the receiving side run the same display update.

Both the Blueprint Set notification and the client's receive notification calling the same ApplyDoorState

Note that Replication is not a mechanism sending every value to every player every frame. Which targets receive updates changes with distance and the like, and intermediate values of something changed rapidly do not all necessarily arrive. Give the door the state "is it open now".

A Server RPC uses your own Character as its entry

Sending an RPC from a client to the server requires the Actor owning that RPC to be owned by the sending client . Ownership here means "which player this Actor is connected to" on the network.

The Character and PlayerController you control hold that connection. A shared door placed in a level, meanwhile, is normally not owned by a specific client. Touching it or storing a reference to it does not create ownership.

The shared door's own RPC does not meet ownership; your Character's RPC takes a door reference instead

In the hands-on, touching the door triggers a Server RPC defined on your own Character . We pass it a reference to "the door to open". A reference is what specifies that door later.

The problem is not "calling from the door's graph". What matters is which Actor the RPC is defined on and who the call's Target is . Calling the shared door's own Server RPC from a client does not meet the ownership condition, so it does not run on the server.

Authority and the character you control are different

Authority indicates whether you are the side deciding that Actor's state. For our shared door, the server's door has Authority. Switch Has Authority narrows logic to that side.

Is Locally Controlled , meanwhile, checks "is this the character controlled in this environment". Both clients and the host have a character they control. The steps below use that difference to separate who sends the request from who rewrites state.

Sponsored

Hands-On: prepare a shared door that rises

Stepping into the zone in front raises the door so you can pass. Once open, it stays open until you restart Play. Let's align that one state change across both screens.

Moving the closed panel upward lets the character pass beneath

Place the components

Create BP_SharedDoor with Actor as its parent. Add these three as children of DefaultSceneRoot. Keep all three directly under DefaultSceneRoot; do not make ButtonZone a child of DoorMesh.

NameTypeSettings
DoorMeshStatic MeshCube, Location (0, 0, 100) , Scale (0.2, 2, 2) , Movable, BlockAll
ButtonZoneBox CollisionLocation (-150, 0, 50) , Box Extent (80, 140, 50) , OverlapOnlyPawn, Generate Overlap Events on
ButtonMarkerStatic MeshCube, Location (-150, 0, 5) , Scale (1.6, 2.8, 0.1) , NoCollision
The component hierarchy and layout with DoorMesh, ButtonZone, and ButtonMarker under DefaultSceneRoot

Turn on "Show Engine Content" in the Content Browser to select the Cube from /Engine/BasicShapes/Cube . ButtonMarker marks where to step. ButtonZone handles the contact test.

Box Extent is half the box's size. ButtonZone becomes 160 cm wide, 280 cm deep, and 100 cm tall. Overlap reports entering a region rather than blocking you.

Turn Replicates on and Replicate Movement off in Class Defaults. We do not send the whole Actor's movement; each environment moves the child DoorMesh from the open state. Leave DoorMesh's "Component Replicates" and "Simulate Physics" off as well.

Create the variables and logic entry points

Prepare the following on BP_SharedDoor. Create functions with the + on "My Blueprint"'s Functions.

NameKindSetting / role
bDoorOpenBoolean variableDefault false, Replication = RepNotify
ApplyDoorStateFunctionNo arguments. Reflects state in appearance
OpenDoorFunctionNo arguments. Sets the open state on the server

Making bDoorOpen RepNotify automatically adds OnRep_bDoorOpen . You do not create a same-named function yourself. Compile and save.

Reflect the open state in the appearance

ApplyDoorState changes DoorMesh's relative location , measured from its parent DefaultSceneRoot. That keeps the opening the same wherever the door Actor sits in the level.

bDoorOpenDoorMesh's relative locationResult
false(0, 0, 100)A panel 200 cm tall from the floor blocks the way
true(0, 0, 350)The panel moves 250 cm up and you can pass beneath

Choose the location from the state

Open ApplyDoorState and place Select Vector . Set A to (0, 0, 350) and B to (0, 0, 100) , and connect a Get of bDoorOpen to Pick A. A Vector here is a position expressed as X, Y, and Z.

Connecting bDoorOpen to Select Vector's Pick A, choosing height 350 when true and 100 when false

Pick A true gives A and false gives B from Return Value. It just picks "the upper location when open, the lower when closed".

Wire exec from the function entry to Set Relative Location . Drag DoorMesh from Components into the graph and connect it to Target. Connect Select Vector's Return Value to New Location and leave Sweep and Teleport off.

Wiring the function's exec, DoorMesh into Target, and the chosen Vector into New Location on Set Relative Location

Target is "the part to move" and New Location is "where to move it". We do not animate here; we switch to a fixed position.

Call the same function from the notify and the initial display

Open OnRep_bDoorOpen and call ApplyDoorState from its entry. Also call ApplyDoorState from Event BeginPlay in the Event Graph to align the appearance at startup. Target on both is Self, the door itself.

Calling ApplyDoorState from both OnRep_bDoorOpen and BeginPlay

ApplyDoorState only "places it at the location matching the current state", so calling it again with the same value never lifts the door further. HP bars and lamp colors are also easier to handle when appearance is derived from the current value.

Ask the server from your Character

Prepare the RPC on the Character

Open BP_ThirdPersonCharacter, right-click in the Event Graph to add a Custom Event, and name it Server_RequestOpenDoor . Select the event and set the following in Details.

EntrySetting
ReplicatesRun on Server
ReliableOn
InputsDoor : BP_SharedDoor Object Reference
Creating Server_RequestOpenDoor on the Character with Run on Server, Reliable, and a Door reference input

Reliable resends until receipt is confirmed. Use it for occasional requests like this open request. It is not a guarantee that ownership can be ignored or that messages arrive after disconnection. Sending many in a row builds up a queue, so avoid calling it from Tick.

Confirm Replicates is on in the Character's Class Defaults and compile. Now the door side can call this event.

Inspect the character that entered the door zone

Return to BP_SharedDoor. Select ButtonZone and add On Component Begin Overlap from the events section in Details. Wire the exec output to Cast to BP_ThirdPersonCharacter with Other Actor into its Object input. Cast Failed does nothing.

Wiring ButtonZone's Overlap exec and Other Actor into the Cast to confirm the entering Character

The Cast here confirms "can the thing that entered be treated as this Character". Drag from the success output As BP Third Person Character and search for Is Locally Controlled . Use the node taking the character as Target and returning a red Boolean with no exec pins.

Passing the Character from the Cast into Is Locally Controlled's Target

Wire the Cast's success exec output to a Branch with Is Locally Controlled's Return Value into Condition. From True, call Server_RequestOpenDoor . Connect the Cast's As BP Third Person Character to the RPC's Target and a Self node to the Door input.

When it is the locally controlled Character, passing Self, the door, to that Character's RPC

That Self is BP_SharedDoor itself , the graph you are editing. You are asking the target Character to "open this door". The Branch's False side does nothing, so the same character visible in other environments does not send duplicate requests.

The diagrams split the same Overlap graph. You do not create a Cast or Is Locally Controlled per diagram.

Sponsored

Verify on the server and open the door

Check the specified door and the distance

Return to BP_ThirdPersonCharacter's Server_RequestOpenDoor. From here on, this runs on that Character's server side.

From the entry, wire to the exec-pin Is Valid . Pass the event's Door output to Input Object and leave Is Not Valid unconnected. Is Valid checks that the target is not empty and is usable.

Passing Server_RequestOpenDoor's Door to Is Valid and continuing only with a valid reference

Then place Get Distance To with Target Self and Other Actor the event's Door. That Self is the server-side Character itself . Feed Return Value into < (Less, numeric comparison) A with B at 400.0 .

Measuring the distance from the server-side Character to the Door with Get Distance To and comparing against 400

Wire exec from Is Valid's success into a Branch with the comparison into Condition. From True, call the Door's OpenDoor , connecting the same event's Door to Target. False does nothing.

After the reference check and the distance condition pass, calling OpenDoor with that Door as Target

We check whether the Character and the door's origin are under 400 cm apart, using the server's positions. 400 cm is a value for this layout, with room to still qualify after stepping a bit past the zone's entrance. In a real game, also check that door's conditions here, such as holding a key or having permission.

Change the door's shared state

Open BP_SharedDoor's OpenDoor. Place Switch Has Authority from the entry and wire only the Authority side to Set bDoorOpen (true). Remote does nothing.

Setting bDoorOpen to true only on OpenDoor's Authority side

Now only the server's door rewrites shared state. Switch Has Authority is not a node that sends work to the server. It only checks which side is running and splits the path. Sending was handled by the earlier Server RPC.

Blueprint's Set bDoorOpen calls OnRep_bDoorOpen, changing the host's appearance. When the value arrives on clients, they too go from OnRep_bDoorOpen into ApplyDoorState. There is no need to add another ApplyDoorState right after the Set.

Looking back at the whole wiring, the flow is:

BP_SharedDoor (the operating side)
ButtonZone's Begin Overlap
  → Cast to BP_ThirdPersonCharacter (Object = Other Actor)
  → Branch (that Character's Is Locally Controlled)
      True → that Character's Server_RequestOpenDoor (Door = this door)

BP_ThirdPersonCharacter (server side)
Server_RequestOpenDoor (Run on Server / Reliable)
  → Is Valid (Door)
  → Branch (Self.GetDistanceTo(Door) < 400)
      True → Door.OpenDoor()

BP_SharedDoor (server side)
OpenDoor → Switch Has Authority → Authority → Set bDoorOpen (true)
  → OnRep_bDoorOpen from the Blueprint Set → ApplyDoorState

BP_SharedDoor (receiving side)
bDoorOpen change received → OnRep_bDoorOpen → ApplyDoorState

Test from the client side first

Compile and save both Blueprints. Place one BP_SharedDoor on the floor and put the two Player Starts outside ButtonZone. Set the Actor's Scale to (1, 1, 1) and keep about 500 cm from the door for easy testing.

  1. Play with two players on Listen Server. Confirm the door is closed on both screens.
  2. Operate the client window and step onto the floor marker. The door rising on both screens is success.
  3. Walk under the door and confirm passage in both environments.
  4. Stop Play and start again. This time have the host step on the marker and confirm it opens on both screens.

We test the client first to verify the path that sends a request to the server. Testing only on the host can run it directly server-side, hiding a send failure from the client.

Investigate the path one stage at a time

When it does not work, check with Print String and Output Log. Temporarily logging right before the RPC call, after the server-side Is Valid succeeds, and on OpenDoor's Authority side narrows down where it stopped. In PIE, also watch the Server / Client labels in logs. Running several games in one editor can show logs from another window; do not judge which side ran from the displaying window alone.

SymptomWhere to check
Stepping on the marker does nothingGenerate Overlap Events on ButtonZone and the Character's Capsule, the Cast target, the start positions
Only the host can open itWhether the RPC is on the Character, whether Target is the touching Character, whether it is Run on Server
The RPC arrives but it does not openWhether Door is valid, whether the distance is under 400, whether OpenDoor's Target is Door
It opens on the host but stays shut on clientsThe door's Replicates, bDoorOpen's RepNotify, the OnRep to ApplyDoorState connection
The state is true on both but nothing movesDoorMesh's Movable, Set Relative Location's Target and New Location
Trying the second player changes nothingIt stays open once opened by design. Restart Play to test

What happens when you change it only locally

You can also confirm why shared state matters with a short experiment. Stop Play, disconnect the wire from the door's Branch True to the RPC, and connect it to Set bDoorOpen (true) instead.

In a fresh Play, stepping only the client onto the marker opens the door in that environment via the Set's RepNotify, but no value is sent to the server. The host's door stays shut. Afterwards, remove the added Set and restore the RPC connection.

Setting the variable only on the client opens your door while the host's stays shut

RepNotify being called and the change reaching the other side are different things. Knowing that difference is the clue when investigating "it only opened for me".

Bonus: Good to Know Up Front

Hold state you want to keep in variables. Holding "the door is open" lets you hand the current state to a client that becomes a replication target later. Sending only the opening effect over Multicast does not replay that past effect for someone who could not receive it.

RPCs have other destinations. A Client RPC notifies the client owning that Actor. A Multicast RPC, called from the server, runs on the server itself and on the connections where that Actor is replicated. Calling Multicast from a client does not broadcast to everyone.

You do not have to keep standing on it here. To extend it to closing when you leave, consider two players standing on it at once. Closing on any single End Overlap would close it while the other is still standing there.

Smooth opening can come later. We switched positions here, but a Timeline can move toward the current state instead. Confirm the open state is shared first, then add presentation.

Movement sync involves other mechanisms too. Replicate Movement mainly handles the Actor root's movement. Not every child mesh's relative location or arbitrary variable is sent by that checkbox alone. Characters also have dedicated movement synchronization.

After it works on one PC, test other network conditions. Testing with added latency and packet loss, and testing on a Dedicated Server where the host does not play, reveals problems the minimal two-window setup hides. A dedicated server runs only the game's progression, with no player of its own.

Joining over the internet is the next stage. PIE prepared the connection for us. The flow where a friend finds and joins a room is organized in the sessions and online connection article. Where shared scores live is also covered in GameMode, GameState, and PlayerState.

Summary

For the shared door, your Character requested with a Server RPC and the server decided the door's state. Clients received that state via Replication and updated the appearance from RepNotify.

Do not judge success from your own screen alone; operate the client first and compare both the host's and the client's state. Following "who asked, where it was decided, and who received the result" is the foundation for your next shared gimmick.

Reference: Networking overview, PIE multiplayer options, Replicating actor properties and Blueprint RepNotify, RPC execution conditions.

Unreal Engine Notes in this section98