Skip to content

PoliceVR

VR Police Decision-Making Training Simulator

ROLE: Lead XR Developer
PERIOD: Sep 2023 — Present
PLATFORMS: PC VR / Meta Quest
STATUS: ACTIVE
DOMAIN: Extended Reality
C#UnityXR Interaction ToolkitUniversal Render PipelineTimelineOpenXRXR HandsInput SystemUI ToolkitAR FoundationAI Navigation
PoliceVR cover

Overview

PoliceVR v2 is a complete architectural rewrite of the VR police training simulator, shifting focus from consent-search scenarios to use-of-force decision training. Built on Unity 6 with XR Interaction Toolkit 3.2.1, it places trainees in high-stress situations where they must decide whether to use lethal force, tracking their decision against the actual threat level to evaluate judgment accuracy.

The rewrite introduces an async/Task-based ScenarioDirector orchestration framework with abstract PhaseDirector pattern, replacing the original coroutine architecture. A novel enum-driven token system (SubtitleStateHub) enables dynamic subtitle resolution, allowing Timeline-authored dialogue to adapt based on player state (holster side chosen, suspect object type, player decision) without requiring code changes.

The training flow guides officers through holster selection and weapon familiarization in a tutorial phase, then presents a scenario where a suspect may or may not be armed, requiring a shoot-or-hold-fire decision under time pressure.

Non-Technical Summary

The second version of PoliceVR is a completely rebuilt training simulator focused on one of the most critical decisions a police officer can face: whether or not to use lethal force. Officers put on a VR headset and go through a structured training experience that starts with choosing which side to wear their holster, learning how to draw and fire their weapon, and then entering a realistic scenario.

In the scenario, the trainee encounters a suspect who may or may not be holding a weapon. The officer must quickly assess the situation and decide whether to fire or hold. The system tracks exactly what happened (what the suspect was holding, what the officer decided, and the timing of that decision) so trainers can review performance and discuss decision-making in the debrief.

What makes this version special is that the dialogue and feedback automatically adjust based on what happened. If the officer chose a left-hand holster and the suspect had a phone instead of a weapon, the debrief narration reflects those specific details. This creates a personalized training experience without requiring trainers to manually customize each session.

Highlights

  • Architected and built a complete ground-up rewrite of a VR police use-of-force training simulator, replacing coroutine-based flow with an async/Task orchestration framework supporting CancellationToken-based lifecycle management
  • Designed an enum-driven decision tracking system (ForceDecisionHandler, SubtitleStateHub) that captures player choices (shoot/hold-fire), suspect state, and holster configuration to drive dynamic narrative branching in real time
  • Engineered a dynamic subtitle pipeline with rule and template modes, enabling Timeline-authored dialogue to resolve contextually based on player state tokens without code changes
  • Built a modular XR weapon system with dual permission gates (IXRSelectFilter) and Timeline-driven interaction windows, allowing designers to precisely control when players can draw and fire weapons during scenarios
  • Implemented a comprehensive UI Toolkit design system with USS stylesheets for main menu, tutorial, disclaimer, and overlay interfaces, replacing legacy canvas-based UI throughout the application

Quick Highlights

  • Complete ground-up rewrite from coroutine-based prototype to async/Task orchestration with CancellationToken support
  • Novel enum-driven token system for dynamic subtitle resolution; dialogue adapts to player state without code changes
  • Dual-gate XR weapon system with Timeline-controlled interaction windows for precise designer control
  • Abstract PhaseDirector pattern enabling plug-and-play scenario phases (Tutorial, Cinematic, Runtime)
  • Full UI Toolkit adoption with comprehensive USS design system replacing legacy canvas UI
  • DontDestroyOnLoad decision handler persists player state across scene transitions seamlessly

Technical Breakdown

ScenarioDirector Orchestration Framework

The ScenarioDirector is the master controller managing the full scenario lifecycle: Tutorial → Intro Cinematic → Runtime → Outro Cinematic → Completion. It uses async/await with CancellationToken for clean lifecycle management and broadcasts events (OnPhaseBegin, OnPhaseEnd, OnCinematicFinished, OnScenarioCompleted, OnScenarioAborted) for loose coupling. Configuration is driven by ScenarioDirectorAsset ScriptableObjects that define scene names, cinematic prefabs, and decision handler prefabs per scenario type.

PhaseDirector Pattern

