A. Overview of SharedSpaces
February 12, 2026 · View on GitHub

A. Overview of SharedSpaces
The VR Developer Tools team built SharedSpaces to show how to quickly gather people in VR using Oculus Social Platform APIs. This version uses Unreal Engine with the Unreal EOS plugin as the transport layer. Other versions exist, including one for Unity.
| Oculus | Group presence with destination, lobby, and match IDs. |
| EOS | Transport via a room named after the lobby or match ID. |
| UE4 | Replication between room members with the master client as host. |
SharedSpaces networking has three layers. The Oculus layer provides presence info to find and connect with friends. The EOS layer transports messages between players. The UE4 layer replicates game objects.
This overview explains each layer and how they connect to create a simple multiplayer app. It lets people connect and play together without a dedicated server.
A Private Lobby Connected to Rooms

SharedSpaces consists of connected levels called destinations. Your personal lobby sits at the center, with doors leading to surrounding matches. Matches on the left are private and accessible only from your lobby. The match on the right is public and reachable from any lobby.
Social Layer - Destination, Lobby & Match Session IDs

This layout directly represents the new group presence APIs. To send you to a SharedSpaces destination, we set your destination and two session IDs in your group presence: a lobby session ID, which rarely changes, and a match session ID, set only when you join a match.
Destinations are specific app areas defined on the Developer Dashboard under Platform Services > Destinations. The lobby session ID identifies a close group that stays together between games and may play as a team. The match session ID identifies players currently in a match, regardless of team.

When you launch SharedSpaces, you start in your private lobby with a unique ID. To form a group before and after matches, you invite others to share your lobby. If they accept, their lobby session ID matches yours. When you are in the lobby simultaneously, you share the same space.
Think of the lobby as your group's base camp. Different groups return to their own lobbies after matches.

Group members can move anytime between their lobby and private matches. This only changes their match session IDs.

You can invite anyone to your private match. When they accept, they join you and their match session ID updates. Accepting a match invitation does not change the lobby session ID.

When players leave a match through the lobby door, they return to their own lobbies if they are not in the same group.

SharedSpaces also includes a purple room representing a public match accessible from all lobbies. Anyone can enter from their lobby at any time. This room lets you meet people outside your group without invitations.
Transport Layer - EOS Lobbies
EOS uses lobbies to connect users. Players in the same match or lobby share an EOS lobby, allowing data to flow between them. The transport layer routes packets between users, often behind firewalls.

EOS lobbies have unique names. We name rooms using the social layer’s session IDs: either the match session ID if set, or the lobby session ID otherwise.
A key EOS feature is tracking the oldest member, called the “master client,” marked with stars.

For example, when Alice, Bob, and Charlie enter the Purple room, Charlie joins first, creating the room and becoming the master client. Alice and Bob join later as normal clients.

If Charlie leaves, EOS selects a new master client and notifies all clients. This feature supports the next networking layer.
Game Replication Layer - Unreal Engine Clients and Listen-Server

UE4 uses a client-server model. Clients connect to a server that enforces rules and replicates relevant objects.
For trusted authority or powerful hardware, the server runs as a dedicated server in a data center. Each UE4 client runs on its own headset and connects to this server.

SharedSpaces hosts the server on one headset as a listen-server. Here, Unreal Engine acts as both server and first client, accepting connections from others.

For each room, the transport layer selects the UE4 listen-server host. The master client of the EOS room becomes the host. The host opens a map with the ‘listen’ option. Clients connect to the host using an EOS Net Driver address.

When the host leaves, host migration occurs. For example, Alice leaves the purple room. EOS selects Bob as the new master client. Remaining members are notified and reconnect to Bob.

