F01


Learning Outcomes

  • Explain how activation events work in Unity’s XR and UI systems. Before class, review how components like XRBaseInteractable and Button expose UnityEvents such as OnSelectEntered() and OnClick() to trigger animations, sounds, or state changes.
  • Apply event-driven design principles in Unity XR projects. In preparation, explore the roles of events, listeners, and handlers, and set up UnityEvents to call specific methods—for example, triggering robot animations in a state-based system.
  • Implement feedback mechanisms to enhance user interactions. Ahead of the session, experiment with visual, audio, and haptic cues to make system responses clear, using tools like the RobotFeedbackManager to play sounds or highlight objects.
  • Design contextual interactions based on system state. Come prepared by creating or testing conditional flows where activation inputs are gated by readiness states, and add clear visual and audio alerts when interactions are blocked.

Activation Events

Activation events in VR refer to specific triggers or signals that are generated when a user interacts with a virtual object or environment in a meaningful way. These events typically indicate a change in state or the initiation of an action based on user input. An activation event might occur when a user presses a button on a VR controller while grasping a virtual object, causing the object to perform an action such as opening, lighting up, transforming, or being thrown. They are used to provide feedback (e.g., trigger sounds, change indicator lights, or update display panels), initiate processes (e.g., start machinery, toggle system states, or signal a change in a simulation), and decouple actions (e.g., separate the user input from the actions performed, enabling modular programming). Key principles include:

  • Event-Driven Design: Activation events follow an event-driven design. When an event is detected (such as a button press), one or more functions execute. This modularity simplifies management and adapts well to complex engineering workflows.

    At the welding station of XFactory, a user presses a number on the robot control box’s touch numpad to initiate the heavy payload robot arm. The button press event triggers two modular actions: one that transitions the robot’s animation state (e.g., to “Pick”), and another that logs the action for monitoring.

  • Feedback Mechanisms: Effective VR systems use both auditory (sounds) and visual (lighting, material changes) feedback. These cues are vital in engineering environments for communicating machine states and ensuring operator awareness.

    When the heavy payload robot arm at the welding station begins moving, the status light on the controller box switches from green to red, and a mechanical hum plays to signal motion. When the robot returns to idle, the light turns green again and a soft chime sounds.

  • Contextual Interactions: Activation events should reflect the current system state, enabling or restricting interactions based on context (e.g., safety conditions, system readiness). This ensures realistic and safe behavior, mirroring industrial safety protocols.

    01

In the welding station of XFactory, pressing a virtual button on the Controller box with a touch-enabled numpad can trigger activation events for the Heavy Payload Robot Arm. For instance, pressing 1 could activate the Pick animation by setting the corresponding trigger in the Animator Controller (event-driven design). Upon activation, the system plays a confirmation beep, the Status Light indicator on the Controller box changes from green to red, and the Heavy Payload Robot Arm GameObject begins moving to pick up the car body from the trolly (feedback mechanism). If the emergency stop button at the welding station is engaged, pressing any number on the numpad will not activate the robot. Instead, a red flashing light is shown and an error buzzer sounds, indicating that activation is disabled until the emergency stop is cleared (contextual interaction).


Event-Driven Design

Event-driven design is a software architecture pattern in which the flow of control is determined by the occurrence of “events” rather than by a predetermined, linear sequence of instructions. In VR and XR applications—where user actions, physics simulations, and external data streams all happen in real time and often unpredictably—this approach provides responsiveness, modularity, and easier maintenance. Typical workflow is as follows:

  1. Emit: A component (e.g. controller driver) emits an event to the central dispatcher (EventBus).
  2. Dispatch: The EventBus routes the event to all registered listeners whose filters match the event type or payload.
  3. Handle: Each listener invokes its handler logic, often forwarding to domain-specific systems (movement, animation, scoring).

F02

