Programming Event Logic for VR Game Levels
We program event logic for VR game levels. It's not just scripting triggers in OnTriggerEnter — it's managing narrative order, environment state, NPC reactions, and tutorial prompts in a three-dimensional space where the player can look anywhere, stand anywhere, and interact with objects in any order. A linear script breaks down by the third step: the player picked up an item before a dialogue triggered — and the system doesn't know its current state.
Why Simple Event Logic Breaks in VR
The most common problem is state serialization via boolean flags. bool doorOpened, bool npcGreeted, bool puzzleSolved in a GameManager. With 15 flags, combinatorial bugs start: the npcGreeted flag is set, but doorOpened is not, yet the player is already inside — because they walked through a wall on re-entry. Debugging this without an explicit state model is nearly impossible.
A second case is concurrent events. The player picks up a key and simultaneously steps on a door trigger. Both events fire in the same frame; OnTriggerEnter and XRGrabInteractable.SelectEntered execute in an indeterminate order. If the logic doesn't account for ordering, state desynchronizes.
A third problem is specific to VR simulators: skipping mandatory steps. The instructor designed a required sequence, but in VR the user can physically perform step 5 before step 2. We need a system that either softly blocks premature actions or adapts the scenario to the actual order of user actions.
How to Build a Fault-Tolerant Event System
The foundation is a State Machine with explicit states and transitions. Not flags, but enum LevelState { Introduction, PuzzleActive, DoorUnlocked, Completed } with methods like TryTransition(LevelState target).
For complex non-linear scenarios, we use a hierarchical State Machine or Behavior Tree:
-
BehaviorDesigneror a custom BT for NPC reactions - For overall level logic — our own
LevelOrchestratorbuilt onIEnumerator-based coroutines orUniTask
Event Bus — a central event broker. All level components publish events to the bus without knowing about each other: EventBus.Publish(new KeyPickedUpEvent(keyId)). LevelOrchestrator subscribes to relevant events and updates the State Machine. This breaks direct dependencies between Trigger components and the Orchestrator.
We implement this via event Action<T> or ScriptableObject-based EventChannel (pattern from Unite Austin 2017): [CreateAssetMenu] KeyPickedUpEventChannel : EventChannelBase<KeyPickedUpEvent>. Each EventChannel is a separate asset; references between components are wired through the Inspector — not via Find().
Checkpoint system — critical for simulators: each completed step is serialized into SessionData. On replay or resume after pause, the state is restored exactly. We save not flags, but a snapshot of the State Machine state plus a list of completed events with timestamps.
| Approach | Implementation Complexity | Bug Robustness | Flexibility |
|---|---|---|---|
| Boolean flags | Low | Low | Low |
| State Machine | Medium | High | Medium |
| Behavior Tree + Event Bus | High | Very High | High |
Specifics of VR Narrative
In VR, the player looks wherever they want, so the classic "narrative event in the center of the screen" doesn't work. We need mechanisms for gently directing attention:
Spatial Audio Cue: Sound emanates from a point of interest — the player naturally turns. Implemented via AudioSource with 3D spatial blend + ReverbZone.
Peripheral Attention Trigger: A bright effect (particles, light) in the peripheral field of view — more effective than an arrow UI indicator.
NPC Look At: The NPC looks at the player and starts dialogue only when the player looks at it (angle < 45°). Checked via Vector3.Dot(playerHeadForward, directionToNPC). This prevents dialogue starting "from behind."
From a specific case: in a VR fire safety trainer, the player must perform 7 evacuation steps in strict order. Initially the logic used flags — during testing, 40% of users found a way to break the scenario. After rewriting to an explicit State Machine with TryTransition(), validation of the order became part of the architecture, not a set of if-checks. Error-free completion rate rose from 55% to 89%.
Debugging and Tools
Custom Level State Viewer — an Editor Window that displays the current State Machine state in real time during Play Mode. A list of active events, transition history, pending events in the queue. Without this tool, debugging event logic is like Debug.Log() in the dark.
Event Log: all events are recorded with timestamps and stack traces into a circular buffer. When a bug occurs, immediate answer to "what happened before this?"
What’s Included in Our Work
- Designing the State Machine and state diagram
- Implementing Event Bus with subscriptions via ScriptableObject
- Integration with XR Interaction Toolkit, Unity Analytics
- Developing debugging tools (Level State Viewer, Event Log)
- Testing edge cases: parallel events, skipped steps, replay
- Scenario documentation and API for extension
Work Process
- Scenario analysis. We dissect the level logic, identify states, events, and dependencies.
- State Machine design. State diagram and transitions before any code.
- Development. EventBus, Orchestrator, integration with VR components (XR Interaction Toolkit events).
- Debugging tools. Level State Viewer, Event Log.
- Testing. All edge cases: parallel events, skipped steps, replay.
| Level Scale | Indicative Timeline |
|---|---|
| Linear scenario, 5–10 events | 1–2 weeks |
| Non-linear level, 20–40 events | 3–5 weeks |
| Complex simulator with BT and checkpoint system | 2–4 months |
Pricing is calculated after scenario analysis and assessment of transition logic complexity. Contact us for a project evaluation — we work turnkey, guaranteeing logic stability. Over 5 years of VR development experience and more than 15 completed projects.





