Develop

SharedSpaces sample overview

Updated: May 11, 2026

Overview

This sample demonstrates how to combine three networking layers into a multiplayer VR experience with host migration, deep linking, voice chat, and player state synchronization. The three layers are Oculus Platform SDK for social presence, Photon Realtime for transport, and Unity Netcode for GameObjects for state replication. It is designed for VR developers building social applications on Unity who want a complete reference for integrating these systems in a listen-server architecture (where one headset acts as both server and client).

What you will learn

  • Integrate Meta Platform SDK Group Presence with Photon Realtime room names and Unity Netcode session IDs
  • Implement host migration with pre-selected fallback hosts and state preservation
  • Handle deep linking from invites and route players to specific rooms and sessions
  • Integrate Photon Voice 2 for spatial voice chat synchronized with networked player objects
  • Synchronize player state using NetworkVariables with client-authoritative movement

Requirements

  • Meta Quest headset
  • Unity development environment configured for Meta Quest development
For detailed SDK versions, toolchain setup, and build configuration, see the sample’s README.

Get started

Clone or download the sample from the Unity-SharedSpaces repository. Open the project in Unity, configure your Meta application ID in the Platform settings, and build to your Quest headset. The sample includes all required packages pre-configured. To experience multiplayer behavior, you need two or more Quest devices with the app installed. For complete build instructions and troubleshooting, consult the README.

Explore the sample

The sample includes six playable scenes representing different multiplayer spaces (the repository also contains TransitionMap.unity for internal scene transitions). Each player group starts in a unique private lobby and can travel through portals to four colored rooms.
File / SceneWhat it demonstratesKey concepts
Startup.scene
Entry point and initialization
Persistent object loading, three-layer init sequence
Lobby.scene
Private group lobby
Unique lobby session ID per group, invite/roster panels
BlueRoom.scene
Private match room
Private match session ID, room-specific spawn points
GreenRoom.scene
Private match room
Private match session ID, room-specific spawn points
RedRoom.scene
Private match room
Private match session ID, room-specific spawn points
PurpleRoom.scene
Public match room
Shared public session ID, cross-group matchmaking
ScriptWhat it demonstratesKey concepts
SharedSpacesApplication.cs
Main orchestration controller
Coroutine-based init, event callbacks, room switching
SharedSpacesNetworkLayer.cs
Connection lifecycle and host migration
8-state ClientState machine, IConnectionCallbacks, host-first fallback strategy
SharedSpacesSession.cs
Networked session state
NetworkBehaviour, ServerRpc/ClientRpc, fallback host pre-selection
SharedSpacesGroupPresenceState.cs
Social layer integration
GroupPresence.Set with retry loop, lobby/match session ID mapping
SharedSpacesPlayerController.cs
Third-person character control
Owner-only input, ServerRpc for animation sync
SharedSpacesVoip.cs
Voice chat integration
Photon Voice 2, speaker factory, voice-to-NetworkObject mapping
SharedSpacesExternalPortal.cs
Cross-app launching
Application.LaunchOtherApp(), session context preservation across apps

Runtime behavior

When you run the Startup scene on a Quest headset, the sample initializes the Meta Platform SDK, connects to Photon, and loads the Lobby scene. You see a private lobby space unique to your group. Approach the invite zone to open the invite panel and add friends to your group. Once friends join, you see their avatars appear in the lobby with synchronized usernames and colors.
Walk through any of the four portal doors to travel to a colored room. In private rooms (Blue, Green, Red), only your group members appear. In the public Purple room, you may encounter players from other groups. Voice chat activates automatically when players connect, with spatial audio positioned at each player’s avatar.

Key concepts

The SharedSpaces sample solves several challenging integration problems specific to multiplayer social VR. Each subsection highlights a key architectural decision visible in the code.

Three-layer networking architecture