Core Concepts

  • Event: A discrete occurrence or signal that something meaningful has happened. In VR this might be a user squeezing the trigger on a hand-held controller, a collision between two virtual objects, or the completion of an asset download. Events carry payload data—such as position vectors, timestamps, or user IDs—that handlers can use to decide how to respond.

  • Listener / Subscriber: A piece of code that declares interest in one or more event types. Listeners register themselves with the event system at startup or dynamically during gameplay. When the matching event fires, each listener is invoked in turn. This separation lets components focus on their own concerns without polling or tight coupling.

  • Event Handler: The function or method that executes in response to an event. Handlers should be quick to execute and delegate heavy work—for example, updating game state or triggering animations—to specialized systems. Clear handler boundaries help avoid frame drops and maintain smooth VR performance.

  • Modularity: By defining a small set of well-documented event types, you can build self-contained modules (input devices, physics engines, UI panels) that communicate only through those events. This makes it easy to replace or upgrade parts—such as swapping in a new hand-tracking SDK—without rewriting the rest of the application.

  • Decoupling: In an event-driven system, no component needs direct references to others. Instead, any part of the code can emit an event and any other part can listen for it. This loose coupling reduces interdependencies, simplifies unit testing (you can emit synthetic events), and avoids ripple effects when requirements change.

Implementation

