Skip to main content

Step 3: Adding Interactions

🎯 Objectives

The aim of this step is to enable simple interactions from the VR headset.

  • Main Interaction: Block specific roads by selecting them with the interaction ray.
  • Visual Feedback: Change road colors based on selection and hover states.
  • Advanced Usage: Understand how to map specific controller buttons to custom scripts (e.g., changing brightness).

1. GAMA Model Modifications

First, we must update the GAMA model to support the "blocked" state and handle commands sent from Unity.

A. Update Traffic and Pollution.gaml

We need to add a blocked attribute to the roads and update their visual aspect.

  1. Open Traffic and Pollution.gaml.
  2. Update the road species as follows:
species road {
// ... existing attributes ...
bool blocked <- false;

aspect default {
// Roads turn red if blocked, white otherwise
draw (shape + 5) color: blocked ? #red : #white;
}
}

TutorialGAMARoad

B. Update Traffic Logic

File: Traffic and Pollution.gaml(Global Section)

To ensure cars intelligently avoid blocked roads, we need to modify the weight (cost) associated with each road in the simulation's network. By applying a massive multiplier to blocked roads, the pathfinding algorithm will naturally redirect traffic to alternative routes.

Add or update the following reflex in your global section:

// Reflex to update the speed of the roads according to the weights
reflex update_road_speed {
// If blocked, weight is multiplied by 100,000, effectively making it impassable
road_weights <- road as_map (each::(each.shape.perimeter / each.speed_coeff * (each.blocked ? 100000.0 : 1.0)));
road_network <- road_network with_weights road_weights;
}

ℹ️ This logic uses a conditional operator (condition ? true_value : false_value). If blocked is true, it applies the 100,000 multiplier; otherwise, it keeps the original weight.

TutorialGAMAGlobal

C. Enable Live Updates

File: Traffic and Pollution.gaml (Experiment Section)

To visualize the color change in GAMA's display, we must enable the refresh rate for the road species.

Find the line species road refresh: false; and change it to species road refresh: true;.

TutorialGAMADisplay

D. Update Traffic and Pollution-VR.gaml

We need to add an action that Unity will trigger to toggle the blocked state.

    // Map to link road names to their agents
map<string,road> roads <- road as_map (each.name :: each);

action block_road(string id) {
road b <- roads[id];
if (b != nil) {
ask b {
// Toggle blocked state
blocked <- !blocked;
}
}
}

TutorialGAMAUnityLinker

2. Unity Project Modifications

Now, let's configure Unity to interact with these new GAMA features.

A. Extend Interaction Distance

Since we are in "Sky View," the default interaction ray might be too short to reach the roads.

  1. In the Hierarchy, select: SkyViewPlayer > Camera Offset > Right Controller > Near-Far Interactor.
  2. In the Inspector, find the Curve Interaction Caster component.
  3. Under Curve Casting Settings, set Cast Distance to 1000.
CastDist

B. Programming the Interaction

We need to modify the C# script that handles what happens when the ray hits an object.

How to open the code: You have two ways to access the script:

1. Via Inspector:

  • Select the Manager (or Game Manager) object in the Hierarchy.
  • In the Inspector, locate the Simulation Manager Interaction component.
  • Double-click on the Script field (where it says SimulationManagerInteraction.cs).
Capture d’écran 2026-02-13 à 13 40 36

2. Via Project Window( Alternative):

  • In the Unity Project Window (bottom panel), navigate to the folder: Assets > Scripts > Gama Provider.
  • Double-click the script: SimulationManagerInteraction.cs.

Unity opens Visual Studio, allowing you to edit the script.

Capture d’écran 2026-02-13 à 13 40 52

Modify the following three methods inside the script:

1. Hover Enter (Pointer enters the road)

Description: This method triggers when the VR pointer starts touching an object. It provides immediate visual feedback to the user.

  • It checks if the object is tagged as a "road".
  • It changes the color to Blue to indicate interaction is possible.
protected override void HoverEnterInteraction(HoverEnterEventArgs ev)
{
GameObject obj = ev.interactableObject.transform.gameObject;
if (obj.tag.Equals("road"))
ChangeColor(obj, Color.blue); // Visual feedback
}

2. Hover Exit (Pointer leaves the road)

Description: This method triggers when the VR pointer leaves the object. It resets the color based on the object's logic state.

  • It checks if the road is currently in the SelectedObjects list (meaning it is blocked).
  • If Selected (Blocked): It turns Red.
  • If Not Selected (Free): It reverts to default Gray.
protected override void HoverExitInteraction(HoverExitEventArgs ev)
{
GameObject obj = ev.interactableObject.transform.gameObject;
if (obj.tag.Equals("road"))
{
bool isSelected = SelectedObjects.Contains(obj);
ChangeColor(obj, isSelected ? Color.red : Color.gray);
}
}

3. Select Interaction (Clicking the Trigger)

Description: This method triggers when the user presses the Trigger button while pointing at an object. It handles the main logic.

  • Cooldown: Checks remainingTime to prevent accidental double-clicks.
  • GAMA Communication: Asks GAMA to trigger the block_road action with the road's ID.
  • Local State: Toggles the road's status in the SelectedObjects list and updates the color immediately.
protected override void SelectInteraction(SelectEnterEventArgs ev)
{
if (remainingTime <= 0.0)
{
GameObject obj = ev.interactableObject.transform.gameObject;
if (obj.tag.Equals("road"))
{
Dictionary<string, string> args = new Dictionary<string, string> {
{"id", obj.name }
};
// Trigger the GAMA action
ConnectionManager.Instance.SendExecutableAsk("block_road", args);

// Toggle selection state and color in Unity
bool newSelection = !SelectedObjects.Contains(obj);
if (newSelection) SelectedObjects.Add(obj);
else SelectedObjects.Remove(obj);

ChangeColor(obj, newSelection ? Color.red : Color.gray);
remainingTime = timeWithoutInteraction; // 1s cooldown
}
}
}

3. Advanced: Custom Controller Buttons

(Informational only - not required for the main tutorial)

To map a specific physical button (like the "A" or "X" button) to an action:

  1. Define the Action: Double-click Assets/Samples/XR Interaction Toolkit/[Version]/Starter Assets/XRI Default Input Actions.
  2. Add Binding: In XRI RightHand Interaction, add a new action (e.g., "Main Button") and bind it to the controller's primary button.
  3. Reference in Script: Use [SerializeField] private InputActionReference primaryRightHandButton; in your script.
  4. Link in Inspector: Drag the Input Action into the corresponding field on your Game Manager object.
  5. Detect Press:
if (primaryRightHandButton != null && primaryRightHandButton.action.triggered) {
TriggerMainButton(); // Your custom function here
}