LUIDA Docs
Tutorials

Tutorial 2 — Proteus Effect (with LUIDA automation)

Rebuild the same Proteus Effect bridge-choice study as Tutorial 1 declaratively, using LUIDA's variables, state machine, and state-listening items.

Time~75 min
Uses LUIDA automation?Yes
PrereqsTutorial 0 — Hello LUIDA complete. Setup complete.
Concepts introducedThe full LUIDA automation surface: within-subjects variables (isRandom), state machine, state-listening items, per-row If conditional branching, Data Collector's Condition lookup source. Built entirely from the editor's predefined actions — no inline JS.
Starting assetAssets/_Experiment_/Scenes/Sample/ProteusSample_practice.unity — the same scene scaffold as Tutorial 1 (a Mirror that functions as a mirror, an InstructionPanel backdrop plate, Bridges, SafeBridgeCheckPoint, DangerousBridgeCheckPoint, and a Portal prefab instance placed in the scene). State-listening items, variables, state machine, and Data Collector are not yet configured. (The scene also ships an unused ThankYouText; ignore or delete it.)
End stateSame as Tutorial 0.

Experiment plan

The participant's experience breaks into a few phases.

PhaseWhat happens
Intro (~5 s)A welcome message appears and dismisses automatically
Trial (×2 repetitions)① An avatar is assigned and shown in the mirror (~10 s)
② The mirror disappears and two bridges become walkable
③ Cross either bridge
④ At the far end, an embodiment questionnaire appears
⑤ Submit to advance to the next trial
EndA portal and thank-you message appear, and the collected data is uploaded to the Web Console

These phases are implemented using LUIDA's state feature. Set up one state per phase, and the runtime automatically advances to the next state once each transition condition is met (configured from step 5 onward).

The trial repeats twice, with elder and young each assigned once (order randomized).

Variables:

within-subjects:
  avatar: ["elder", "young"]   isRandom = true

(no between-subjects)
trialsCountPerCondition: 1
→ 2 trials per participant

Recorded per trial:

  • avatar"elder" or "young".
  • bridge"safe" or "danger".

1. Register the experiment in the Web Console

Create the experiment

luida.cluster.mu+ New Experiment.

  • Title: Proteus Bridge Experiment
  • Room capacity: 1
  • Status: Testing
The Web Console New Experiment form with Title, Room capacity, and Status filled in.
The New Experiment form with this tutorial's values.

Keep Status as Testing throughout this tutorial. When you later create your own experiment and switch it to Public, it will be listed on LUIDA's recruitment world the next day and participants can join.

Set Participant Avatar to HIDE

Detail page → World & PlatformParticipant Avatar = HIDE. Save.

The World & Platform section with Participant Avatar set to HIDE.
Participant Avatar set to HIDE in the World & Platform section.

During the experiment, Unity assigns avatars to the participant based on the experimental condition, so we hide the participant's own Cluster avatar.

The HIDE setting takes effect the next day. Cluster's avatar restriction propagates only on the day after you change it. If you upload the world and test it yourself today, your own avatar will overlap with the Unity-assigned avatar — but you can ignore this during the tutorial test.

Add the VEQ template questionnaire

Scroll to QuestionnairesAdd QuestionnaireTemplate Selection = VEQ. Click Add. The questionnaire is auto-populated with the standard VEQ items.

The Questionnaires section before any questionnaires have been added.
Questionnaires section before adding.
The Add Questionnaire dialog with Template Selection set to VEQ.
Adding the VEQ template.
The Questionnaires section after the VEQ has been added, showing the standard items.
The VEQ row after creation.

Copy the eID

Copy the eID from the experiment detail page — you'll paste it into Unity in step 2. The verify token is auto-managed on Cluster's side at Unity login; no copy needed.

The experiment detail page with the eID at the top.
The eID shown at the top of the experiment detail page.

2. Initial Unity setup

Duplicate the sample scene

Open Assets/_Experiment_/Scenes/Sample/ProteusSample_practice.unity. Use LUIDA → Scene → Duplicate current scene to duplicate it under any name you like (e.g. tutorial_2).

The Unity menu showing LUIDA → Scene → Duplicate current scene.
LUIDA → Scene → Duplicate current scene.
The Duplicate Scene dialog with a name field.
Naming the duplicated scene.

