Skip to main content

Unified Menu System

1. Overview

The Unified Menu System is the core architecture within the HoloMIT SDK for creating and managing User Interfaces (UI). Its design is based on an MVC (Model-View-Controller) structure, allowing developers to configure menus once and deploy them dynamically across multiple platforms, natively supporting both Desktop (2D PC) and Virtual Reality (VR).

The system consists of a unified central controller (SystemMenuController) acting as the action database, and multiple "Views" (DesktopMenuView and VRWristMenuView) that interpret and render this data based on the user's hardware.


2. SystemMenuController (Central Controller)

This is the brain of the menu system. It operates under a Singleton pattern (SystemMenuController.Instance), allowing any UI instantiated at runtime (e.g., a spawned multiplayer player prefab) to connect to it and fetch button configurations without breaking references.

2.1. Inspector Reference

Inspector SystemMenuController Figure 1: The SystemMenuController component in the Unity Inspector. Shows the panel configuration and the dynamic list of Menu Actions.

(Note: These hardcoded panel dependencies are temporary and will be abstracted in future versions. See Section 8).

  • Generic Buttons Panel (GameObject): The UI container where the auto-generated buttons will be grouped and instantiated.
  • Self Calibration Panel (GameObject): A static, pre-designed panel containing complex interfaces (sliders, toggles) that are not generated via code. The controller manages the transition between this panel and the generic buttons.
  • Menu Actions (List): The central database of your menu. This is where developers configure what buttons exist, their appearance, and the code they execute, using the MenuActionConfig data model.

2.2. Public API Reference

  • public List<MenuActionConfig> GetActiveMenuActions() Returns a filtered list containing only the buttons marked as visible. Called by the Views upon initialization.
  • public void OpenCalibrationPanel() / ReturnToMainMenu() Toggles the visibility between the main menu and the static calibration panels.
  • public void RequestCalibrationMenuOpen() Triggers the global event OnCalibrationMenuRequested. Ideal to be called from Inspector UnityEvents to safely communicate with instantiated prefabs.

3. MenuActionConfig (Data Model)

To avoid manually creating buttons on a Canvas, the system uses serialized configurations. Each element in the Menu Actions list of the SystemMenuController represents a button.

3.1. How to Add a New Button (Step-by-Step)

Adding a new button to the UI is entirely done through the Unity Inspector, without touching any UI Canvas or Prefab:

  1. Select the SystemMenuController GameObject in your scene.
  2. In the Inspector, locate the Menu Actions list.
  3. Click the + icon at the bottom right of the list to add a new MenuActionConfig element.
  4. Expand the new element and configure its Appearance:
    • Type the Button Name (e.g., "Reset Session").
    • Assign a Button Icon (optional).
    • Ensure Is Visible is checked.
  5. Configure its Execution Logic:
    • If this is a destructive action, check Requires Confirmation and type a warning message (e.g., "All unsaved progress will be lost. Continue?").
  6. In the On Execute () UnityEvent box, click the + sign.
  7. Drag the target GameObject/Script into the empty slot and select the method you want to execute from the dropdown menu.
  8. Play the scene. The button will automatically appear across all instantiated views (Desktop, VR, and any custom views you have created)!

Detailed Property Breakdown:

  • Button Name (string): The visible text on the button.
  • Button Icon (Sprite): Optional icon. It can be used alongside text or independently.
  • Is Visible (bool): If unchecked, the button will not be generated in the final view, allowing options to be hidden during development without deleting the logic.
  • Requires Confirmation (bool): If active, clicking the button will not execute the action immediately. Instead, it will open the GenericConfirmDialog.
  • Confirmation Message (string): The warning text displayed in the dialog.
  • On Execute (UnityEvent): The event fired when the button is clicked (or after accepting the confirmation dialog).

4. UI Views (Cross-Platform Rendering)

The "Views" inherit from the abstract class BaseMenuView, which contains the common logic to instantiate button prefabs (GenericButtonView) and connect them to the confirmation system (GenericConfirmDialog).

4.1. DesktopMenuView

Implementation designed for 2D screens (PC/Mac).

Inspector DesktopMenuView Figure 2.1: DesktopMenuView configuration. Allows mapping the specific input action (the "X" key) to open the menu and customizing the floating activator button.

Runtime DesktopMenuView Figure 2.2: DesktopMenuView Example.

  • Menu Panel: Reference to the background and main container of the 2D menu.
  • Toggle Menu Action: Integrates Unity's new Input System to open/close the menu via keyboard (mapped to the "X" key).
  • Activator Button: A permanently visible, small floating UI button used to deploy the menu with the mouse. When opening the menu, this view automatically manages unlocking and showing the system cursor (Cursor.lockState).

4.2. VRWristMenuView

Volumetric implementation designed for Virtual Reality, meant to be anchored to the player avatar's wrist.

Inspector VRWristMenuView Figure 3.1: Advanced VR wrist menu configuration, integrating physical gaze systems, the "X" button controller mapping, and static calibration panels.

