Extended Reality
Gesture Interface
Kinect-powered gesture recognition display for interactive content browsing
A gesture-driven interactive display system built in Unity that integrates Microsoft Kinect v2 skeleton tracking with UI Toolkit to deliver an immersive, multi-modal content browsing experience. Users navigate paired content displays through physical gestures, with automatic idle slideshows and crowd modes for passive viewing.
- My role
- Lead XR Developer
- Period
- Jul 2022 – Dec 2025
- Platforms
- Windows
- Status
- delivered
Tools & technologies
Impact & Results
- Eliminated tight coupling across the entire codebase by introducing dependency injection and event-driven communication, making every component independently testable and replaceable. The startup pipeline's graceful fallback design ensures the application runs in degraded mode (local content, no Kinect) rather than crashing when infrastructure is unavailable. The SafeEvent pattern eliminated a class of runtime crashes where one faulty event subscriber could bring down the entire application.
Overview
Gesture Interface is an interactive display system that uses Microsoft Kinect v2 skeleton tracking to let visitors browse content through physical gestures. When a user steps into the detection zone, paired content displays respond to their body movements, enabling hands-free navigation through images, text, and media. When no one is present, the system automatically transitions to an idle slideshow or crowd mode for passive viewing.
Developed at the Shenandoah Center for Immersive Learning for deployment in public-facing exhibition spaces, the project has evolved through three major versions: from a proof-of-concept to a production system with event-driven architecture, dependency injection, and extracted reusable packages. It is built in Unity with UI Toolkit and designed for unattended operation on Windows display hardware.
Role Summary
- Led the complete architectural redesign as sole developer. Designed the event-driven state management system with AppFlowBehaviour. Created the dependency injection framework with ServiceRegistry supporting [AutoRegister] and [Inject] attributes. Built the StartupProcessProvider sequential pipeline. Refactored all mode handlers, content services, and body rig systems to use interface-based composition.
Non-Technical Summary
Under the hood, this version was about making the system professional-grade. Previously, the different parts of the application talked to each other directly: the gesture detector would call the display handler, which would call the content loader. If any piece changed, everything connected to it had to change too.
In v3.0, the entire system was redesigned so that components communicate through events and interfaces rather than direct connections. Think of it like upgrading from a telephone tree (where each person calls the next) to a bulletin board system (where anyone can post updates and anyone can read them). This makes it much easier to add new features, fix bugs, or swap out components without breaking everything else.
Highlights
- Architected an event-driven state machine (AppFlowBehaviour) with queued mode transitions, transition scoping, and SafeEvent invocation to eliminate race conditions and exception cascades across 3 runtime modes
- Designed and implemented a dependency injection framework using [AutoRegister] and [Inject] attributes for scene-scoped service resolution
- Built a sequential startup pipeline with 7 ordered initialization steps (service validation, environment checks, config loading, server health, content processing, and Kinect initialization) with graceful fallbacks at each stage
- Refactored all mode handlers to interface-driven composition with ModeOrchestrator coordinating IAppCommands and IAppActivity for fully decoupled mode lifecycle management
Quick Highlights
- Event-driven state machine with SafeEvent preventing exception cascades
- Dependency injection via [AutoRegister] and [Inject] attributes
- Sequential 7-step startup pipeline with graceful fallbacks
- Interface-driven mode orchestration with IAppCommands/IAppActivity
- Thread-safe async/await with UnityMainThread synchronization
Technical Breakdown
Event-Driven State Machine: AppFlowBehaviour implements IAppFlow and manages runtime mode transitions through an event-driven architecture. Mode change requests are queued during active transitions, preventing race conditions. TransitionScope tracks ownership of active transitions, and when a handler begins a transition, it acquires a scope that must be disposed before new transitions can proceed. SafeEvent wraps event invocation to catch and log exceptions from individual subscribers without breaking the event chain, preventing a single faulty handler from crashing the entire application. Events include ModeChanged, TransitionChanged, and InteractionOccurred.
Dependency Injection: The ServiceRegistry provides scene-scoped dependency injection. Services are registered using [AutoRegister] attributes on MonoBehaviours, which are discovered at scene load. Consumer scripts use [Inject] attributes on fields to receive resolved services. The registry validates that all required services are registered during the startup pipeline, failing fast with clear error messages if dependencies are missing. This replaced the previous pattern of direct GetComponent and singleton references throughout the codebase.
Sequential Startup Pipeline: StartupProcessProvider defines a 7-step ordered initialization sequence: (1) Validate Services: ensure all critical services are registered, (2) Validate Environment: check persistent storage paths, layer setup, and UI document availability, (3) Load Settings: async config initialization from AppConfigAsset ScriptableObject, (4) Check Server: HEAD request to content server for availability, (5) Process Content: load local content or trigger remote download based on server availability, (6) Kinect Init: initialize Kinect sensor with timeout fallback for environments without hardware, (7) Complete: transition to initial runtime mode. Each step reports progress to the InitializationUIHandler.
Config Registry: AppConfigProvider loads configuration from AppConfigAsset ScriptableObjects, supporting runtime reload via ConfigReloaded events. The AppConfig structure provides typed access to server settings (URL, credentials), content paths, body rig configuration, and idle timing parameters. Thread-safe async initialization ensures config is available before dependent systems start.
Mode Orchestrator: ModeOrchestrator implements both IAppCommands (for requesting mode changes and marquee transitions) and IAppActivity (for reporting user interaction). This centralizes mode coordination: InputHandler calls IAppCommands.RequestMarquee(), GestureHandler subscribes to IGestureHandler events, and the orchestrator manages the lifecycle. This replaced the previous pattern where handlers directly referenced each other.
Threading: UnityMainThread provides thread-safe async/await support, ensuring callbacks from async operations return to Unity's main thread. CancellationToken integration enables clean shutdown of long-running operations during mode transitions or application exit.
Systems Used
- Event-Driven State Machine: AppFlowBehaviour with queued mode transitions, transition scoping, and SafeEvent invocation for decoupled runtime state management
- Service Registry Dependency Injection: Scene-scoped service registration with [AutoRegister] and [Inject] attributes for loose coupling and testability
- Sequential Startup Pipeline: Ordered startup process with validation, config loading, server checking, content processing, and Kinect initialization steps
- Config Registry System: ScriptableObject-based configuration with runtime reload support, async initialization, and ConfigReloaded events
- Content Service Architecture: Modular content pipeline with ContentParser, ContentMaterializer, ContentCarousel, and PersistentContentStorage components
- Body Rig Priority System: Multi-body skeleton tracking with priority manager, zone interactors, interaction gates, and boundary policies
- Marquee Animation Engine: Direction-aware slide transitions with PanelPair double-buffering, MarqueeAnimator coroutines, and transition scope locking
- Mode Orchestrator Pattern: ModeOrchestrator implementing IAppCommands and IAppActivity interfaces for centralized mode handler coordination
Deep Dive
The v3.0 architectural overhaul was driven by the codebase having grown complex enough that tight coupling was causing cascading breakages during modifications.
Event System Design: The decision to use an event-driven architecture over a traditional state machine pattern was motivated by the need for multiple independent systems to react to state changes without knowing about each other. AppFlowBehaviour publishes ModeChanged events, and any number of subscribers, including UI handlers, body rig system, and content service, can respond independently. The SafeEvent wrapper was introduced after encountering a production issue where an exception in one subscriber's handler prevented all subsequent subscribers from receiving the event, effectively breaking the entire application from a single error.
Dependency Injection Approach: Rather than adopting a heavyweight DI framework like Zenject, a lightweight custom ServiceRegistry was built specifically for Unity's MonoBehaviour lifecycle. The [AutoRegister] attribute automatically discovers and registers services during scene initialization, while [Inject] resolves dependencies before Start() is called. This approach was simpler to debug than reflection-heavy frameworks and had minimal performance overhead. The registry supports scene-scoped lifetimes, which aligns with Unity's scene-based architecture.
Startup Pipeline Architecture: The sequential startup pipeline solved a recurring problem: initialization order dependencies. Previously, scripts relied on Unity's non-deterministic Awake()/Start() ordering, leading to intermittent null reference errors when services initialized in the wrong order. StartupProcessProvider makes the order explicit and adds validation at each step. The fallback design (skip Kinect if hardware is absent, use local content if server is unreachable) was critical for development environments where not all infrastructure is available.
Refactoring Strategy: The refactoring was executed in small, incremental commits rather than a single large rewrite. Each commit introduced one architectural change (e.g., "Implement EventSystem framework", "Add ServiceRegistry system", "Refactor BaseHandler to use [Inject]"), making it possible to bisect regressions. The body rig system, content service, and mode handlers were each refactored independently to use the new patterns, validating the approach at each step.