Abstract PhaseDirector base class provides coroutine-to-Task bridging via PhaseGate, a custom YieldInstruction that gates auto-completion until explicitly opened. Concrete implementations include ForceTutorialDirector (569 lines, managing holster choice → lesson prompt → weapon draw → fire practice gates), ForceRuntimeDirector (weapon monitoring, decision commitment, Timeline signal integration), and CinematicDirector (Timeline playback orchestration).

Enum-Driven Decision System

ForceDecisionHandler extends ScenarioDecisionHandlerBase (a DontDestroyOnLoad persistent handler) tracking three enum states: HolsterPosition (Left/Right), SuspectObject (None/Weapon/Phone), and PlayerDecision (None/Shoot/HoldFire). At the intro cinematic phase, ChooseRandomSuspectObject() selects what the suspect holds. During runtime, CommitPlayerDecision() records the player's choice. PushAllTokens() synchronizes all state to the subtitle system.

SubtitleStateHub Token Engine

A DontDestroyOnLoad singleton managing enum-based token sources via generic Set<TEnum>(value), TryGet<TEnum>(out value), and Clear<TEnum>() methods. The hub caches EnumTokenSource instances keyed by enum type, enabling any component to push state that subtitle clips can resolve. This creates a decoupled communication channel between game logic and narrative content.

Dynamic Subtitle Pipeline

DynamicSubtitleBehaviour supports two resolution modes: Rule mode evaluates SlotValueMatch conditions against SubtitleStateHub tokens to select the first matching DynamicSubtitleCase; Template mode replaces TokenRef placeholders in text strings with current enum values. SubtitleTokenProvider uses reflection to cache all enum types with [SubtitleToken] attributes, providing a discoverable registry for the editor UI.

WeaponEngine XR Controller

WeaponEngine implements IXRSelectFilter with two independent permission gates: isInteractionAllowed (can grab/select weapon) and isFireAllowed (can fire when activated). Audio feedback includes safety on/off and shoot clips. The WeaponInteractionTrack custom Timeline track drives both gates frame-by-frame, allowing designers to create precise interaction windows within authored sequences.

XR Hip Holster System

XRHipHolster supports three follow modes: HipBone (reads from animator skeleton), CameraBody (HMD-relative positioning), and RigTransform (XROrigin-based). Position and rotation are smoothly LERPed with configurable weights. Side-flip animation handles left/right transitions via ForceDecisionHandler.OnHolsterSideChosen events, with ground-relative height and lateral/backward offset calculations.

UI Toolkit Design System

Full UI Toolkit adoption with UXML documents and USS stylesheets organized into a design system: palette variables, sizing constants, component styles, overlay layouts, and tutorial-specific layouts. Handlers include DisclaimerUIHandler, MainMenuUIHandler, and ForceTutorialUIHandler. The TransitionHandler manages fade overlays via UIDocument opacity with input blocking during transitions.

Custom Editor Tools

Comprehensive editor tooling: DynamicSubtitleTrackEditor and DynamicSubtitleClipEditor for authoring conditional subtitles, ForceDecisionHandlerEditor for inspecting decision state, PhaseDirectorEditor for runtime phase debugging, ScenarioDirectorEditor for scenario configuration, and TokenRefDrawer for enum token property selection.

Systems Used

  • ScenarioDirector Orchestration Framework - Async/Task-based master controller managing phase-based scenario execution with CancellationToken support and event broadcasting
  • PhaseDirector Pattern - Abstract base class for phase executors with coroutine-to-Task bridging and PhaseGate synchronization primitives
  • Enum-Driven Decision System - Type-safe enum token system tracking player decisions, suspect objects, holster positions, and fire timing through ForceDecisionHandler
  • SubtitleStateHub Token Engine - DontDestroyOnLoad singleton managing enum-based token sources for dynamic subtitle text resolution across scene transitions
  • Dynamic Subtitle Pipeline - Timeline-integrated subtitle system with rule and template modes resolving contextual dialogue based on player state via DynamicSubtitleBehaviour
  • WeaponEngine XR Controller - IXRSelectFilter-based weapon system with dual permission gates for interaction and firing, driven by Timeline WeaponInteractionTrack
  • XR Hip Holster System - Three-mode weapon placement system (HipBone, CameraBody, RigTransform) with smooth LERP transitions and side-flip animation
  • UI Toolkit Design System - Comprehensive USS-based design system with palette, sizing, component styles, overlays, and tutorial-specific layouts