The sample integrates three independent networking systems that each serve a distinct role. The Meta Platform SDK manages social graph and group presence, Photon Realtime provides the transport layer for real-time data, and Unity Netcode for GameObjects handles game state replication. The critical integration point is the session-to-room mapping function, which bridges all three layers:
private string GetPhotonRoomName() {
    return groupPresenceState.matchSessionID == ""
        ? groupPresenceState.lobbySessionID
        : groupPresenceState.matchSessionID;
}
This function ensures Photon room names match Meta Platform session IDs, allowing deep links and invites to route players to the correct network sessions. When no match session exists, the function returns the lobby session ID. Match rooms use composite IDs like “BlueRoomLobby-a3f9b2c1”.
For complete implementation, see SharedSpacesApplication.cs.

Group presence and session IDs

Each group receives a unique lobby session ID when the first player launches the app. The sample generates this ID using a random hex string prefixed with “Lobby-“. When players travel to private rooms (Blue, Green, Red), the match session ID concatenates the room name with the lobby session ID, ensuring only group members can join. The public Purple room uses a hardcoded session ID (“PurpleRoom”) shared across all groups. The GroupPresenceState wrapper retries failed presence updates automatically, handling transient network failures without manual intervention.
For complete implementation, see SharedSpacesGroupPresenceState.cs.

Host migration

The sample pre-selects a fallback host before the current host leaves. When a Netcode session spawns, the host identifies the client with the smallest client ID (excluding itself) and broadcasts this ID to all clients via ClientRpc. Each client stores this fallback ID locally. When the Photon master client changes, the NetworkLayer transitions to the MigratingHost state if the local client matches the fallback ID. The migrating host disconnects from Netcode, reconnects to Photon as the new master client, then restarts Netcode as the new host. Player positions are preserved by saving local state on NetworkObject destroy and restoring it after reconnection.
For complete implementation, see SharedSpacesSession.cs and SharedSpacesNetworkLayer.cs.

Deep linking and destination routing

The sample registers a callback for OnJoinIntentReceived, which fires when a player accepts an invite or clicks a deep link. The intent contains destination, lobby session ID, and match session ID. If the app is already running, SwitchRoom loads the target scene, updates session IDs via GroupPresence, then disconnects from the current Photon room and reconnects with the new room name. If the app launches fresh from a deep link, the init sequence uses the intent data to join the correct session immediately. This pattern ensures invites always route players to the exact room and session of the inviter, regardless of app state.
For complete implementation, see SharedSpacesApplication.cs.

Voice chat integration

The sample uses Photon Voice 2, which operates independently from the Photon Realtime transport used for game data. The challenge is mapping voice player IDs (assigned by Photon Voice) to Netcode NetworkObjectIds. The sample solves this by storing each player’s NetworkObjectId in Photon custom properties when they spawn. The SpeakerFactory callback reads these properties when a remote voice player connects and attaches the audio source to the correct player GameObject. This ensures spatial audio corresponds to the correct visual avatar, even when players join in different orders.
For complete implementation, see SharedSpacesVoip.cs.

Player state synchronization

The sample demonstrates client-authoritative movement combined with server-authoritative state. Each player’s transform is controlled by ClientNetworkTransform, allowing smooth local movement without server round-trips. Player color, username, and master client indicator are NetworkVariables that replicate from server to all clients. When a player changes color locally, the client sends a ServerRpc to the host, which updates the NetworkVariable, triggering OnValueChanged callbacks on all clients. This pattern separates latency-sensitive input (movement) from consistency-critical state (visual appearance).
For complete implementation, see SharedSpacesPlayerState.cs and ClientNetworkTransform.cs.

Extend the sample

  • Modify the PaintShop trigger zones to store persistent cosmetic changes in player profiles instead of applying session-local color changes
  • Add a fifth room type with different session ID logic to explore alternative matchmaking patterns, such as skill-based or interest-based lobbies
  • To use this architecture in your own project, start from the three-layer initialization sequence in SharedSpacesApplication.cs and adapt the session-to-room mapping logic in GetPhotonRoomName() and GetMatchSessionID()
For additional multiplayer samples, explore the Meta Quest samples repository.