Configure experiment identifiers

LUIDA → Configure experiment identifiers. Paste this experiment's eID into Experiment ID and set Number of Participants to 1.


3. Register the sample avatars

Sample avatar prefabs ship at Assets/_Experiment_/Models/Sample/ElderAvatar/ and Assets/_Experiment_/Models/Sample/YoungAvatar/. This tutorial uses them as-is — you don't need to bring your own VRMs.

Open LUIDA → Configure avatars and drag each folder's .prefab into the Register Avatars slot as below. Leave the other fields at their defaults.

Avatar IDAvatar prefab
elderAssets/_Experiment_/Models/Sample/ElderAvatar/ElderAvatar.prefab
youngAssets/_Experiment_/Models/Sample/YoungAvatar/YoungAvatar.prefab

Save. Registration order is irrelevant — the Assign avatar to participant action looks up by avatarID at runtime.

If you swap in your own avatar, import an fbx file or a VRM 0 file into your Unity project before registering. LUIDA currently does not support VRM 1.0 — models exported as VRM 1.0 fail to load. When exporting from VRoid Studio or similar, choose VRM 0.0.

The Unity menu showing LUIDA → Configure avatars.
Opening Configure avatars.
The Configure avatars window with an empty Register Avatars slot.
The slot before registering. Drag the avatar `.prefab` here to register it.
The Configure avatars window with both elder and young avatar prefabs registered.
After registering elder and young.

4. Declare the LUIDA variable + trial count

The variable values you declare in this tab become the candidate condition values for each trial. For example, declaring avatar: [elder, young] expands into 2 trials, with the avatar condition being either elder or young on each trial.

Open the automation editor

LUIDA → Configure experiment automation → Variables tab. The within-subjects variable you declared in the Web Console should appear automatically. If not, click Refresh from Web Console.

Configure avatar

In Variables for Within-Subject Conditions, confirm one row:

NameValuesisRandom
avatarelder, young

isRandom: true shuffles the trial order per participant — roughly half see elder first, the other half young first.

Leave Variables for Between-Subject Conditions empty.

The Variables tab of the automation editor showing the avatar row with isRandom ticked.
The avatar within-subjects variable with isRandom enabled.

Set Trials Count per Condition = 1

The trial count is auto-computed: 2 unique conditions × 1 = 2 trials per participant.


5. Build the state machine

Here we lay out the phases from the "Experiment plan" as states.

Switch to the State Machine tab

Inside LUIDA → Configure experiment automation.

Add the states

#State nameDest stateHas Exit TimeExit Time (s)qIDNotes
1IntroTrial - Start5Auto-advances after 5 s (Has Exit Time on). Tick the box; type 5 into Exit Time.
2Trial - StartTrial - Rest0Per-item Sleep chains drive the Phase A → B → C timing. Bridge collision advances the state machine via the checkpoint's On collide event handler.
3Trial - RestEnd01VEQ (qID = 1) appears immediately on entry; participant submit auto-advances.
4End(empty)0Teardown + upload + portal.

Trial - Start and Trial - Rest form the trial loop, repeated unique conditions × trialsCountPerCondition times (here, 2 × 1 = 2) automatically.

The State Machine tab showing the four states: Intro, Trial - Start, Trial - Rest, End.
The four-state machine after saving.

6. Configure the Data Collector (Builder GUI)

In Data Collector, you assemble the data uploaded to the Web Console per trial in two stages:

  • Collected data items: Declare receiving slots (scratchpad) that scene actions write into. For example, the bridge checkpoints will write the bridge value into the slot defined here.
  • Fields to be saved: Using the slots above plus condition values, assemble the actual row that gets uploaded.

Open LUIDA → Configure data collector from Unity's top menu.

Declare the collected data item

Add one collected data item:

LabelType
bridgeString