Impact & Results

  • Delivered a production-ready VR use-of-force training simulator with fully automated decision tracking and contextual narrative feedback
  • Reduced scenario authoring effort by enabling designers to create branching dialogue entirely in Timeline without code changes via the dynamic subtitle token system
  • Eliminated 100% of legacy coroutine-based flow control, replacing it with cancellable async/Task orchestration for reliable lifecycle management
  • Built a modular scenario framework supporting plug-and-play phase directors, enabling rapid development of new training scenarios beyond use-of-force
  • Created comprehensive custom editor tooling that accelerates content authoring for subtitle tracks, decision handlers, and scenario configuration

Deep Dive

Motivation for the Rewrite

The v1 prototype validated the concept of VR-based police training but revealed significant architectural limitations. Coroutine-based flow control couldn't be cleanly cancelled, making it impossible to abort scenarios mid-execution. The MonoBehaviour-heavy design coupled scenario logic tightly to Unity lifecycle methods. The signal-driven Timeline integration created implicit dependencies that were difficult to maintain. And the canvas-based UI was inflexible for the increasingly complex interface requirements. The v2 rewrite addressed all of these systematically.

Async/Task Migration Strategy

The most fundamental architectural change was replacing coroutines with async/await throughout the scenario pipeline. ScenarioDirector.RunScenarioAsync() orchestrates the full phase sequence with a CancellationToken that propagates through every phase director and transition handler. This enables clean abort-on-demand: if a scenario needs to be interrupted, a single cancellation request cleanly unwinds the entire execution stack. The PhaseDirector abstract base class bridges the gap by wrapping legacy coroutine patterns in Task-based wrappers, allowing gradual migration of phase implementations.

The PhaseGate Synchronization Primitive

PhaseGate is a custom YieldInstruction that acts as a manual gate within phase coroutines. A phase coroutine yields on a gate, blocking progress until external code calls gate.Open(). This pattern enables the tutorial to wait for specific player actions (holster choice, weapon draw, fire practice completion) without polling. The ForceTutorialDirector uses a sequence of gates: holster selection gate → lesson prompt acceptance gate → weapon draw gate → fire practice completion gate, creating a clean state machine within coroutine syntax.

Enum Token Architecture

The dynamic subtitle system's token architecture is the project's most novel design. Instead of hardcoding dialogue variants, the system uses enum types as token keys. SubtitleStateHub stores the current value of each enum type (e.g., HolsterPosition.Left, SuspectObject.Weapon, PlayerDecision.Shoot). When a DynamicSubtitleBehaviour evaluates during Timeline playback, it can operate in two modes:

Rule mode: Each DynamicSubtitleCase has a list of SlotValueMatch conditions (e.g., "SuspectObject == Weapon AND PlayerDecision == Shoot"). The first case whose conditions all match against SubtitleStateHub wins, and its text is displayed. This enables branching narrative without code.

Template mode: Text contains TokenRef placeholders that are resolved at runtime by querying SubtitleStateHub for the current enum value's display string. For example, "You drew from your {HolsterPosition} holster" becomes "You drew from your Left holster."

The SubtitleTokenProvider uses reflection to discover all enum types marked with [SubtitleToken] attributes, building a registry that the custom editor UI uses for dropdown selection. This makes the system fully extensible: adding a new enum type automatically makes it available in the subtitle authoring tools.

Weapon Interaction Design

The WeaponEngine uses a dual-gate permission model that separates "can interact with the weapon" from "can fire the weapon." This distinction is critical for training: during the tutorial, players can grab and holster the weapon before being allowed to fire. During runtime, both gates are controlled by WeaponInteractionTrack clips in Timeline, giving designers frame-accurate control over when weapons become interactive and lethal. The IXRSelectFilter interface integrates directly with XR Interaction Toolkit's selection pipeline, preventing interaction at the framework level rather than through game logic checks.

Persistent Decision Handler Pattern

ScenarioDecisionHandlerBase uses DontDestroyOnLoad to persist across scene transitions, which is critical because the scenario spans multiple scenes (MainMenu → Cinematic → Tutorial → Runtime → Cinematic). The base class uses reflection-based auto-wiring to find and rebind to the ScenarioDirector, TutorialDirector, and RuntimeDirector after each scene load. ForceDecisionHandler extends this with the specific enum state for force training: HolsterPosition, SuspectObject (randomly chosen at intro), and PlayerDecision (committed during runtime). All state is pushed to SubtitleStateHub via PushAllTokens() to keep narrative content synchronized.

Scene Architecture

The v2 scene structure is deliberately minimal: 0_MainMenu_Scene (selection/disclaimer), 1_Cinematic_Scene (reusable for both intro and outro via prefab swapping), 2_Force_Tutorial_Scene (weapon familiarization), and 3_Force_Runtime_Scene (active scenario). The cinematic scene is reused rather than duplicated. ScenarioDirectorAsset specifies separate intro and outro prefabs that are instantiated into the same scene, reducing maintenance burden and ensuring consistent cinematic playback behavior.