At the welding station of XFactory, the user controls a heavy-duty robot arm using a numpad with four configurable buttons. Each button maps to a different action (e.g., Pick, Turn, Place, Home). These actions trigger the corresponding animation state and update the system’s status indicator. Our goal is therefore to (1) map each numpad button (1–4 GameObjects) to an animation trigger (Pick, Turn, Place, Home) on the robot arm’s Animator, (2) log each activation to the console, and (3) change the status light color based on whether the robot is idle or moving.

  1. Setup the Animator Triggers:
    • Go to the robot arm’s Animator Controller.
    • Add or verify states: Idle, Pick, Turn, Place.
    • Add or verify transitions using Trigger Parameters: "Home", "Pick", "Turn", "Place".

    02

  2. Create the RobotController Script:
    • Prepare a RobotController.cs script to manage the robot arm’s animation triggers and updates a status light to reflect whether the robot is active (moving) or idle. A script can help synchronize visual feedback with animation states in a modular, event-driven way.
     using UnityEngine;
    
     public class RobotController : MonoBehaviour
     {
         public Animator robotAnimator;
         public GameObject statusLight;
         public Material greenMaterial;
         public Material redMaterial;
    
         private Renderer statusRenderer;
    
         // Name of the idle state as it appears in the Animator
         private const string idleStateName = "Robot Arm Idle";
    
         private void Start()
         {
             if (statusLight != null)
                 statusRenderer = statusLight.GetComponent<Renderer>();
         }
    
         private void Update()
         {
             if (robotAnimator == null || statusRenderer == null) return;
    
             bool isInTransition = robotAnimator.IsInTransition(0);
             AnimatorStateInfo currentState = robotAnimator.GetCurrentAnimatorStateInfo(0);
    
             // Light should only be green if not in transition and fully in the idle state
             bool isTrulyIdle = !isInTransition && currentState.IsName(idleStateName);
    
             statusRenderer.material = isTrulyIdle ? greenMaterial : redMaterial;
         }
    
         public void TriggerAnimation(string triggerName)
         {
             if (robotAnimator == null) return;
    
             robotAnimator.SetTrigger(triggerName);
             Debug.Log($"Robot Triggered: {triggerName}");
             // Status light will update automatically in Update()
         }
     }
    
  3. Configure the Script:
    • Attach the script to a GameObject representing the robot (e.g., Heavy Payload Robot Arm).
    • Robot Animator: Assign the Animator component that controls the robot’s state machine.
    • Status Light: Assign Status Light.
    • Green Material: Assign a status material to show the robot is idle.
    • Red Material: Assign a status material used when the robot is moving.

    03

  4. Configure the Numpad Buttons:
    • From each numpad button, locate the OnClick() event,
    • Click the + to add a new event.
    • Drag the robot GameObject (with the RobotController.cs script) into the field.
    • Select RobotController → TriggerAnimation(string).
    • Enter the corresponding animation trigger in the string field (e.g., Home, Pick, Turn, or Place).
    • Repeat this process for each numpad button, assigning the desired trigger to each.

    04

  5. (Optional) Use a Configurable Button Mapping:
    • To make the system flexible, create a script that allows users to assign animation triggers to each numpad button using a dictionary.
    • Update your RobotController script as follows:
     using UnityEngine;
     using System.Collections.Generic;
    
     public class RobotController : MonoBehaviour
     {
         public Animator robotAnimator;
         public GameObject statusLight;
         public Material greenMaterial;
         public Material redMaterial;
    
         private Renderer statusRenderer;
    
         // Name of the idle state as it appears in the Animator
         private const string idleStateName = "Robot Arm Idle";
    
         // Configurable mapping from button numbers to animation triggers
         public Dictionary<int, string> buttonMappings = new Dictionary<int, string>()
         {
             { 1, "Pick" },
             { 2, "Turn" },
             { 3, "Place" },
             { 4, "Home" }
         };
    
         private void Start()
         {
             if (statusLight != null)
                 statusRenderer = statusLight.GetComponent<Renderer>();
         }
    
         private void Update()
         {
             if (robotAnimator == null || statusRenderer == null) return;
    
             bool isInTransition = robotAnimator.IsInTransition(0);
             AnimatorStateInfo currentState = robotAnimator.GetCurrentAnimatorStateInfo(0);
    
             bool isTrulyIdle = !isInTransition && currentState.IsName(idleStateName);
             statusRenderer.material = isTrulyIdle ? greenMaterial : redMaterial;
         }
    
         // This method is called from UI or XR button events
         public void OnButtonPressed(int buttonId)
         {
             if (buttonMappings.ContainsKey(buttonId))
             {
                 TriggerAnimation(buttonMappings[buttonId]);
             }
             else
             {
                 Debug.LogWarning($"Button ID {buttonId} not mapped to any trigger.");
             }
         }
    
         public void TriggerAnimation(string triggerName)
         {
             if (robotAnimator == null) return;
    
             robotAnimator.SetTrigger(triggerName);
             Debug.Log($"Robot Triggered: {triggerName}");
             // Status light updates automatically in Update()
         }
     }
    
  6. Configure the Script:
    • Select the button GameObject in the Hierarchy (e.g., 1, 2, …).
    • In the OnClick() event section, click the + button and drag the robot GameObject (which has the RobotController.cs script attached) into the event field.
    • From the function dropdown, select RobotController → OnButtonPressed(int).
    • Enter the appropriate button number into the parameter field: Button 1 → 1, Button 2 → 2, etc.
    • This will call the correct animation trigger for each button based on the dictionary mapping.

    05

    This setup allows you to change button-to-trigger mappings in one place (in the buttonMappings dictionary) and easily support reconfiguration via menus or UI tools.

  7. Deploy and Test:
    • Deploy the app to your device.
    • Each button press on the XR numpad sends a configurable command to the robot.
    • The robot’s animation state changes via an Animator trigger.
    • A red or green status light reflects active vs. idle states.
    • Console logging supports debugging or monitoring.
    • All interactions are modular, extensible, and VR-ready.

    06


Feedback Mechanisms

In interactive VR systems—particularly in engineering and industrial contexts—feedback mechanisms deliver critical sensory cues that inform users about system status, guide their actions, and help prevent errors. By mimicking or augmenting real-world signals, these mechanisms boost immersion, build operator confidence, and reduce safety risks. In VR engineering scenarios, feedback should emulate industrial realities: machines emit characteristic sounds when they engage, warning indicators flash during overload conditions, and tools vibrate to signal contact or completion—ensuring users react as they would on a real factory floor.

F03

