Extended Reality
Facility 14
VR suspense experience: a facility control room with 14 surveillance monitors, cinematic NPC sequences, and immersive HDRP post-processing
A VR horror/suspense experience built in Unity with HDRP, featuring a facility control room with 14 dynamic surveillance monitors, cinematic NPC sequences driven by Timeline, and immersive post-processing effects. Originally developed at Shenandoah University.
- My role
- Sole Developer
- Period
- Feb 2023 – Mar 2023
- Platforms
- Meta Quest / PC VR
- Status
- delivered
Tools & technologies
Impact & Results
- Delivered a complete VR experience from concept to final build in six weeks as a solo developer
- Produced 145 commits across the development cycle, with peak productivity sessions of 30+ commits in a single day during major integration milestones
- Achieved full HDRP lightmapping across 8 lightmap sets, covering the control room, hallways, and connecting spaces
- Integrated 12 Mixamo animation clips with custom Timeline sequencing for the NPC cinematic system
- Mixed spatial audio across three distinct zones (main menu, player, control room) for environmental immersion
- Optimized the final build with a cleanup pass that reduced asset footprint by approximately 8,000 lines of tracked content
Overview
Facility 14 is a VR horror/suspense experience that places the player inside a facility control room surrounded by 14 surveillance monitors. As the experience unfolds, monitors shift unpredictably between live feeds, static, and darkness while an unidentified scientist NPC drives the narrative through cinematic sequences. The project explores how virtual reality can create tension and unease through environmental storytelling, dynamic visual states, and post-processing effects like chromatic aberration and simulated eye blinks.
Built in Unity with HDRP as a solo developer project at Shenandoah University, Facility 14 combines real-time VR interaction (teleportation, hand tracking, grab mechanics) with Timeline-orchestrated cinematics to deliver a self-contained narrative experience targeting Meta Quest and PC VR platforms.
Role Summary
- As sole developer, designed and built every system in the project from the ground up. Created the control room environment with custom PBR materials sourced from Ambientcg at 4K resolution, baked 8 lightmap sets for the game scene, and configured HDRP rendering settings for visual fidelity on VR hardware.
- Wrote all C# gameplay scripts: the MonitorHandler for monitor state management, PostProcessingHandler for effect animation, ActivateTeleportationRay and ActivateGrabRay for XR locomotion, AnimateHandOnInput for hand tracking, and SetPlayerHeight for dynamic character controller calibration. Composed all Timeline cinematic sequences, integrated Mixamo animations for the NPC character, mixed spatial audio across three audio source zones, and implemented the 12-signal event system connecting cinematics to gameplay state.
Non-Technical Summary
This project is a virtual reality experience designed to create a sense of suspense and unease. When you put on a VR headset, you find yourself standing in a facility control room. The room is filled with monitors showing camera feeds from around the building. As the experience progresses, the monitors begin to behave unpredictably: some flicker to static, others go dark, and you never know which ones will change next.
An unidentified scientist character appears and moves through the space, triggering events and sound cues that advance the story. The experience uses visual effects like screen distortion and simulated eye blinks to make you feel like something is wrong, pulling you deeper into the atmosphere.
The project was built over six weeks as part of a university research course exploring how virtual reality can be used to create immersive narrative experiences. Everything from the environment design to the character animations to the programming was handled by a single developer.
Highlights
- Designed and developed a VR suspense experience in Unity with HDRP, delivering a fully lightmapped control room environment with 14 dynamically-controlled monitor displays across 145 commits over six weeks
- Engineered a MonitorHandler system managing three distinct visual states (active feed, TV static, disabled) with randomized transition logic to create unpredictable suspense pacing
- Built a coroutine-driven PostProcessingHandler controlling chromatic aberration intensity and vignette-based player blink animations for immersive horror effects
- Integrated XR Interaction Toolkit with custom ActivateTeleportationRay and ActivateGrabRay controllers, coordinating teleportation, grab interactions, and hand-tracked input across Meta Quest and PC VR platforms
- Orchestrated NPC-driven cinematic sequences using Unity Timeline with a 12-signal event system, synchronizing Mixamo character animations, spatial audio cues, and environmental state changes
Quick Highlights
- 14 dynamically-controlled monitors with randomized state transitions for suspense pacing
- HDRP rendering with full lightmapping across 8 lightmap sets and custom post-processing effects
- NPC cinematic system driven by 12 Timeline signals orchestrating animation, audio, and environment changes
- Hand-tracked VR interactions with animated finger models driven by controller trigger and grip input
- Immersive horror effects: chromatic aberration, vignette blink, TV static, and distortion shaders
- Complete VR locomotion suite with teleportation rays and context-aware grab interactions
Technical Breakdown
Monitor Display System: The MonitorHandler class manages 14 MeshRenderer references, each representing a monitor in the control room. It exposes six public methods that transition monitors between three material states: control room camera feed, TV static, and disabled (black screen). Methods like TVStaticSomeCameras() use Random.Range to select a subset of monitors for state changes, creating unpredictable visual patterns. TVStaticSomeCamerasAndActivateTheRest() and RemoveTVStaticFromSomeCameras() provide granular control for cinematic sequencing.
Post-Processing Effects: The PostProcessingHandler drives two HDRP Volume overrides: ChromaticAberration and Vignette. A coroutine-based PlayerBlink() method interpolates the vignette intensity from 0 to 1 and back over configurable durations, simulating an eye blink. Chromatic aberration intensity is set directly through public methods called by Timeline signals. The handler includes cleanup logic in OnApplicationQuit() to reset effect values and prevent stale state in the editor.
XR Locomotion: Two complementary scripts manage VR movement. ActivateTeleportationRay listens to an InputActionProperty for activation, then checks via raycast whether the player is hovering over an interactable object before enabling the teleportation line. ActivateGrabRay monitors an XRDirectInteractor's selection state and toggles the grab ray's GameObject visibility accordingly, hiding it when the player is holding something.
Hand Animation: AnimateHandOnInput reads two InputActionProperty values each frame (pinch/trigger and grip) and writes them as float parameters to an Animator component. The Oculus hand models use blend trees driven by these "Trigger" and "Grip" parameters to produce smooth finger curl animations.
Player Height Calibration: SetPlayerHeight runs in FixedUpdate to maintain physics consistency. It reads the VR camera's local y-position, clamps it between 0.5 and 2.0 units, and applies it as the CharacterController.height with the center offset at half-height. This ensures the collision capsule matches the player's real-world stance.
Timeline and Signal Architecture: The main cinematic sequence (Game_Cinematic.playable) uses 12 signal assets to trigger gameplay events at specific points in the timeline. Signals fire methods on MonitorHandler, PostProcessingHandler, and AudioHandler, coupling the narrative playback to environmental state changes without hard-coding timing in scripts.
Systems Used
- Monitor Display State System: Controls 14 monitor renderers across three visual states (active feed, TV static, disabled) with randomized transition logic for suspense pacing
- XR Locomotion Manager: Coordinates teleportation ray and grab ray activation based on controller input and current interaction state
- Post-Processing Animation System: Coroutine-driven HDRP effect controller managing chromatic aberration intensity and vignette-based player blink animations
- Hand Animation Input System: Maps XR controller trigger and grip analog values to hand model animator parameters for real-time finger animation
- Player Height Calibration: Physics-aware character controller adjustment that tracks VR headset position and clamps player height within safe bounds
- Timeline Cinematic System: Signal-driven playable sequences orchestrating NPC animations, audio cues, and environmental state changes for narrative progression
Deep Dive
Environment Design and Rendering: The control room and hallway environments were built using custom 3D models with PBR materials at 4K resolution sourced from Ambientcg. The material library includes concrete, brick, diamond plate metal, leather, plastic, and office ceiling textures, each with full albedo, normal, and roughness maps. HDRP was configured with linear color space, 8 lightmap sets covering the game scene, and custom volume profiles for post-processing. The rendering pipeline uses separate light layers and decal layers for fine-grained visual control.
Monitor System Architecture: The MonitorHandler is the central gameplay system. It holds references to 14 MeshRenderer components and two Material references (control room camera feed and TV static). Six public methods provide different transition patterns: EnableAllCameras() and DisableAllCameras() for bulk state changes, TVStaticAllCameras() for full disruption, and three randomized methods that select subsets of monitors. TVStaticSomeCameras() picks 2-5 monitors for static, TVStaticSomeCamerasAndActivateTheRest() creates a mixed state, and RemoveTVStaticFromSomeCameras() restores 6-8 monitors. These methods are called by Timeline signals to create escalating tension throughout the cinematic sequence.
Post-Processing Pipeline: The PostProcessingHandler accesses HDRP's VolumeProfile at runtime to drive two effects. Chromatic aberration is controlled by setting the intensity value directly, called from Timeline signals at narrative beats. The vignette system is more complex: PlayerBlink() is a coroutine that lerps vignette intensity from 0 to 1 over a configurable rise duration, holds briefly, then lerps back to 0. This simulates an involuntary eye blink, reinforcing the horror atmosphere. The handler overrides volume component states on initialization and resets them in OnApplicationQuit() to prevent editor state pollution.
XR Interaction Architecture: The locomotion system uses two scripts that coordinate with XR Interaction Toolkit's built-in components. ActivateTeleportationRay subscribes to an input action for activation. On each frame where the action is held, it performs a raycast to check whether the player is pointing at an interactable. If no interactable is detected, the teleportation ray line renderer activates, allowing the player to target a teleportation destination. ActivateGrabRay takes a simpler approach: it monitors the XRDirectInteractor's hasSelection property and toggles the grab ray GameObject off when the player is holding an object, preventing visual clutter during manipulation.
Hand Tracking Integration: The Oculus hand models are driven by AnimateHandOnInput, which reads analog trigger and grip values from the input system each frame. These float values are written to an Animator's "Trigger" and "Grip" parameters, which drive blend trees in the hand animation controller. The result is smooth, proportional finger curl that matches the player's physical grip on the controller.
Character and Animation System: The Unidentified Scientist NPC uses a custom model with 8 material slots (beard, body, bottom, gloves, hat, mask, shoes, top). Animations were sourced from Mixamo and include walking with a briefcase, walking while talking, walking with a device, and working-on-something idle variations. These clips are composed in the Game_Cinematic Timeline asset, which grew to over 1,300 lines of sequencing data across iterative refinement passes.
Audio Design: Three audio source zones partition the soundscape: main menu ambient, player-local effects, and control room environmental audio. An audio mixer manages volume balancing across zones. Voice lines and background sounds were integrated through Timeline audio tracks, synchronized with NPC animation clips and environmental state changes. The AudioHandler script provides programmatic access to these sources, though the final implementation relies primarily on Timeline-driven playback.
Scene Architecture: The project contains five scenes with distinct purposes. Game_Scene_v1 is the primary experience (463 KB, the largest scene) containing the control room, monitors, NPC, and cinematic sequences. MainMenu_Scene_v2 serves as the entry point. Animations_Scene_v1 was used for isolated animation testing during development. OutdoorsScene provides an exterior environment. The scene hierarchy uses custom tags (Right Hand, Left Hand) and layers (Lights, Hallway, Hallway Ceiling) for rendering and interaction filtering.
Development Timeline: The project was completed in approximately six weeks with 145 commits. Development progressed through distinct phases: scene setup and material creation (week 1), asset integration and animation framework (week 2), heavy animation and audio work (weeks 3-4), polish and effects (week 5), and final camera systems and optimization (week 6). The final commit included a significant cleanup pass with more deletions than additions, indicating asset optimization and unused content removal.