Project-XVR
Multiplayer VR assessment platform with cooperative missions, networked physics, and hand tracking
Overview
The project is built on a custom framework called XVRAssessmentSuite, which provides a standardized mission lifecycle (PreInit, Init, Ready), interface-driven task contracts, and a mediator-pattern XR interaction system. Networking is handled through Unity's Netcode for GameObjects with server-authoritative gameplay, Vivox voice chat for player communication, and Timeline-driven narrated introductions for each mission.
The platform targets Meta Quest via OpenXR and XR Interaction Toolkit 3.1.2, with full hand tracking support through XR Hands 1.5.1. The architecture is designed for extensibility: new missions can be added by implementing the framework's interfaces and registering with the Service Locator.
Non-Technical Summary
The application guides players through four different missions, each with its own style of challenge. In one mission, players sort colored objects into matching slots. In another, they work together to fire cannons and push a payload to a goal. Each mission has a narrator that explains the rules and a timer counting down, creating an engaging and slightly competitive atmosphere.
Behind the scenes, the application manages all the complexity of keeping everyone's view of the world synchronized. When one player picks up an object, everyone else sees it move in real time. A significant amount of engineering went into making this feel seamless, including three complete rewrites of the networking system before settling on the final approach.
Highlights
- Designed and built the XVRAssessmentSuite framework, a reusable, namespace-organized mission lifecycle system with async PreInit/Init/Ready phases, interface-driven contracts (IInitialization, IPostInitialization, IXVRTask, IXVRSpawnHandler), and a mediator-pattern XR interaction pipeline
- Migrated networking stack from Photon PUN 2 through Photon Fusion 2 to Unity Netcode for GameObjects (v2.3.2) across 5 iterative attempts, ultimately delivering server-authoritative gameplay with NetworkVariable synchronization, ownership transfer on grab, and ServerRpc/ClientRpc communication
- Integrated Vivox voice chat (v16.6.0), XR hand tracking (v1.5.1), and Timeline-driven narration with captions to create an immersive multiplayer VR experience targeting Meta Quest via OpenXR
- Implemented 4 distinct cooperative mission types (symbol matching with room-based distribution, team cannon physics with payload delivery, obstacle navigation with puzzle gates, and color-matching tutorials), each with networked state, UI Toolkit interfaces, and task completion tracking
Quick Highlights
- Server-authoritative multiplayer via Netcode for GameObjects with ownership transfer for true collaborative object manipulation
- Custom XVRAssessmentSuite framework with async initialization pipeline and interface-driven mission contracts
- Vivox voice chat integration for real-time player communication during missions
- XR hand tracking and controller support via OpenXR with mediator-pattern interaction filtering
- Timeline-driven narration system with audio captions for guided mission introductions
Technical Breakdown
The core framework is organized into namespaces under XVRAssessmentSuite:
- CoreSubsystems.Base -
MissionHandlerBaseprovides the abstract lifecycle:PreInit()for pre-spawn setup,Init()for scene initialization, andReady()for server-side validation before gameplay begins. FiresOnMissionInitCompleteto signal downstream systems. - CoreSubsystems.Interfaces -
IInitialization,IPostInitialization,IXVRTask(withOnTaskCompleteevent), andIXVRSpawnHandlerdefine contracts for mission components. - CoreSubsystems.Enums -
MissionStateenum tracks lifecycle phases. - MissionSubsystem - Houses
MissionHandlerBaseand mission-specific handlers. - TaskSubsystem - Task completion tracking with event-driven callbacks.
- ServiceLocation - Scene-scoped
ServiceLocatorfor dependency injection across networked scenes. - UIToolkit - UI Toolkit integration layer for mission-specific UI panels.
- Networking - Network state management (renamed from
NetworkingReworkduring v2.0 cleanup). - XR -
XVRBaseInteractorMediatorandXVRBaseInteractableMediatorproviding mediator-pattern XR interaction filtering.
Networking Architecture
The multiplayer stack uses Netcode for GameObjects 2.3.2 with a server-client topology:
- NetworkObject ownership model:
GrabTransferOwnershipchangesNetworkObject.OwnerClientIdon XR grab events viaChangeOwnershipServerRpc(), enabling any player to manipulate any interactable. - NetworkVariable synchronization: mission state (obstacle visibility in
PuzzleOne, flashlight state inFlashlightInteractableHandler, color assignments inColorHandler) is synced automatically. - NetworkList:
MissionOneSpawnHandlerusesNetworkList<Vector3>to manage spawn point allocation across clients. - ServerRpc/ClientRpc: All authoritative actions (cannon firing in
CannonManager, catapult rotation inMoveCatapult, puzzle activation inMissionThree) route through ServerRpc with ClientRpc for broadcast.
Mission Implementations
Mission 0 - Color Matching (Tutorial): ColorHandler assigns random materials to objects and targets via server-authoritative NetworkVariable. Players grab objects and place them in matching color slots. Correct placements lock via LockSocketInteractorMediator.
Mission 1 - Symbol Matching: MissionHandler initializes rooms based on player count, uses SymbolObjectFactory to spawn symbol objects across 6 cube faces (Front, Back, Left, Right, Top, Bottom). NewRoomHandler tracks local and global correct interactables. MissionOneSpawnHandler allocates spawn points via NetworkList. MissionOneUIHandler displays status via UI Toolkit with TimelineHandler countdown.
Mission 2 - Cannon Payload: CannonManager spawns networked ammo with NetworkObject.Spawn() and applies Rigidbody.AddForce() impulse. MoveCatapult handles server-controlled rotation in 4 directions. MovePayload applies physics forces to push the payload left/right with Y-axis frozen. CollisionDetector fires win condition. UIManager displays team scores and 60-second countdown.
Mission 3 - Obstacle Navigation: WaypointMover moves a payload along a Waypoint hierarchy path with pause/resume via ServerRpc. PuzzleOne, PuzzleTwo, PuzzleThree each manage obstacle visibility through NetworkVariable<bool> arrays. PuzzleManager implements IXVRTask to detect payload boundary triggers and fire OnTaskComplete. MissionThree orchestrates puzzle progression and timer management.
XR Interaction Pipeline
The interaction system extends XR Interaction Toolkit with a mediator pattern:
LockSocketInteractorMediatorextendsXVRBaseInteractorMediator, implementsIXRHoverFilterandIXRSelectFilterto restrict socket interactions to a configured allowed-interactable set. On correct interaction, firesOnTaskCompleteviaIXVRTask.FlashlightInteractableHandlerextendsXVRBaseInteractableMediatorwithNetworkVariable<bool>light state synchronized viaToggleLightServerRpc().
Scene Flow
0B_MainMenu_Scene -> 1_Intermission_Scene -> M_Mission_0_Scene (tutorial) -> M_Mission_1_Scene -> M_Mission_2_Scene -> M_Mission_3_Scene. Each mission scene is loaded with its own MissionHandler registered in the ServiceLocator.
Voice Communication
Vivox 16.6.0 provides in-game voice chat with channel management tied to the multiplayer session lifecycle.
Audio System
Main_AudioMixer routes mission-specific SFX (collision sounds, button presses, door effects, lighting SFX, UI hover/click sounds) and narration audio through dedicated mixer groups. Timeline-driven narration plays mission introductions and instructions with synchronized captions.
Systems Used
- Mission Handler Base - Abstract base class providing standardized mission lifecycle management with OnMissionInitComplete events and interface-driven initialization
- XVR Interactor Mediator - Mediator-pattern XR interaction filtering system that restricts socket interactions to allowed interactable sets and fires task completion events
- Service Locator - Scene-scoped service locator for dependency injection of mission handlers, UI managers, and spawn handlers across networked scenes
- Grab Transfer Ownership - NetworkObject ownership transfer system enabling true multiplayer object manipulation by changing ownership on grab events
- Symbol Object Factory - Factory-pattern object creation system that instantiates and configures symbol objects across 6 cube faces with networked spawning
- Timeline Handler - Timeline-driven narration and caption system for mission introductions and instructions with audio mixer integration
- Network Spawn Handler - Server-authoritative spawn point allocation using NetworkList to manage player teleportation positions across mission scenes
- Waypoint Navigation System - Physics-based payload movement along waypoint paths with networked pause/resume and gizmo visualization for level design
Impact & Results
- Survived 3 complete networking stack migrations (Photon PUN 2 -> Photon Fusion 2 -> Netcode for GameObjects) within a 6-month development cycle, arriving at a stable server-authoritative architecture
- Built a reusable framework (XVRAssessmentSuite) with 9 distinct namespaces and interface-driven contracts, enabling team members to implement mission-specific logic without modifying core systems
- Coordinated contributions from 4 developers across 884 commits, managing parallel feature branches (Zoe-Mission-1, Yohan-Branch) with regular merges into the main Netcode-Attempt-5 development branch
- Integrated 5 major Unity XR packages (OpenXR, XRI, XR Hands, XR Management, AR Foundation) with Vivox voice chat and Timeline narration into a cohesive multiplayer VR experience
Deep Dive
The most significant technical challenge was networking. The project went through 5 distinct networking iterations:
Photon PUN 2 (Dec 2024 - Feb 2025): Initial import of Photon PUN and Voice. Built client/server scripts and a
BootstrapHandler. Abandoned due to limitations with VR object synchronization.Photon Fusion 2 (Feb 2025): Imported after removing PUN 2. Explored room-based networking. Created a
MissionSubsystemandPlayerServiceslayer. Still insufficient for the server-authoritative model needed for assessment integrity.Photon Fusion Attempt 3 (Feb 2025): Clean restart with "base project with no multiplayer," importing
TimelineExtensions, Polygon asset packs, and UI prefabs. Established the visual foundation but networking remained problematic.Netcode Attempt 4 (Feb 2025): First Unity Netcode attempt. Configured for Dedicated Server topology. Achieved a "working implementation" but dedicated server approach was overly complex for the use case.
Netcode Attempt 5 (Feb 2025 - May 2025): Final and successful approach. Client-hosted Netcode for GameObjects with
NetworkVariablesynchronization, ownership transfer, andServerRpc/ClientRpccommunication. This became themainbranch and was tagged V1.0.0.
Each migration required re-architecting how player state, object ownership, and mission progression were synchronized. The final Netcode approach was chosen for its tight Unity integration, simpler topology, and first-party support for NetworkObject ownership transfer, critical for VR grab interactions.
Framework Design Decisions
Why a mediator pattern for XR interactions? XR Interaction Toolkit provides generic grab/socket interactions, but assessment missions need filtered interactions: only the "correct" object should lock into a socket. LockSocketInteractorMediator wraps XRI's filter interfaces (IXRHoverFilter, IXRSelectFilter) to reject non-matching interactables before the physics layer processes them. This keeps mission logic decoupled from XRI internals.
Why async initialization (PreInit/Init/Ready)? Networked VR scenes have ordering dependencies: the server must spawn objects before clients can interact, and clients must be at their spawn points before gameplay begins. The three-phase lifecycle ensures: (1) PreInit configures server-side state, (2) Init spawns and positions everything, (3) Ready validates all clients are synchronized before releasing control.
Why Service Locator over dependency injection? Unity's scene-based architecture makes constructor injection impractical for MonoBehaviour components. The ServiceLocator provides scene-scoped registration/resolution that survives Netcode's spawning lifecycle, where objects are instantiated by the networking layer rather than by application code.
Performance Considerations
- Ownership transfer is used sparingly, only on grab events, to minimize authority handoff overhead
- NetworkVariable is preferred over RPCs for continuous state (flashlight on/off, puzzle visibility) to leverage Netcode's delta compression
- NetworkList for spawn points avoids repeated RPC broadcasts when players connect sequentially
- Physics on the payload (
MovePayload) freezes the Y-axis and disables gravity to prevent drift in networked physics simulation
vv1 — v1.0 — Photon PUN / Fusion Multiplayer
vv1 Highlights
- Explored and evaluated Photon PUN 2 and Photon Fusion 2 as multiplayer networking solutions, building client/server scripts, a BootstrapHandler, and scene transition infrastructure
- Designed the initial MissionSubsystem and PlayerServices architecture with ScriptableObject-based mission data configuration, establishing patterns that persisted through the Netcode migration
- Created the scene flow structure (Initialization, MainMenu, Intermission, Mission scenes) and UI Toolkit foundation used throughout the project lifecycle
vv1 Overview
The primary technical focus was evaluating networking SDKs. Photon PUN 2 was imported first with client/server scripts and voice support, then replaced by Photon Fusion 2 for its improved room-based model. Neither solution provided the server-authoritative control needed for assessment integrity, leading to the Netcode migration in v2.0.
vv1 Technical Breakdown
The Unity 6 (6000.1.0f1) project was configured with:
- OpenXR 1.14.3 as the XR runtime targeting Meta Quest
- XR Interaction Toolkit 3.1.2 for grab, socket, and teleportation interactions
- XR Hands 1.5.1 with HandVisualizer samples for hand tracking
- Universal Render Pipeline 17.1.0 with linear color space for HDR rendering
- Input System 1.14.0 with custom
InputSystem_Actions.inputactionsfor VR controller mapping
Photon Networking Stack
Two Photon SDKs were evaluated in sequence:
Photon PUN 2: Imported with Photon Voice for VOIP. Client and Server scripts were created alongside a
BootstrapHandlerfor application initialization. TheTransitionHandler(made static) managed scene loading between MainMenu and mission scenes.Photon Fusion 2: Imported after PUN 2 removal. The project was rebuilt as a "base project with no multiplayer" before re-integrating Fusion's room-based networking. During this phase,
PlayerServiceswas created withPlayerIDandPlayerAvatarcomponents, andMissionDataScriptableObjects were established for mission configuration.
Early Architecture
- Scene Structure:
0A_Initialization_Scene,0B_MainMenu_Scene,1_Intermission_Scene, and placeholder mission scenes - MissionSubsystem: Initial mission data management with
MissionDataSubsystemandMissionDataScriptableObjects - UI Foundation: UI Elements and UI Toolkit imported with basic menu prefabs
- Asset Pipeline: Polygon asset packs (City, Military, Office, SciFiCity, SciFiSpace, Town, War) imported for environment variety
- TimelineExtensions: Caption support for Timeline-driven narration sequences
vv2 — v2.0 — Netcode for GameObjects
vv2 Highlights
- Designed and built the XVRAssessmentSuite framework, a reusable, namespace-organized mission lifecycle system with async PreInit/Init/Ready phases, interface-driven contracts (IInitialization, IPostInitialization, IXVRTask, IXVRSpawnHandler), and a mediator-pattern XR interaction pipeline
- Migrated networking stack from Photon PUN 2 through Photon Fusion 2 to Unity Netcode for GameObjects (v2.3.2) across 5 iterative attempts, ultimately delivering server-authoritative gameplay with NetworkVariable synchronization, ownership transfer on grab, and ServerRpc/ClientRpc communication
- Integrated Vivox voice chat (v16.6.0), XR hand tracking (v1.5.1), and Timeline-driven narration with captions to create an immersive multiplayer VR experience targeting Meta Quest via OpenXR
- Implemented 4 distinct cooperative mission types (symbol matching with room-based distribution, team cannon physics with payload delivery, obstacle navigation with puzzle gates, and color-matching tutorials), each with networked state, UI Toolkit interfaces, and task completion tracking
vv2 Overview
The project is built on a custom framework called XVRAssessmentSuite, which provides a standardized mission lifecycle (PreInit, Init, Ready), interface-driven task contracts, and a mediator-pattern XR interaction system. Networking is handled through Unity's Netcode for GameObjects with server-authoritative gameplay, Vivox voice chat for player communication, and Timeline-driven narrated introductions for each mission.
The platform targets Meta Quest via OpenXR and XR Interaction Toolkit 3.1.2, with full hand tracking support through XR Hands 1.5.1. The architecture is designed for extensibility: new missions can be added by implementing the framework's interfaces and registering with the Service Locator.
vv2 Technical Breakdown
The core framework is organized into namespaces under XVRAssessmentSuite:
- CoreSubsystems.Base -
MissionHandlerBaseprovides the abstract lifecycle:PreInit()for pre-spawn setup,Init()for scene initialization, andReady()for server-side validation before gameplay begins. FiresOnMissionInitCompleteto signal downstream systems. - CoreSubsystems.Interfaces -
IInitialization,IPostInitialization,IXVRTask(withOnTaskCompleteevent), andIXVRSpawnHandlerdefine contracts for mission components. - CoreSubsystems.Enums -
MissionStateenum tracks lifecycle phases. - MissionSubsystem - Houses
MissionHandlerBaseand mission-specific handlers. - TaskSubsystem - Task completion tracking with event-driven callbacks.
- ServiceLocation - Scene-scoped
ServiceLocatorfor dependency injection across networked scenes. - UIToolkit - UI Toolkit integration layer for mission-specific UI panels.
- Networking - Network state management (renamed from
NetworkingReworkduring v2.0 cleanup). - XR -
XVRBaseInteractorMediatorandXVRBaseInteractableMediatorproviding mediator-pattern XR interaction filtering.
Networking Architecture
The multiplayer stack uses Netcode for GameObjects 2.3.2 with a server-client topology:
- NetworkObject ownership model:
GrabTransferOwnershipchangesNetworkObject.OwnerClientIdon XR grab events viaChangeOwnershipServerRpc(), enabling any player to manipulate any interactable. - NetworkVariable synchronization: mission state (obstacle visibility in
PuzzleOne, flashlight state inFlashlightInteractableHandler, color assignments inColorHandler) is synced automatically. - NetworkList:
MissionOneSpawnHandlerusesNetworkList<Vector3>to manage spawn point allocation across clients. - ServerRpc/ClientRpc: All authoritative actions (cannon firing in
CannonManager, catapult rotation inMoveCatapult, puzzle activation inMissionThree) route through ServerRpc with ClientRpc for broadcast.
Mission Implementations
Mission 0 - Color Matching (Tutorial): ColorHandler assigns random materials to objects and targets via server-authoritative NetworkVariable. Players grab objects and place them in matching color slots. Correct placements lock via LockSocketInteractorMediator.
Mission 1 - Symbol Matching: MissionHandler initializes rooms based on player count, uses SymbolObjectFactory to spawn symbol objects across 6 cube faces (Front, Back, Left, Right, Top, Bottom). NewRoomHandler tracks local and global correct interactables. MissionOneSpawnHandler allocates spawn points via NetworkList. MissionOneUIHandler displays status via UI Toolkit with TimelineHandler countdown.
Mission 2 - Cannon Payload: CannonManager spawns networked ammo with NetworkObject.Spawn() and applies Rigidbody.AddForce() impulse. MoveCatapult handles server-controlled rotation in 4 directions. MovePayload applies physics forces to push the payload left/right with Y-axis frozen. CollisionDetector fires win condition. UIManager displays team scores and 60-second countdown.
Mission 3 - Obstacle Navigation: WaypointMover moves a payload along a Waypoint hierarchy path with pause/resume via ServerRpc. PuzzleOne, PuzzleTwo, PuzzleThree each manage obstacle visibility through NetworkVariable<bool> arrays. PuzzleManager implements IXVRTask to detect payload boundary triggers and fire OnTaskComplete. MissionThree orchestrates puzzle progression and timer management.
XR Interaction Pipeline
The interaction system extends XR Interaction Toolkit with a mediator pattern:
LockSocketInteractorMediatorextendsXVRBaseInteractorMediator, implementsIXRHoverFilterandIXRSelectFilterto restrict socket interactions to a configured allowed-interactable set. On correct interaction, firesOnTaskCompleteviaIXVRTask.FlashlightInteractableHandlerextendsXVRBaseInteractableMediatorwithNetworkVariable<bool>light state synchronized viaToggleLightServerRpc().
Scene Flow
0B_MainMenu_Scene -> 1_Intermission_Scene -> M_Mission_0_Scene (tutorial) -> M_Mission_1_Scene -> M_Mission_2_Scene -> M_Mission_3_Scene. Each mission scene is loaded with its own MissionHandler registered in the ServiceLocator.
Voice Communication
Vivox 16.6.0 provides in-game voice chat with channel management tied to the multiplayer session lifecycle.
Audio System
Main_AudioMixer routes mission-specific SFX (collision sounds, button presses, door effects, lighting SFX, UI hover/click sounds) and narration audio through dedicated mixer groups. Timeline-driven narration plays mission introductions and instructions with synchronized captions.