Core Concepts

  • Visual Feedback: Changes in lighting, color, UI elements, or animations that signal state transitions or alerts. For example, a virtual valve might glow green when open and pulse red when overheating; status panels can fade in when a sensor reading crosses a threshold. Visual feedback leverages our spatial vision to highlight important information without breaking immersion.

  • Auditory Feedback: Sound cues—such as motor whirrs, warning beeps, confirmation chimes, or verbal prompts—used to reinforce or substitute visual signals. In a VR factory simulation, machinery startup might be accompanied by a deep hum, and an approaching forklift could emit a horn blast. Audio feedback ensures operators notice out-of-view events and can maintain focus on critical tasks even when their gaze is occupied.

  • Tactile (Haptic) Feedback: Vibration patterns or force feedback delivered through controllers, gloves, or exoskeletons to simulate touch, resistance, or impacts. For instance, you might feel a sharp pulse when a robotic arm grips a component or a sustained rumble when drilling. Haptics convey weight, texture, and collision in VR, reinforcing hand–eye coordination and improving manipulation accuracy.

  • Multimodal Feedback: The coordinated use of visual, auditory, and tactile signals to create redundant and complementary cues. A high-pressure valve might flash a warning light (visual), emit a rising alarm tone (auditory), and trigger a vibration warning in the controller (haptic) simultaneously. By engaging multiple senses, multi-modal feedback minimizes the chance of missed signals and supports faster, more intuitive responses in complex, safety-critical operations.

Implementation

