SuperController Singleton

SuperController- The heart of VAM — manages scenes, objects, interactions, and overall system control. SuperController is a core component of the VAM system that inherits from Unity’s MonoBehaviour. If VAM was a brain, this is the prefrontal cortex. It is implemented as a singleton, meaning there is only one active instance of SuperController available globally throughout the application.

It is implemented as a singleton, which means that only one instance exists at any time and can be accessed globally through the static property SuperController.singleton.

Core Responsibilities

  • Dynamic UI Creation:
    SuperController provides methods such as CreateDynamicUIElement, CreateDynamicButton, CreateDynamicToggle, and CreateDynamicSlider to instantiate and manage dynamic UI elements from predefined prefabs. These functions handle the instantiation, parenting, and validation of UI components, ensuring that the user interface is created consistently throughout the application.
  • Scene and UI Synchronization:
    It manages various aspects of scene control by toggling UI elements based on the current state. For example, methods like SyncAdvancedSceneEditModeTransforms and SyncUIToUnlockLevel adjust which UI elements are active based on user permissions, scene editing mode, and global scene options. This synchronization ensures that features such as advanced scene editing, saving, or loading are only accessible when allowed.
  • Integration with Global Scene Options:
    Many of SuperController’s properties (e.g., disableAdvancedSceneEdit, disableSaveSceneButton, etc.) are designed to interact with a global options singleton (GlobalSceneOptions.singleton). This integration allows the controller to enforce application-wide settings, adapting the available functionality based on the current license level or user settings.
  • Centralized Logging and Error Handling:
    The class includes extensive logging and error management functionalities. It provides methods for logging messages and errors, ensuring that the state of the application is consistently monitored and that issues can be diagnosed quickly.
  • Asset and Package Management:
    SuperController also plays a role in handling assets and packages, managing directories for saves and demos, and interfacing with file browsers to select or store scene data. This allows for seamless loading, saving, and packaging of scenes within the application.
  • Input and Navigation Handling:
    SuperController is deeply integrated with input systems. It manages various input actions (including VR-specific actions) for navigation, selection, and manipulation of scene elements. It also coordinates the behavior of different controller types (e.g., VR controllers, mouse input) and supports features like teleports, free movement, and object selection.

Singleton Implementation

Private Instance:
The class defines a private static variable (commonly named _singleton) to hold its single instance.

This template is intended only to demonstrate how VAM organizes its core functionality. It serves as an educational reference for understanding the underlying design of Virt-A-Mate.

public class SuperController : MonoBehaviour
{
    private static SuperController _singleton;
    // ... rest of the class
}

Global Access: The singleton instance is typically exposed via a public static property (e.g., SuperController.singleton). This property allows any part of the code to access the single SuperController instance.

Usage Examples

Logging Messages

Use static methods provided by SuperController for logging:

SuperController.LogMessage("Informational log");
SuperController.LogError("Error log");
// Note: VaM does not have a separate "warning" log method; errors are commonly logged as LogError.

//Examples:
SuperController.LogMessage("Button pressed!");
SuperController.LogError("Intensity storable not found!");

These methods centralize log outputs, ensuring consistent handling of informational and error messages.

Accessing Scene Elements

Retrieve scene elements (referred to as “Atoms” in VAM) using the singleton:

Atom invisibleLight = SuperController.singleton.GetAtomByUid("InvisibleLight");

Add a new Atom to a VAM scene

SuperController.singleton.AddAtomByType(string type, bool userInvoked = false, bool forceSelect = false, bool forceFocus = false);
Parameter Description
type Name of the atom type to spawn (InvisibleLight, Cube, etc.).
userInvoked Set to true if the user directly clicked a button to create the atom (used for undo/redo tracking).
forceSelect Set to true if you want to automatically select the atom after it’s created (very useful for quick editing).
forceFocus Set to true if you want to move the camera to focus on the new atom immediately (very useful for user feedback).

Return Value:

void: The method does not return a reference to the spawned atom.

Additional Notes:

Why it returns void: Internally, this method starts a coroutine to spawn the atom asynchronously. As a result, you cannot capture a reference to the new atom directly from this call.

Working with the spawned atom: If you need to manipulate the spawned atom (e.g., change its position), you must retrieve its reference by other means (such as using GetAtomByUid or scanning the scene’s atom list after the spawning process completes).

Example Usage

using UnityEngine;

public class SpawnCubePlugin : MVRScript
{
    public override void Init()
    {
        // Spawn a Cube atom. Note: No reference is returned.
        SuperController.singleton.AddAtomByType("Cube", false, false, false);
        SuperController.LogMessage("Cube atom spawned. Retrieve its reference separately if needed.");
        
    }
}

⚠️ Incorrect usage

// Incorrect usage – this will not compile because AddAtomByType returns void.
Atom newAtom = SuperController.singleton.AddAtomByType("Cube", false, false, false);

Moving Atoms in VAM Scenes

In Virt-A-Mate (VaM), every atom is a scene object with a Transform component, just like a standard Unity GameObject. You can change an atom’s position by modifying its Transform’s position property.

How to Move an Atom:

Obtain a Reference: Use methods such as GetAtomByUid, GetContainingAtom(), or iterate over GetSceneAtoms() to retrieve a reference to the atom you wish to move.

Set the New Position: Once you have the reference, update its position by assigning a new Vector3 value. For example Move the atom to position (2, 2, 2):

//This code moves the atom to coordinates (2, 2, 2) in the scene.
atom.transform.position = new Vector3(2f, 2f, 2f);

Optional Adjustments: Similarly, you can adjust the atom’s rotation using transform.eulerAngles or its scale using transform.localScale.

Example Usage: Below is a sample plugin that uses a JSONStorableAction for the “Move Cube” button. This action, when triggered, searches for an atom with UID “Cube” and moves it to position (2,2,2). If the atom isn’t found, an error is logged.

using UnityEngine;

public class MoveCubeAtomPlugin : MVRScript
{
    private JSONStorableAction moveCubeAction;

    public override void Init()
    {
        // Create a JSONStorableAction for moving the Cube atom.
        moveCubeAction = new JSONStorableAction("MoveCube", MoveCube);
        RegisterAction(moveCubeAction);

        // Create a button in the plugin UI and bind it to the JSONStorableAction.
        CreateButton("Move Cube", false).button.onClick.AddListener(() => moveCubeAction.actionCallback.Invoke());
    }

    private void MoveCube()
    {
        // Retrieve the Cube atom by its UID.
        Atom cubeAtom = SuperController.singleton.GetAtomByUid("Cube");

        if (cubeAtom != null)
        {
            // Move the Cube atom to position (2, 2, 2).
            cubeAtom.transform.position = new Vector3(2f, 2f, 2f);
            SuperController.LogMessage("Cube atom moved to (2,2,2)!");
        }
        else
        {
            SuperController.LogError("Cube atom not found.");
        }
    }
}

Key Points

Global Accessibility: The singleton pattern ensures that all plugins and components access the same SuperController instance.

Decoupling: Plugins do not need to inherit from SuperController to use its methods. Instead, they reference SuperController.singleton for core functionalities like logging and scene management.