UI Toolkit Migration

The v2 rewrite fully adopted UI Toolkit, replacing all legacy canvas-based UI. The design system is organized into USS stylesheets covering palette variables (colors, opacity), sizing constants, component-level styles (buttons, labels, panels), overlay layouts (fade, transition), and tutorial-specific arrangements. UXML documents define the structure while USS handles presentation, enabling theme changes without touching layout. The TransitionHandler leverages UIDocument opacity for fade effects with input blocking, providing cleaner transitions than the v1 canvas overlay approach.


vv1 Highlights

  • Designed and built a VR police training simulation prototype at Shenandoah University using Unity and XR Interaction Toolkit, establishing the foundational scenario-based training architecture
  • Engineered a coroutine-based scenario execution framework with Timeline-driven cinematics, enabling designers to author branching training sequences without code changes
  • Built a custom Timeline subtitle track system for synchronized dialogue playback during VR scenario walkthroughs, supporting timed text overlays across multiple scenario phases
  • Integrated OpenXR-based hand tracking and interaction systems for immersive police training, including weapon handling and environmental interaction mechanics

vv1 Overview

PoliceVR v1 was the initial prototype of a VR police training simulation developed at Shenandoah University's SCiL (Simulation and Collaboration in Learning) lab. The application placed trainees in an interactive consent-search scenario where they practiced proper procedures for traffic stops and consent encounters.

Built on Unity 2022.3 with XR Interaction Toolkit and OpenXR, the prototype used a coroutine-based scenario flow driven by Unity Timeline signals. Scenario logic was managed through a MonoBehaviour-heavy architecture with ScenarioHandler and ConsentScenarioHandler classes controlling outcome branching based on player actions.

The project established the core training loop: briefing cinematic → interactive scenario → outcome debrief, with custom subtitle tracks for synchronized dialogue and a fade-based transition system between phases.

vv1 Technical Breakdown

Scenario Execution Framework

The core architecture was built around a ScenarioHandler base class with ConsentScenarioHandler as the concrete implementation. Scenario flow was managed through Unity coroutines, with phase transitions driven by Timeline signal events. The handler used switch-case outcome branching to determine scenario results based on player actions during the interactive phase.

Timeline Subtitle System

A custom PlayableTrack implementation consisting of SubtitleTrack, SubtitleClip, and SubtitleMixer. The mixer blended subtitle clip weights to display timed text overlays during scenario playback. A SubtitleBoxUIHandler managed a player-following UI canvas that tracked the user's position in VR space.

UI and Transition Layer

The UIHandler managed fade in/out transitions between scenario phases using canvas-based overlays. Signal receivers on Timeline assets triggered phase transitions, fade events, and subtitle display changes. Scene management was handled through direct SceneManager calls within the coroutine flow.

XR Integration

Built on XR Interaction Toolkit with OpenXR backend. Hand presence detection enabled interaction with scenario objects. The XR Origin prefab was configured with locomotion providers and interaction managers for the training environment. 3D environments were assembled from Synty Polygon asset packs (Heist, Office, Town, City).


vv2 — v2.0 — Force Scenario Rewrite

vv2 Highlights

  • Architected and built a complete ground-up rewrite of a VR police use-of-force training simulator, replacing coroutine-based flow with an async/Task orchestration framework supporting CancellationToken-based lifecycle management
  • Designed an enum-driven decision tracking system (ForceDecisionHandler, SubtitleStateHub) that captures player choices (shoot/hold-fire), suspect state, and holster configuration to drive dynamic narrative branching in real time
  • Engineered a dynamic subtitle pipeline with rule and template modes, enabling Timeline-authored dialogue to resolve contextually based on player state tokens without code changes
  • Built a modular XR weapon system with dual permission gates (IXRSelectFilter) and Timeline-driven interaction windows, allowing designers to precisely control when players can draw and fire weapons during scenarios
  • Implemented a comprehensive UI Toolkit design system with USS stylesheets for main menu, tutorial, disclaimer, and overlay interfaces, replacing legacy canvas-based UI throughout the application

vv2 Overview

PoliceVR v2 is a complete architectural rewrite of the VR police training simulator, shifting focus from consent-search scenarios to use-of-force decision training. Built on Unity 6 with XR Interaction Toolkit 3.2.1, it places trainees in high-stress situations where they must decide whether to use lethal force, tracking their decision against the actual threat level to evaluate judgment accuracy.