At the welding station of XFactory, the heavy payload robot arm gives the user real-time multi-modal feedback when a robot action is triggered. It includes (1) audio feedback, playing sounds for movement and idle, (2) visual feedback, flashing the button that was last pressed, and (3) haptic feedback, sending a short vibration to the VR controller when the robot begins to move. Here is how to implement it:

  1. Create a Script:
    • Add a RobotFeedbackManager.cs script to a central object in your scene (e.g., an empty RobotFeedbackManager GameObjet), or to the robot itself (Heavy Payload Robot Arm).
    • Copy the following script into your IDE.
     using UnityEngine;
     using UnityEngine.XR.Interaction.Toolkit;
     using UnityEngine.UI;
     using System.Collections;
    
     public class RobotFeedbackManager : MonoBehaviour
     {
         [Header("Audio Feedback")]
         public AudioSource audioSource;
         public AudioClip motorHumClip;
         public AudioClip idleBeepClip;
    
         [Header("Visual Button Feedback")]
         public float flashDuration = 0.5f;
         public Color flashColor = Color.yellow;
         public float flashInterval = 0.1f;
    
         [Header("Haptic Feedback")]
         public XRBaseController xrController;
         public float hapticAmplitude = 0.7f;
         public float hapticDuration = 0.2f;
    
         private Coroutine flashRoutine;
    
         public void OnRobotStartMoving()
         {
             PlaySound(motorHumClip);
             SendHaptics();
         }
    
         public void OnRobotReturnToIdle()
         {
             PlaySound(idleBeepClip);
         }
    
         public void FlashButton(GameObject button)
         {
             if (button == null) return;
    
             // First, try UI button (Image-based)
             Image img = button.GetComponent<Image>();
             if (img != null)
             {
                 if (flashRoutine != null)
                     StopCoroutine(flashRoutine);
    
                 flashRoutine = StartCoroutine(FlashUIRoutine(img));
                 return;
             }
    
             // Fallback to 3D mesh button (Renderer-based)
             Renderer rend = button.GetComponent<Renderer>();
             if (rend != null)
             {
                 if (flashRoutine != null)
                     StopCoroutine(flashRoutine);
    
                 flashRoutine = StartCoroutine(Flash3DRoutine(rend));
             }
         }
    
         private IEnumerator FlashUIRoutine(Image img)
         {
             Color originalColor = img.color;
             float elapsed = 0f;
    
             while (elapsed < flashDuration)
             {
                 img.color = flashColor;
                 yield return new WaitForSeconds(flashInterval);
                 elapsed += flashInterval;
    
                 img.color = originalColor;
                 yield return new WaitForSeconds(flashInterval);
                 elapsed += flashInterval;
             }
    
             img.color = originalColor;
             flashRoutine = null;
         }
    
         private IEnumerator Flash3DRoutine(Renderer rend)
         {
             Material mat = rend.material; // Safe instance
             Color originalColor = mat.color;
             float elapsed = 0f;
    
             while (elapsed < flashDuration)
             {
                 mat.color = flashColor;
                 yield return new WaitForSeconds(flashInterval);
                 elapsed += flashInterval;
    
                 mat.color = originalColor;
                 yield return new WaitForSeconds(flashInterval);
                 elapsed += flashInterval;
             }
    
             mat.color = originalColor;
             flashRoutine = null;
         }
    
         private void PlaySound(AudioClip clip)
         {
             if (audioSource != null && clip != null)
             {
                 audioSource.Stop();
                 audioSource.clip = clip;
                 audioSource.Play();
             }
         }
    
         private void SendHaptics()
         {
             if (xrController != null)
             {
                 xrController.SendHapticImpulse(hapticAmplitude, hapticDuration);
             }
         }
     }
    
  2. Configure the Script:
    • Audio Source: Assign an AudioSource component. Leave the “Audio Clip” field in the AudioSource blank. The script will dynamically assign the appropriate clip at runtime.
    • Motor Hum Clip: Assign a sound to play when the robot starts moving (e.g., mechanical hum or activation tone).
    • Idle Beep Clip: Assign a sound to play when the robot returns to idle (e.g., soft beep or chime).
    • Flash Duration: Set how long the button should flash overall (e.g., 3 seconds).
    • Flash Color: Choose the color that the button should blink (typically yellow for feedback).
    • Flash Interval: Set how fast the button should blink (e.g., 0.2 seconds per on/off cycle).
    • XR Controller: Drag in the LeftHand Controller or RightHand Controller GameObject from your XR Rig.

    07

  3. Trigger Feedback:
    • Select a numpad button GameObject in the Hierarchy (e.g., 1, 2, etc.).
    • In the Button (Script) component, go to the OnClick() event list.
    • Click the + button to add a second event.
    • Drag the robot GameObject (Heavy Payload Robot Arm) into the event slot.
    • From the dropdown, select RobotFeedbackManager → OnRobotStartMoving() (this enables auditory feedback).
    • Click + again to add a third event.
    • Drag the robot GameObject into this slot.
    • From the dropdown, choose RobotFeedbackManager → FlashButton(GameObject) (this enables visual feedback).
    • Drag the same button GameObject into the argument field.
    • Repeat the above for all numpad buttons.

    08

  4. Specify Idle State at the End of Animation:
    • In the Animator, go to the end of your Robot Arm Idle animation.
    • Add an Animation Event to the last frame.
    • Set the function to OnRobotReturnToIdle.

    09

  5. Deploy and Test:
    • When the robot is activated (e.g., via numpad button), the status light turns red, a motor hum sound plays, and the user’s controller vibrates (haptic pulse).
    • When the robot returns to idle (triggered by animation event at the end of the idle animation), the status light turns green, and a beep (or idle motor) sound plays

This feedback loop simulates a real-world factory machine’s behavioral cues, enhancing immersion, safety, and real-time situational awareness for learners.


Contextual Interactions

Contextual interactions are dynamic behaviors in which the available actions, controls, or responses adapt to the current state or condition of the virtual system. In engineering and industrial XR scenarios, these interactions enforce safety protocols, guide users through proper sequences, and ensure that every operation reflects real-world constraints. Such contextual interactions reinforce real-world discipline—only allowing users to tighten a virtual valve once the pressure gauge reads safe levels, or preventing tool activation until protective shielding is verified—so that virtual practice mirrors the careful sequence of industrial procedures.

F04