Now, two EOS rooms exist. The Purple room is hosted by Bob, with Charlie and Donna connected. Alice left through the door to her lobby. Since she is alone there, she becomes the master client and host of her group lobby.
B. SharedSpaces in Action
Let's see how SharedSpaces works.
Alice starts SharedSpaces alone in her private lobby. She is the master client and host, shown by the star next to her name.
Alice wants Bob to join her group so they can stay together between matches. She steps on the invite panel switch and sends him an invitation from her lobby. When Bob accepts, SharedSpaces launches on his headset with a deeplink message that lets him join Alice in-game. Bob’s lobby session ID updates to match Alice’s, so they share the same lobby.
Bob enters the blue door to start a private match, followed by Alice. They arrive in the same Blue Room with a shared match session ID for their private room. Since Bob arrived first, he hosts the room, and Alice connects to him.
Alice invites her friend Charlie to join their match. Charlie accepts while in his own lobby. His match session ID updates to the private match ID, but he keeps his original lobby session ID. This means he remains in a different group.
When Bob leaves the blue room, EOS notifies Alice and Charlie that the master client changed. Alice becomes the new master client and opens a UE4 listen-server. Charlie connects to her. Meanwhile, Bob starts hosting his own group lobby.
When Charlie leaves the blue room, he does not join Bob because they have different lobby session IDs. Instead, Charlie returns to his separate lobby. You can verify this by stepping on the roster panel switch, which lists your different groups.
For Alice, returning to the lobby means rejoining Bob, who is waiting for her.
To add Charlie to their group, Alice or Bob simply send him an invitation from their lobby. When Charlie accepts, his lobby session ID updates, and all three share the same lobby between matches.
C. SharedSpaces Implementation
SharedSpaces uses the Oculus Platform and Unreal EOS plugin. Each has a game instance subsystem implemented in its own plugin. This design makes them easy to reuse in your projects.
You can find detailed documentation for these plugins here:
Next, we explain how these subsystems integrate at the project level.
1. SharedSpaces Game Instance
The core logic at the project level is the SharedSpaces Game Instance blueprint. In Unreal Engine, the game instance is a persistent singleton object that lasts for the entire process, surviving map loads.
Below is its overall layout.
We can divide it into two parts: initializing subsystems and using them to enable player communication through social, transport, and application networking.
1a. Registering Subsystem Callbacks
At the top, the blueprint registers event handlers for both major subsystems.
-
Oculus Platform Subsystem
- On Login Complete: Wait until Oculus login finishes before identifying with EOS, since both use the same unique user and application identifiers. These identifiers do not reveal personal information.
- On Launch Intent Changed: Triggered when the app starts. We only handle manual launches to start at the default destination.
- On Group Launch Intent Received: Triggered when the app receives an Oculus group presence launch intent. This happens when joining a specific destination with lobby and match IDs plus a deeplink message, usually after accepting an invitation.
-
EOS Subsystem
- On Log Entry: Enables logging of EOS info, warnings, and errors.
- On Login: Fires when the user attempts EOS login. EOS login must succeed before continuing initialization.
- On Room Created: Fires when creating an EOS lobby, triggering a UE4 client/server connection.
- On Room Found: Fires after searching for an EOS lobby. If found, the user joins it; otherwise, they create a new lobby.
- On Room Joined: Fires when joining a lobby, triggering a UE4 client/server connection.
- On Master Client Changed: Handles host migration. When the EOS master client leaves, remaining users are notified. The new master client starts a listen-server, and others connect to it.
- On Room Destroyed: Fires when a user leaves a lobby. If the user was alone, the lobby is destroyed.
- On Player Joined: Notifies the master client when a player joins.
- On Player Left: Notifies the master client when a player leaves.
- On App Enters Foreground: Usually triggered when the user removes and replaces their headset. The app rejoins the previous lobby.
These subsystems share the game instance’s lifecycle: they are singletons that live as long as the app. The main challenge is managing timing dependencies between subsystems and the game instance. Here, the Oculus Platform and EOS subsystems notify the game instance by calling OculusPlatformSubsystemStarted and EOSInitialized events. These calls happen by name in code, so no prior bindings are needed.
1b. Social Networking: Setting Your Group Presence
The rest of the blueprint handles the Network Launch sequence, which starts with a user's social platform group presence. This presence includes a destination and lobby and match IDs. Here is the information needed to connect players:
-
Destination Api Name: Define destinations for your app on the Oculus application dashboard. These destinations support deep linking for seamless navigation. SharedSpaces defines one destination for the lobby and one for each colored room.
-
Level Name: Each destination links to a distinct level in SharedSpaces. This design choice helps illustrate destinations as simple, colored spaces. The level to load is stored in the destination’s dashboard configuration.
The *deeplink message* is an optional app-specific string. SharedSpaces uses a simple JSON format with key-value pairs. Every destination includes a "map" key, as shown for the Red Room.
-
Lobby Id: Oculus group presence includes identifiers to specify location. The lobby session id groups users who stay together beyond a single match. In the roster panel, users sharing a lobby id appear grouped. Your app decides how to use this grouping, but generally, users in the same lobby stay together between matches and, if possible, on the same team.
In SharedSpaces, you receive a unique lobby id when starting the app. You share your lobby id by inviting others to join your Lobby destination. Accepting the invitation adds them to your team.
At the Lobby destination, you join the EOS room named after your lobby id.
-
Match Id: The match session id identifies your current destination when not in the team lobby.
For private matches (Red, Green, Blue rooms), match ids link uniquely to your lobby (e.g., "RedRoom_for_Lobby123"). For public matches (Purple room), match ids use a shared public name (e.g., "ThePurpleRoom"). Currently, only one purple room exists, which does not scale. Public rooms should use matchmaking to distribute players across instances, considering lobby ids to keep teams together. We plan to add this in a future version.
At a colored room destination, you join the EOS room named after your match id.
Group presence setup differs when joining a lobby versus a match. The destination type is stored in the destination deeplink on the dashboard. We query the destination by name and extract this info using the Get Is Lobby node from the deeplink. Since this data format is app-specific, the node is implemented in a project blueprint library (see SharedSpacesFunctionLibrary.h).
When joining a lobby, we set the user's presence to the lobby session id passed to Network Launch. For invitations, this is the sender’s lobby id, so you join their team and lobby. Joining a lobby always clears your match session id.
You can join a match in two ways: through a portal or by accepting an invitation. When using a portal, we derive the match id from the origin lobby (e.g., "RedRoom_for_Lobby123"). Since all users in a lobby share the same lobby id, they join the same match via the portal.
When accepting a match invitation, a user keeps their current presence lobby id but uses the match id passed to Network Launch as their presence match id.
For public matches, we override the match id with the room’s public name, as explained earlier.
1c. Transport Layer: Joining or Rejoining an EOS Lobby
We join an EOS Lobby based on the user's group presence set earlier: we use the match ID if available, otherwise the lobby ID.
We locate the lobby using Find Session.
After Find Session completes, a callback runs. If the lobby is found, the user attempts to join it by calling Join Session. If not, the lobby is created with Create Session, and the user becomes the initial master client.
We must rejoin the current EOS room in two cases: when the connection to the EOS lobby is lost (rare) and when the application returns to the foreground (common). The EOS setup region of the blueprint handles these events.
1d. Application Replication: Establishing UE4 Client-Server Connections
UE4 network connections are established after joining or rejoining an EOS lobby. We need three key pieces of information: the EOS room master client status, the host address of the current master client, and an optional start location.
When the master client changes, we perform a host migration by reestablishing UE4 network connections.
We create the UE4 connection using the open console command. This command takes a parameter resembling an HTTP URL and has two forms depending on whether we host the level or join an existing server:
- Master Client: open <level> # <startpos> ? listen
- Normal Client: open <address> # <startpos>
The EOS room master client opens the level by name and adds "?listen" to accept client connections, enabling listen-server mode. Other clients connect using the host address, which in SharedSpaces is the host's application-specific user ID followed by ".oculus".
The start position has two forms:
- Joining: the destination origin
- Rejoining: the current location and rotation as a string
We provide a sensible start position depending on whether the user is joining or rejoining.
We place several player start objects in the level, which look like console controllers with flags.
Each player start can have an optional Player Start Tag. SharedSpaces includes one default player start without a tag for first-time game starts. We also place tagged player starts in front of portals matching their destinations. When traveling between spaces, players spawn near the door leading to their origin.
When rejoining a space, such as during host migration, we respawn the player at their current location and orientation. We create a new player start at that location and use it during spawning.
2. Roster and Invite Panels
The roster panel is a key part of the Oculus group presence system. It appears as a 3D system panel in your field of view, overlaying any game content. It shows who you are playing with and who you have invited.
In SharedSpaces, you open the roster panel by moving your character onto a pressure plate. The RosterPanelLaunchTrigger blueprint calls the Oculus Platform Subsystem to launch the panel.
When the system panel opens, VROS absorbs all controller input until the panel closes. In UE4, this means if your player triggers the panel while moving, they will keep moving even after releasing the thumbstick. To prevent this, the blueprint waits for the character to stop moving and confirms the character is still on the pressure plate before opening the panel.
You can open the invite panel from the roster panel (lower left button) or directly via a separate call to the Oculus Platform Subsystem.
3. Portals
The Portal blueprint triggers a Network Launch on the SharedSpaces Game Instance for any player entering its collision volume. You can place it in the level with its built-in visible geometry or next to an existing door object. You must set its destination parameter, which matches a destination name defined on the dashboard.
When a user enters the portal, we retrieve their current lobby ID and query the destination's level name. We perform a network launch with these values and leave the match ID empty, even when traveling to a match. This works because match IDs derive from lobby session IDs when traveling through portals, as explained earlier.
4. SharedSpaces Character
The SharedSpaces Character is a classic UE4 mannequin with some modifications.
4a. Character Attributes
In SharedSpaces, users differ by name, color, master client status, and current destination. Pressing the trigger sets a new random color. Pressing the thumbstick down hides your character name (default is your Oculus name) and lets you choose between "Alice," "Bob," and "Charlie." This hidden feature helps create screenshots and videos.
Most objects, including the player character, are lost between level loads. To persist data, we copy these values to variables on the SharedSpaces Game Instance. For example, the current destination is used during the UE4 Open Level process.
We replicate the user's name, color, and master client status so all players can identify each other, with distinctive colors and a star above the master client.
For example, the character color uses a linear color variable called BodyColor with RepNotify replication. When the server sets this value, it replicates to all clients and triggers the notification function OnRep_BodyColor.
Calling SetBodyColor does not set BodyColor locally. Instead, it sends a message to the server to set the value, ensuring replication to all clients, including the caller.
NotifyServerOfBodyColor is a reliable server event that sets BodyColor on the server and replicates it with notification.
When clients receive the replicated value, OnRepBodyColor runs and updates the character's material parameter. This happens on all clients, including the owner.
4b. Camera Behavior
The camera components are highly configurable. For this showcase, we implemented a custom behavior via blueprint: the camera location does not move with the character to improve comfort. After the character stops moving and a delay passes, the camera resets behind the character, oriented by the character's rotation. The camera still rotates with the headset as the user looks around.
Two parameters under Camera Logic control this: the wait time after stopping before resetting the camera, and the minimum travel distance between resets. These reduce the number of camera resets for comfort.
Each tick, the local character checks movement status, providing IsStationary, LastTimeWithMovement, and LastLocationWithMovement.
After confirming VR headset use, two conditions must be met before resetting the camera: the character must be stationary, and enough time and distance must have passed since the last reset.
These conditions are bypassed when the user manually requests a reset by pressing the A or X buttons.
We adjust the camera lag speed to freeze or reset the camera location. The camera boom (Spring Arm Component) follows the player smoothly. We set lag speed near zero to hold the camera in place and high speed to reset it instantly.
5. In-Game Log
Pressing the grip button toggles the in-game log panel. Most important events happen behind the scenes in this technical showcase. The log panel connects to the persistent log added to the Oculus Platform Subsystem, ensuring the log remains between level loads.
6. External Application Portal
These portals work like the standard Portals. Place the ExternalAppPortal blueprint, then set the Application App ID and destination name. When a player enters its collision volume, the portal calls Launch External App on the SharedSpaces Game Instance. The Game Instance validates inputs and calls the Platform function Application Launch Other App.
This implements the App to App Travel platform feature.
7. User Reporting
When users press the Oculus button and select Report, your application must notify the system how it will handle the report. You can provide your own in-app reporting flow or defer to the system by selecting Unhandled.
This implements the User Reporting platform feature.
D. Oculus Application Configuration
To build and run your own copy of SharedSpaces, create an application on the Developer Dashboard.
1. Application Identifier
Place your Oculus application identifier in SharedSpaces/Config/DefaultEngine.ini as follows:
[OnlineSubsystemOculus]
bEnabled=false
RiftAppId=123xxxxxxxxxx321
MobileAppId=123xxxxxxxxxx321
Although SharedSpaces does not use the Oculus Online Subsystem (OSS), the identifier is still required in this section.
Find the identifier (App ID) in the API section.
You need a MobileAppId for Quest builds and a RiftAppId for Quest Link.
2. Destinations
Recreate the SharedSpaces destinations in your application. Find Destinations under Engagement.
SharedSpaces has four destinations: a Lobby, three private rooms (Red, Green, Blue), and one public room (Purple). Their settings are:
| API Name | Deeplink Message | Display Name | Description |
|---|---|---|---|
| Lobby | {"is_lobby":"true","map":"Lobby"} | Lobby | The Lobby |
| RedRoom | {"map":"RedRoom"} | Red Room | The Red Room |
| GreenRoom | {"map":"GreenRoom"} | Green Room | The Green Room |
| BlueRoom | {"map":"BlueRoom"} | Blue Room | The Blue Room |
| PurpleRoom | {"map":"PurpleRoom","public_room_name":"ThePurpleRoom"} | Purple Room | The Purple Room |
Set Deeplink Type to Enabled and add an image for each destination. SharedSpaces sets the destination Audience to Everyone. Also, set the max group launch capacity for each destination to enable the group launch feature.
3. Data Use Checkup
Request access to platform data required by SharedSpaces. Under Data Use Checkup, add and submit these items for certification:
- User ID
- User Profile
- Deep Linking
- Friends
- Invites
4. Upload to Release Channel
Upload an initial build to a release channel to use platform features. Follow the instructions at Unreal Sign Project for Release.
After packaging your project, open the Meta Developer Hub app, go to App Distribution, find your app, select a Release Channel, and press Upload.
Once uploaded, the build appears on the Oculus Developer Dashboard under Distribution -> Release Channels.
After passing all tests, go to Distribution -> Release Channels, select your release channel, then go to Users and click Email Invite Users. Invite all users who need access.
Use emails linked to Quest devices; otherwise, the Oculus platform will not work correctly.
If done correctly, the app appears in your Quest library, ready to download and install. After this, you can upload development or shipping builds directly, and the entitlement check will always pass.
Each time you upload a new build, delete the app data on your Quest device before launching. Go to Settings -> Storage, find your app, and select Delete App Data.
To test with other users, add them to the channel. See Add Users to Release Channel for details.
Once the initial build is uploaded, you can test local changes with any development build using the same application ID without uploading every build.
5. EOS Configuration
Configure EOS by following the instructions here.