The rewrite introduces an async/Task-based ScenarioDirector orchestration framework with abstract PhaseDirector pattern, replacing the original coroutine architecture. A novel enum-driven token system (SubtitleStateHub) enables dynamic subtitle resolution, allowing Timeline-authored dialogue to adapt based on player state (holster side chosen, suspect object type, player decision) without requiring code changes.

The training flow guides officers through holster selection and weapon familiarization in a tutorial phase, then presents a scenario where a suspect may or may not be armed, requiring a shoot-or-hold-fire decision under time pressure.

vv2 Technical Breakdown

ScenarioDirector Orchestration Framework

The ScenarioDirector is the master controller managing the full scenario lifecycle: Tutorial → Intro Cinematic → Runtime → Outro Cinematic → Completion. It uses async/await with CancellationToken for clean lifecycle management and broadcasts events (OnPhaseBegin, OnPhaseEnd, OnCinematicFinished, OnScenarioCompleted, OnScenarioAborted) for loose coupling. Configuration is driven by ScenarioDirectorAsset ScriptableObjects that define scene names, cinematic prefabs, and decision handler prefabs per scenario type.

PhaseDirector Pattern

Abstract PhaseDirector base class provides coroutine-to-Task bridging via PhaseGate, a custom YieldInstruction that gates auto-completion until explicitly opened. Concrete implementations include ForceTutorialDirector (569 lines, managing holster choice → lesson prompt → weapon draw → fire practice gates), ForceRuntimeDirector (weapon monitoring, decision commitment, Timeline signal integration), and CinematicDirector (Timeline playback orchestration).

Enum-Driven Decision System

ForceDecisionHandler extends ScenarioDecisionHandlerBase (a DontDestroyOnLoad persistent handler) tracking three enum states: HolsterPosition (Left/Right), SuspectObject (None/Weapon/Phone), and PlayerDecision (None/Shoot/HoldFire). At the intro cinematic phase, ChooseRandomSuspectObject() selects what the suspect holds. During runtime, CommitPlayerDecision() records the player's choice. PushAllTokens() synchronizes all state to the subtitle system.

SubtitleStateHub Token Engine

A DontDestroyOnLoad singleton managing enum-based token sources via generic Set<TEnum>(value), TryGet<TEnum>(out value), and Clear<TEnum>() methods. The hub caches EnumTokenSource instances keyed by enum type, enabling any component to push state that subtitle clips can resolve. This creates a decoupled communication channel between game logic and narrative content.

Dynamic Subtitle Pipeline

DynamicSubtitleBehaviour supports two resolution modes: Rule mode evaluates SlotValueMatch conditions against SubtitleStateHub tokens to select the first matching DynamicSubtitleCase; Template mode replaces TokenRef placeholders in text strings with current enum values. SubtitleTokenProvider uses reflection to cache all enum types with [SubtitleToken] attributes, providing a discoverable registry for the editor UI.

WeaponEngine XR Controller

WeaponEngine implements IXRSelectFilter with two independent permission gates: isInteractionAllowed (can grab/select weapon) and isFireAllowed (can fire when activated). Audio feedback includes safety on/off and shoot clips. The WeaponInteractionTrack custom Timeline track drives both gates frame-by-frame, allowing designers to create precise interaction windows within authored sequences.

XR Hip Holster System

XRHipHolster supports three follow modes: HipBone (reads from animator skeleton), CameraBody (HMD-relative positioning), and RigTransform (XROrigin-based). Position and rotation are smoothly LERPed with configurable weights. Side-flip animation handles left/right transitions via ForceDecisionHandler.OnHolsterSideChosen events, with ground-relative height and lateral/backward offset calculations.

UI Toolkit Design System

Full UI Toolkit adoption with UXML documents and USS stylesheets organized into a design system: palette variables, sizing constants, component styles, overlay layouts, and tutorial-specific layouts. Handlers include DisclaimerUIHandler, MainMenuUIHandler, and ForceTutorialUIHandler. The TransitionHandler manages fade overlays via UIDocument opacity with input blocking during transitions.

Custom Editor Tools

Comprehensive editor tooling: DynamicSubtitleTrackEditor and DynamicSubtitleClipEditor for authoring conditional subtitles, ForceDecisionHandlerEditor for inspecting decision state, PhaseDirectorEditor for runtime phase debugging, ScenarioDirectorEditor for scenario configuration, and TokenRefDrawer for enum token property selection.