Core Concepts

  • System State Awareness: The XR environment continuously monitors key parameters—such as machine power status, emergency-stop flags, or calibration levels—and enables interactions only when conditions are valid. For example, a virtual CNC lathe will lock out its control panel until the spindle is fully stationary and safety guards are in place.

  • State-Dependent Behavior: User inputs can be transformed or blocked depending on operational modes. In “maintenance” mode, a virtual robot arm might allow direct joint manipulation, whereas in “production” mode the same gestures instead trigger pre-programmed routines. This ensures users cannot accidentally invoke inappropriate actions for the current context.

  • Safety by Design: Contextual rules simulate industrial interlocks, lockouts, and emergency-stop procedures. Attempting to open a virtual control cabinet without first disabling power might trigger an automatic shutdown or require a confirmation sequence—teaching users the importance of procedural compliance and preventing hazardous mistakes.

  • Feedback on Invalid Interactions: Whenever an action is blocked or modified, the system provides clear, contextual cues: a red overlay on a disabled button, a “lock” icon appearing on a tool handle, an audible warning tone, or a brief on-screen message explaining why the operation is unavailable. This immediate, informative feedback helps users correct their workflow without confusion.

Implementation

At the welding station of XFactory, an emergency stop button governs the entire robot operation. If the e-stop is engaged, the numpad is disabled, any button press triggers a buzz alarm and a flashing red status light, and robot animations are blocked to simulate faulty state. If the e-stop is cleared, however, normal operation resumes, the light returns to solid green, sounds are reset and input is re-enabled. Here is how to implement this behavior:

  1. Create a System Manager Script:
    • Create a new empty GameObject (System Manager) as a child of Heavy Payload Robot Arm.
    • Attach the following script to System Manager to manage the global system state and toggles interaction accordingly.
     using UnityEngine;
     using UnityEngine.Events;
    
     public class SystemStateManager : MonoBehaviour
     {
         public bool isEmergencyStopped = false;
         public UnityEvent onEStopEngaged;
         public UnityEvent onEStopCleared;
    
         public void ToggleEmergencyStop()
         {
             isEmergencyStopped = !isEmergencyStopped;
             if (isEmergencyStopped)
             {
                 Debug.Log("Emergency Stop Engaged");
                 onEStopEngaged?.Invoke();
             }
             else
             {
                 Debug.Log("Emergency Stop Cleared");
                 onEStopCleared?.Invoke();
             }
         }
     }
    
  2. Configure the Script in the Inspector:
    • Leave isEmergencyStopped unchecked (default is normal state).
    • Under onEStopEngaged (when e-stop is pressed), click the + button. Add each numpad button individually. Assign Button → bool interactable and uncheck the box to disable interaction during emergency stop.
    • Under onEStopCleared (when e-stop is reset), click the + button again. Add the same objects. Assign Button → bool interactable and check the box to re-enable interaction when normal operation resumes.

    10

  3. Update RobotController.cs to Check State: -Modify your RobotController.cs to respect the emergency stop:

     using UnityEngine;
    
     public class RobotController : MonoBehaviour
     {
         public Animator robotAnimator;
         public GameObject statusLight;
         public Material greenMaterial;
         public Material redMaterial;
         public SystemStateManager systemStateManager;
         public RobotFeedbackManager feedbackManager;
    
         private Renderer statusRenderer;
    
         // Name of the idle state as it appears in the Animator
         private const string idleStateName = "Robot Arm Idle";
    
         private void Start()
         {
             if (statusLight != null)
                 statusRenderer = statusLight.GetComponent<Renderer>();
         }
    
         private void Update()
         {
             if (robotAnimator == null || statusRenderer == null) return;
    
             bool isInTransition = robotAnimator.IsInTransition(0);
             AnimatorStateInfo currentState = robotAnimator.GetCurrentAnimatorStateInfo(0);
    
             // Light should only be green if not in transition and fully in the idle state
             bool isTrulyIdle = !isInTransition && currentState.IsName(idleStateName);
    
             statusRenderer.material = isTrulyIdle ? greenMaterial : redMaterial;
         }
    
         public void TriggerAnimation(string triggerName)
         {
             if (systemStateManager != null && systemStateManager.isEmergencyStopped)
             {
                 Debug.Log("Blocked: System is in Emergency Stop");
                 feedbackManager?.OnEmergencyBlocked(); // Optional: handle blocked input feedback
                 return;
             }
    
             if (robotAnimator == null) return;
    
             robotAnimator.SetTrigger(triggerName);
             feedbackManager?.OnRobotStartMoving();
             Debug.Log($"Robot Triggered: {triggerName}");
         }
     }
    
  4. Configure the Script:
    • Select the robot GameObject with RobotController.cs.
    • In the System State Manager field, drag in the System Manager GameObject (the one with the SystemStateManager.cs script).
    • In the Feedback Manager field, assign the robot itself (which contains the RobotFeedbackManager.cs script).

    11

  5. Add Emergency Feedback to FeedbackManager.cs:
    • Expand the RobotFeedbackManager.cs to support flashing and buzzing.
     private Coroutine flashingCoroutine;
    
     public AudioClip errorBuzzClip;
    
     public void OnEmergencyBlocked()
     {
         StartFlashingRed();
         PlaySound(errorBuzzClip);
     }
    
     public void OnEmergencyCleared()
     {
         StopFlashing();
         SetLightColor(greenMaterial);
         audioSource.Stop();
     }
    
     private void StartFlashingRed()
     {
         if (flashingCoroutine != null) return;
         flashingCoroutine = StartCoroutine(FlashRed());
     }
    
     private void StopFlashing()
     {
         if (flashingCoroutine != null)
         {
             StopCoroutine(flashingCoroutine);
             flashingCoroutine = null;
         }
     }
    
     private IEnumerator FlashRed()
     {
         while (true)
         {
             statusLightRenderer.material = redMaterial;
             yield return new WaitForSeconds(0.5f);
             statusLightRenderer.material = greenMaterial;
             yield return new WaitForSeconds(0.5f);
         }
     }
    
  6. Configure the Script:
    • Error Buzz Clip: Assign a short alert/buzzer audio clip.
    • Status Light Renderer: Drag in the Renderer component of the robot’s status light (Status Light).
    • Red Material: Assign a red material used to indicate emergency.
    • Green Material: Assign a green material used to indicate normal operation.

    12

  7. Hook Up the Emergency Stop Button:
    • In the E-Stop button’s Inspector, locate the OnClick() event section.
    • Click the + to add a new event.
    • Drag the System Manager GameObject (the one with SystemStateManager.cs) into the object field.
    • From the dropdown, select SystemStateManager → ToggleEmergencyStop(). This enables toggling the emergency state on and off when the e-stop is pressed.

    13

  8. Wire Unity Events in the Inspector:
    • Use Unity Events to link state transitions to visual and audio feedback.
    • Locate the SystemStateManager component On E Stop Engaged. Drag Heavy Payload Robot Arm into the field and call RobotFeedbackManager.OnEmergencyBlocked().
    • Similarly, locate On E Stop Cleared and call RobotFeedbackManager.OnEmergencyCleared()

    14

  9. Deploy and Test:
    • Robot control is disabled during an emergency stop.
    • Pressing any number on the numpad while the e-stop is active results in a flashing red status light, a buzz alarm sound, and no animation is triggered on the robot.
    • Releasing the e-stop (pressing again) returns the system to normal. The status light turns solid green, the buzzing stops, the numpad becomes interactive again, and robot controls are re-enabled.

This reflects real-world industrial safety behavior, where all interactive systems respect current operational context, promoting safety, realism, and reliable user feedback.


Key Takeaways

Activating objects in VR within Unity’s XR framework hinges on an event-driven approach that connects user inputs to specific actions through well-structured events, listeners, and handlers. By decoupling interaction logic from the actions performed, developers can create modular, maintainable systems that are easy to extend or reconfigure. Effective implementation blends multi-modal feedback—visual, auditory, and haptic cues—to reinforce system responses and keep users informed, while contextual interaction logic ensures operations respect current states and safety conditions, such as disabling controls during an emergency stop. Together, these principles not only enhance immersion and usability but also model real-world engineering practices and safety protocols.