Field 1: avatar

  • Name: avatar
  • Source: Condition lookup (returns the current trial's condition variable value as-is)
  • Variable name: avatar

Field 2: bridge

  • Name: bridge
  • Source: Collected (reads the value written into a slot declared under Collected data items)
  • Label: bridge

Save

Click Save & Combine.

The Luida Data Collector Builder GUI showing the bridge collected data slot and the two output fields avatar and bridge.
The Data Collector Builder GUI after declaring the slot and two fields.

From here through step 12, we define the behavior of GameObjects that react to state transitions (the experiment flow) — these are called state-listening items. For each item, we list what happens when it enters or leaves each state (show / hide / set text / collect data, etc.) as rows.


7. Wire AvatarController (state-listening item)

Create AvatarController from scratch in the state-listening items editor. This item gathers the avatar lifecycle and per-trial teleport rows.

Open the State-listening Items tab and create AvatarController

LUIDA → Configure experiment automation → State-listening Items. In the New Item Name field at the top, type AvatarController and click Create New Listening Item. LUIDA creates a new GameObject named AvatarController at the scene root with the Luida State Listening Item component already attached, and adds a column for it to the grid.

Add the rows

Add each row from top to bottom in each table (same convention for the following sections). For Assign avatar to participant rows, use the If toggle (only visible on trial-related states): tick it, set Var Name = avatar, then pick Is Value per the table below.

Trial - Start / When entering

ActionFieldsPurpose
Participant transform / Set participant positionParticipant # = 1, x = 0, y = 0, z = -2Snap the participant back to the spawn area at the start of each trial
Avatar / Unassign avatar from participantParticipant # = 1Defensively clear the previous trial's avatar
State machine / Sleepseconds = 3Phase A: wait 3 s (sync'd with Instruction's "Preparing trial...")
Avatar / Assign avatar to participant — tick If, Var Name = avatar, Is Value = elderAvatar ID = elder, Participant # = 1Assign the elder avatar only when this trial's condition is elder
Avatar / Assign avatar to participant — tick If, Var Name = avatar, Is Value = youngAvatar ID = young, Participant # = 1Assign the young avatar only when this trial's condition is young

Trial - Rest / When leaving

ActionFieldsPurpose
Avatar / Unassign avatar from participantParticipant # = 1Unassign the avatar after questionnaire submit
The AvatarController cell under Trial - Start / When entering with all five action rows configured.
AvatarController — Trial - Start / When entering.
The AvatarController cell under Trial - Rest / When leaving with the Unassign avatar row.
AvatarController — Trial - Rest / When leaving.

8. Wire Instruction

Create Instruction from the state-listening items editor. The scene already has a background plate called InstructionPanel; we make it a child of Instruction.

Create Instruction as a state-listening item

LUIDA → Configure experiment automation → State-listening Items. Type Instruction into New Item Name and click Create New Listening Item.

Add the rows

Intro / When entering

ActionFieldsPurpose
Item / Show itemShow the Instruction panel
Item / Set texttext = Welcome. The first trial will begin shortly.Display the welcome message

Trial - Start / When entering

ActionFieldsPurpose
Item / Show itemRe-show the panel that was hidden in Trial - Rest
Item / Set texttext = Preparing trial...Phase A: show the preparing message
State machine / Sleepseconds = 3Wait Phase A's 3 s
Item / Set texttext = Look in the mirrorPhase B: switch to the mirror prompt
State machine / Sleepseconds = 10Wait Phase B's 10 s
Item / Set texttext = Walk to the other side via one of the bridgesPhase C: switch to the bridge-crossing prompt

Trial - Rest / When entering

ActionFieldsPurpose
Item / Hide itemHide Instruction while the questionnaire is up
The Instruction cell under Intro / When entering with Show item and Set text rows.
Instruction — Intro / When entering.
The Instruction cell under Trial - Start / When entering with the six-row chain.
Instruction — Trial - Start / When entering.

Make InstructionPanel a child of Instruction

In the Hierarchy, drag the existing InstructionPanel GameObject onto Instruction so it becomes a child. The local Transform can stay as it is.

Align the auto-spawned Text over InstructionPanel

The moment you added the first Set text row in the previous step, LUIDA auto-spawned a Text GameObject as a child of Instruction. Adjust its local position so the message lines up flush against InstructionPanel (a small z offset toward the camera avoids z-fighting with the panel). Tweak the local scale or TextView font size as needed so the message fits inside the panel.

The Hierarchy showing Instruction with InstructionPanel and Text as children.
Instruction in the Hierarchy with its two children.
The Scene view showing Text aligned over InstructionPanel.
Text aligned over InstructionPanel.

9. Wire Mirror

The scene has a GameObject called Mirror that functions as a mirror. In the state-listening items editor, create a new Mirror, then make the existing Mirror its child.

Create Mirror as a state-listening item

LUIDA → Configure experiment automation → State-listening Items. Type Mirror into New Item Name and click Create New Listening Item. Because the scene root already has a Mirror, Unity temporarily appends a suffix (e.g. Mirror (1)) — that's fine, we'll make the existing one a child and rename in the next step.

Add the rows

Trial - Start / When entering

ActionFieldsPurpose
State machine / Sleepseconds = 3Wait Phase A's 3 s (Mirror still hidden)
Item / Show itemPhase B begins: show the Mirror
State machine / Sleepseconds = 10Wait Phase B's 10 s
Item / Hide itemPhase C begins: hide the Mirror so the participant focuses on the bridges
The Mirror cell under Trial - Start / When entering with the four-row Sleep / Show / Sleep / Hide chain.
Mirror — Trial - Start / When entering.

Make the existing scene Mirror a child of the new item

In the Hierarchy, drag the existing scene Mirror onto the new item so it becomes a child, then rename the new item back to Mirror.

The Hierarchy showing Mirror containing the original Mirror as a child.
The original Mirror sits inside the new Mirror.

10. Wire Bridges and the bridge checkpoints

The scene contains Bridges (both bridge meshes combined into a single GameObject), SafeBridgeCheckPoint at (1.5, 1, 8), and DangerousBridgeCheckPoint at (-1.5, 1, 8) — checkpoint cubes placed at the far end of each bridge. For each, create a same-named state-listening item and move the existing scene object inside as a child.

Create Bridges as a state-listening item

State-listening Items → New Item Name: BridgesCreate New Listening Item. In the Hierarchy, drag the existing scene Bridges onto the new item so it becomes a child, then rename the new item back to Bridges.

Add the rows.

Trial - Start

HookActionFieldsPurpose
When enteringState machine / Sleepseconds = 13Wait Phase A + Phase B combined 13 s (Bridges still hidden)
When enteringItem / Show itemPhase C begins: make the bridges walkable
When leavingItem / Hide itemHide the bridges the moment the crossing fires To next state
The Bridges cell under Trial - Start with the Sleep / Show rows on When entering and Hide on When leaving.
Bridges — Trial - Start hook rows.
The Hierarchy showing Bridges containing the original Bridges as a child.
The original Bridges sits inside the new Bridges.

Create DangerousBridgeCheckPoint as a state-listening item

State-listening Items → New Item Name: DangerousBridgeCheckPointCreate New Listening Item. In the Hierarchy, drag the existing scene DangerousBridgeCheckPoint onto the new item so it becomes a child, then rename the new item back to DangerousBridgeCheckPoint. Add the per-state rows.

Trial - Start

HookActionFieldsPurpose
When enteringItem / Show itemShow the checkpoint cube (same timing as the bridges)
When leavingItem / Hide itemHide the checkpoint cube (same timing as the bridges)
The DangerousBridgeCheckPoint cell under Trial - Start with Show item on entry and Hide item on leave.
DangerousBridgeCheckPoint — Trial - Start Show / Hide rows.

Add a Rigidbody to DangerousBridgeCheckPoint

The On collide event handler in the next step requires a Rigidbody on the state-listening item. In the Hierarchy, select the newly-created DangerousBridgeCheckPoint (not the child checkpoint cube). In the Inspector, click Add Component → Physics → Rigidbody and configure it like this:

FieldValue
Use Gravityunchecked
Is Kinematicunchecked
Constraints / Freeze PositionX, Y, Z all checked
Constraints / Freeze RotationX, Y, Z all checked

You'll repeat this same Rigidbody setup on SafeBridgeCheckPoint later.

The Inspector showing a Rigidbody component with Use Gravity off, Is Kinematic off, and all six Freeze constraints ticked.
Rigidbody settings for each checkpoint state-listening item.

DangerousBridgeCheckPoint: add an On collide event handler

In the same state-listening item cell for DangerousBridgeCheckPoint, find the Always-on event handlers row. Click + Add event ▾ and pick On collide from the dropdown. Add three rows.

The DangerousBridgeCheckPoint cell with the Always-on event handlers row, after picking On collide from the dropdown.
Adding the On collide event handler.
ActionFieldsPurpose
Data collection / Push data to collectorlabel = bridge, type = String, value = 'danger' (single-quoted — the value field is a JS expression)Write the chosen bridge type into the bridge slot
Data collection / Save pushed data to bufferSnapshot the current bridge value plus the active condition values as one row into the upload buffer
State machine / To next stateAdvance the state machine to Trial - Rest
The On collide event handler popup with the three action rows configured.
The three rows added to DangerousBridgeCheckPoint's On collide handler.

Close the popup.

SafeBridgeCheckPoint: same shape with safe

Repeat the previous three steps for SafeBridgeCheckPoint: create the same-named state-listening item, make the existing checkpoint cube its child, add the same Trial - Start Show / Hide rows, add the same Rigidbody to the new item, and add the same On collide event handler — but set row 1's value to 'safe'.

Both checkpoint cells under Trial - Start with their Show / Hide rows.
Both checkpoints' Trial - Start rows side by side.
The SafeBridgeCheckPoint On collide event handler popup with the three action rows configured.
SafeBridgeCheckPoint's On collide handler — row 1 writes 'safe'.
The Hierarchy showing SafeBridgeCheckPoint and DangerousBridgeCheckPoint each containing the original checkpoint cube as a child.
The original checkpoint cubes sit inside each new checkpoint item.

11. Wire Portal

The scene has Portal — an instance of the WorldGateToLuidaBar prefab at (0, 0, 0.5). Create a same-named state-listening item, place the existing scene Portal inside as a child, and load the End-state work onto it. The scene's ThankYouText is not used here — ignore or delete it.

Create a new Portal and make the existing Portal its child

State-listening Items → New Item Name: PortalCreate New Listening Item. In the Hierarchy, drag the existing scene Portal (the WorldGateToLuidaBar prefab instance) onto the new item so it becomes a child, then rename the new item back to Portal.

Add the rows

End / When entering

ActionFieldsPurpose
Item / Show itemShow the Portal
Item / Set texttext = Thank you. Please step onto the portal to finish.Display the thank-you message
Data collection / Upload collected dataUpload all buffered rows to the Web Console

Add Upload collected data to exactly one item. Putting it on two items would trigger duplicate row uploads.

The Portal cell under End / When entering with the three rows: Show item, Set text, Upload collected data.
Portal — End / When entering.
The Hierarchy showing Portal containing the WorldGateToLuidaBar prefab and an auto-spawned Text child.
The original Portal and auto-spawned Text sit inside the new Portal.

12. Test in Unity editor

Play through the full sequence

Press the ▶️ (Play) button. You should see:

  1. Intro (5 s) — Instruction reads Welcome. The first trial will begin shortly.; Mirror, Bridges, Portal all hidden; no avatar assigned. After 5 s the state machine auto-advances.
  2. Trial - Start Phase A (3 s) — Instruction reads Preparing trial.... Mirror and bridges hidden. No avatar.
  3. Trial - Start Phase B (10 s) — one of elder / young is assigned (per-participant random); mirror appears; instruction reads Look in the mirror.
  4. Trial - Start Phase C — mirror hides; bridges appear; instruction reads Walk to the other side via one of the bridges. Walk onto either bridge.
  5. State advances to Trial - Rest. The VEQ (qID = 1) appears immediately and you submit it. The avatar is unassigned; the next iteration teleports you back to spawn.
  6. Back to Trial - Start for the other avatar; same Phase A/B/C; cross either bridge; submit the second VEQ.
  7. EndPortal activates with a thank-you message. Unity Console shows one [CSEmulator] callExternal type=uploadCustomData ... → 200 line for the two-row payload.

Web Console → Data tab: two custom-data rows, one per trial, each with avatar and bridge populated. Plus two VEQ submissions stored against qID = 1.

Force the alternate order

To lock the order for repeatable testing, go to Configure experiment automation → Variables and uncheck isRandom on the within-subjects variable avatar. Trials will then run in the order written in the Values field. Re-tick isRandom after testing. See Concepts → Conditions, sessions, participants → Debug overrides for more detail.


13. Upload to Cluster and test on it

  1. Top menu's Cluster > UploadWorld
  2. Press the Create World button
  3. Fill in a title, select an image, and press the Beta function enabled Upload as ... button
  4. Navigate to https://cluster.mu/account/contents/worlds to find the world you just uploaded.
  5. Don't make the world public yet! Keep it private; join the world yourself to verify everything works.
The Unity menu showing Cluster → UploadWorld.
Cluster → UploadWorld in the top menu.
The Cluster upload window with the Create World button.
Press Create World to register the upload target.
The upload window with title and image fields and the Upload as Beta button.
Fill in the title and image, then press the Upload button.

14. Verify data on the Web Console

After playing through the uploaded world, open the experiment detail page on luida.cluster.mu and confirm both data sinks received the session:

  • Custom data tab — two rows for the session (one per trial) carrying avatar and bridge. Each of elder / young appears exactly once.
  • Questionnaire responses tab — two VEQ submissions stored against qID = 1.

If either tab is empty or fields are blank, see the matching entry in Common issues.


15. Register the World ID

In Cluster → My Content → Worlds → copy the UUID from the world URL. Paste into the Web Console's World ID field. Save.


A few final reminders:

  • Avatar overlap during your Cluster test is normal. The Participant Avatar = HIDE setting on Cluster only takes effect the next day, so today your own Cluster avatar overlaps with the Unity-assigned avatar. It resolves automatically the next day.
  • Keep this tutorial world private on Cluster. It's only for verifying behavior — no need to publish.
  • When you eventually publish your own experiment, switch the Web Console's Status to Public. The next day, your experiment world will be listed on LUIDA's recruitment world and participants will be able to join.

What you just learned

  • The trial loop runs just by declaring variables: Declare the value that varies between trials as a variable, and the state machine automatically iterates the trial the right number of times. Adding more conditions doesn't change the scene structure — you don't manually unroll trials.
  • Build the experiment flow in states: Lay out phases like "Intro → Trial → Rest → End" as states, decide each transition's trigger, and the flow is done.
  • Scene-side behavior is rows in a "state × item" grid: Each GameObject lists, per state and hook, what should happen as rows. Even per-trial branching is expressed at the row level via the If toggle instead of writing if in code — so JS knowledge is barely needed.
  • Declare the saved-data shape with Data Collector: Separating "scratchpad slots that scene actions fill" from "columns in the final saved row" keeps the data-collection flow easy to follow.
  • No CCK triggers or gimmicks needed: Even state-independent events (like collisions) become state-listening items' "always-on event handlers." The whole scene's behavior is configurable entirely from LUIDA's editor.

Common issues

For anything else, email luida@cluster.mu.


What's next

On this page

Experiment plan1. Register the experiment in the Web ConsoleCreate the experimentSet Participant Avatar to HIDEAdd the VEQ template questionnaireCopy the eID2. Initial Unity setupDuplicate the sample sceneConfigure experiment identifiers3. Register the sample avatars4. Declare the LUIDA variable + trial countOpen the automation editorConfigure avatarSet Trials Count per Condition = 15. Build the state machineSwitch to the State Machine tabAdd the states6. Configure the Data Collector (Builder GUI)Declare the collected data itemField 1: avatarField 2: bridgeSave7. Wire AvatarController (state-listening item)Open the State-listening Items tab and create AvatarControllerAdd the rows8. Wire InstructionCreate Instruction as a state-listening itemAdd the rowsMake InstructionPanel a child of InstructionAlign the auto-spawned Text over InstructionPanel9. Wire MirrorCreate Mirror as a state-listening itemAdd the rowsMake the existing scene Mirror a child of the new item10. Wire Bridges and the bridge checkpointsCreate Bridges as a state-listening itemCreate DangerousBridgeCheckPoint as a state-listening itemAdd a Rigidbody to DangerousBridgeCheckPointDangerousBridgeCheckPoint: add an On collide event handlerSafeBridgeCheckPoint: same shape with safe11. Wire PortalCreate a new Portal and make the existing Portal its childAdd the rows12. Test in Unity editorPlay through the full sequenceForce the alternate order13. Upload to Cluster and test on it14. Verify data on the Web Console15. Register the World IDWhat you just learnedCommon issuesWhat's next