Inspector VRWristMenuView Figure 3.2: Advanced VR wrist menu preview.

  • Toggle Menu Action: Maps the menu opening action to the VR Controller (mapped to the "X" button on Meta controllers).
  • VR Activator Button: A floating holographic button the user can physically press with their virtual finger. Unlike the Desktop version, this button only appears when the menu is closed AND the user is looking at their wrist.
  • Static Pre-designed Panels & Calibration Buttons: Allows mapping internal references (Reset, Save, Cancel) of the static calibration panels. Upon initialization, it injects logic via code (C# delegates) to interact with the Calibration.LocalInstance Singleton, ensuring references are not lost in network-instantiated Prefabs.

4.3. Creating a New Custom View (e.g., Mobile Android)

Because of the MVC architecture, creating a new UI for a completely different platform (like a mobile touchscreen) is straightforward. You only need to create a new View; the central controller and data remain untouched.

Step-by-step Guide:

  1. Create the Script: Create a new C# script (e.g., MobileMenuView.cs) that inherits from BaseMenuView.
  2. Handle Input: Implement your platform-specific input logic (e.g., detecting a screen tap to toggle the menu visibility).
  3. Fetch Data: In your Start() method, check for SystemMenuController.Instance, fetch the active actions, and pass them to the BuildMenu() base method.
using System.Collections.Generic;
using UnityEngine;

namespace Holo.Core
{
public class MobileMenuView : BaseMenuView
{
[Header("Mobile UI")]
[SerializeField] private GameObject mobilePanel;

private void Start()
{
// 1. Fetch data from the Singleton dynamically
if (SystemMenuController.Instance != null)
{
List<MenuActionConfig> activeActions = SystemMenuController.Instance.GetActiveMenuActions();

// 2. This base method auto-generates all the buttons!
BuildMenu(activeActions);
}
}

// 3. Add your touch-input logic to show/hide the mobilePanel here...
}
}
  1. Setup the Prefab: Create a new UI Canvas in Unity, attach your new MobileMenuView script.
  2. Link References: Assign the required base references in the inspector (layoutContainer for the layout group, buttonPrefab for your custom mobile button design, and confirmDialog). The system will handle the rest!

5. VR UX Modules (User Experience Modules)

To ensure the VR menu feels organic, the wrist view relies on two critical sub-modules that handle spatial interaction:

5.1. WristLookDetector

Calculates the spatial orientation between the player's wrist and their head.

  • Main Camera: Dynamically finds the main XR camera (Camera.main). Its logic gracefully handles camera loading delays common in VR frameworks.
  • Activation Tolerance Angle (float): The vision cone (in degrees) within which the user is considered to be "looking" at the wrist (default: 45º).
  • Logic Flow: Provides a public property IsCurrentlyLooking that the menu constantly polls to decide whether to show or hide the holographic activator button.

5.2. WristMenuVisibility

Replaces abrupt panel toggling (SetActive) with smooth, immersive transitions.

  • Requires a CanvasGroup component on the UI.
  • Uses a coroutine (FadeCanvas) governed by FadeSpeed to animate the UI's Alpha value.
  • Automatically blocks interaction Raycasts when the panel is invisible, preventing ghost clicks.

6. Auxiliary Components (Reusable Elements)

The system uses modular UI components to facilitate maintenance:

  • GenericButtonView: The base prefab for all buttons. It auto-configures itself (text, icon, events) and automatically hides graphic elements that are not being used (e.g., if a button only has text and no icon).
  • GenericConfirmDialog: A generic modal warning panel. It intercepts the user's action, displays the configured ConfirmationMessage, and only executes the UnityEvent if the user clicks "Confirm", closing itself automatically.

7. Setup Guidelines & Common Pitfalls

When configuring new UI Canvases (especially for VR), Unity's default UI system can cause silent interaction bugs if not set up correctly. Keep the following rules in mind:

  • VR Input Module: Do NOT use Unity's standard Standalone Input Module or UI Input Module in your EventSystem if you are targeting VR. You MUST replace it with the XR UI Input Module. Otherwise, your laser pointers and direct pokes will be ignored.
  • Graphic Raycaster: Ensure that the root Canvas of any Menu View has a Graphic Raycaster component attached. Without this, the Canvas cannot detect any raycasts from controllers or mice.
  • Raycast Blocking (Invisible Walls): If your buttons are not responding, check your UI hierarchy for transparent or decorative images (like backgrounds or spacers). Ensure that the Raycast Target checkbox is set to false for any UI element that is not meant to be interacted with. A transparent image over a button will block the raycast.
  • Physical Poking in VR: To interact with World Space UI buttons physically (touching them with the avatar's finger), the player's hand controller needs an XR Poke Interactor component configured properly, along with a Collider that acts as the physical interaction tip.

8. Future Roadmap & Next Steps

The Unified Menu System is actively evolving. The following architectural upgrades are planned to increase modularity and scalability:

  • Abstract Panel Routing: Currently, the SystemMenuController holds hardcoded dependencies for specific panels (such as the Generic Buttons and Self Calibration panels). A planned update will abstract this routing directly into the MenuActionConfig. Developers will be able to drag and drop a target UI Panel directly into the button's configuration in the Inspector, making the central controller completely agnostic to the panels it manages.
  • Categorized Tab System: To prevent UI clutter as the application grows, the current single-panel layout (where all buttons coexist) will be upgraded to a Tab-based architecture. Menu actions will be categorized into dedicated main tabs (e.g., General, Session Settings, Calibration Settings, User Settings), providing a cleaner and much more scalable User Experience.