UdonSharp Arrays: Cycle Three Lights with One Script

Created: 2025-12-19Last updated: 2026-09-09

Cycle three lights with one UdonSharp array, and learn why indices start at 0, how to stay in range, and what unset Inspector elements break.

You want three lights in a room, lit in turn. The straightforward version looks like this.

[SerializeField] private Light light1;
[SerializeField] private Light light2;
[SerializeField] private Light light3;

It works. Grow to five and it's five lines; ten and it's ten. The logic gets copied just as many times, and fixing anything means fixing all of it.

This article uses arrays, which handle things of the same kind together, to cycle three lights with one piece of code.

Three ceiling lamps lighting in turn

What You'll Learn

  • What gets easier when you gather things into an array
  • Why indices start at 0
  • How to write it so you don't go out of range
  • What happens when an Inspector element is empty

Start from having tried variables and references.

Sponsored


An array is a divided tray

An array is a box holding things of the same kind, lined up and numbered.

The difference between separate variables and an array in a divided tray

Three separate variables are three independent boxes. Names are the only way to tell them apart.

An array makes them one tray with dividers. You can pick by number, so you can say "the third light." That's the decisive difference.

Written out:

[SerializeField] private Light[] lights;   // An array of Light

// Using it
lights[0].enabled = true;   // The first
lights[1].enabled = true;   // The second

The [] in Light[] is the marker that says "this is an array." The [0] in lights[0] is the index, saying which one.

Indices start at 0. An array of three is 0, 1, 2. There's no 3.

That feels odd at first, and there's a reason. An index expresses "how many elements offset from the start." The first is offset by zero, so it's 0, and the next is 1.

What makes arrays useful is that the number can be a variable.

int index = 2;
lights[index].enabled = true;   // The third lights up

With that, you can process everything in turn with for, or advance to the next light on each press. Three separate variables can't do that.

Sponsored

Hands-On: Cycle three lamps in turn

Each press moves the lit lamp from left to right. After the third it returns to the first.

1. Place three lamps and a button

Place four things in a scene with a floor.

NameHow to make it, and settings
Lamp0Point Light. (-1.5, 2.2, 1), Range 4, Intensity 2, Mode "Realtime"
Lamp1Point Light. (0, 2.2, 1), same
Lamp2Point Light. (1.5, 2.2, 1), same
LampButtonCube. (0, 1, -1), Scale (0.4, 0.4, 0.4)
Arrangement of three lights and the button

Names start at Lamp0 so they line up with the indices and cause less confusion.

2. Write the code

In Assets/Scripts, choose "Create → U# Script" and make LampSequence.

using UdonSharp;
using UnityEngine;

[UdonBehaviourSyncMode(BehaviourSyncMode.None)]
public class LampSequence : UdonSharpBehaviour
{
    [SerializeField] private Light[] lamps;   // Receive the three lights together

    private int currentIndex;

    private void Start()
    {
        Apply();
    }

    public override void Interact()
    {
        // Advance. Wrap back to 0 at the end
        currentIndex++;
        if (currentIndex >= lamps.Length)
        {
            currentIndex = 0;
        }

        Apply();
        Debug.Log("[LampSequence] index=" + currentIndex);
    }

    // Light only the lamp at the current index
    private void Apply()
    {
        for (int i = 0; i < lamps.Length; i++)
        {
            if (lamps[i] == null) continue;   // Skip empty slots
            lamps[i].enabled = (i == currentIndex);
        }
    }
}

Three things to note.

It uses lamps.Length — the property returning the array's element count. Writing 3 directly would mean editing the code when you grow to four lamps. With Length, raising the size in the Inspector is enough.

It loops over everything with for. i runs from 0 to Length - 1. Note it's i < lamps.Length, not i <= lamps.Length. Get that wrong and you get the out-of-range access from the next section.

The difference between writing each one out, and gathering them with an array and a for loop

lamps[i].enabled = (i == currentIndex); does the lighting and the extinguishing in one line. It means "true if it matches the current index, false otherwise." Since it runs for every element, whatever was lit before goes out automatically.

3. Assign in the Inspector

Add a Udon Behaviour to LampButton and set LampSequence.

A Lamps field appears in the Inspector. It's an array, so you set the size first.

  1. Open Lamps and enter 3 into Size
  2. Drag Lamp0 into Element 0
  3. Drag Lamp1 into Element 1 and Lamp2 into Element 2

Raising the size creates empty slots (None). Fill them all in. Forgetting causes the accident in the next section.

Set Interaction Text to Next light.

4. Run it and confirm

Press Play and press the button.

ActionExpected result
Right after startOnly the left lamp is lit. index=0
Press onceIt moves to the middle. index=1
Press twiceIt moves to the right. index=2
Press three timesIt returns to the left. index=0

Try growing to four lamps. Make Lamp3, set Size to 4 in the Inspector, and assign it. Without changing a character of code, it cycles through four.

That's the payoff of arrays.

Don't reach out of range

The most common failure with arrays is pointing at an index that doesn't exist.

Light[] lamps = new Light[3];   // Three: 0, 1, 2
lamps[3].enabled = true;        // There is no 3

An array of three has no 3. Running it errors and halts the logic there. Nothing past that point runs.

An array that only goes 0 to 2 has no 3

Two ways to avoid it.

First, use Length. Don't write numbers directly; base everything on lamps.Length.

if (currentIndex >= lamps.Length)
{
    currentIndex = 0;
}

Second, skip null. An empty slot in the Inspector makes that element null. Using it directly errors, so check first.

if (lamps[i] == null) continue;   // Skip if empty

continue means "end this pass here and go to the next." Even with an empty slot, the rest gets processed correctly.

Raising the Size and forgetting to fill it in is the most common accident. Whenever you touch an array in the Inspector, look at every slot.

Sponsored

Common Pitfalls

  • Pressing does nothing → Is Lamps' size 0? Are all the elements empty?
  • One of them doesn't light → That index's element is None
  • It errors and stops → You're pointing at an out-of-range index. Check that you're using Length
  • They all light at once → Are you writing an unconditional enabled = true in Apply()? The (i == currentIndex) comparison is required

Bonus: Good to Know Up Front

  • foreach works too: When you're just processing everything in turn, you can write foreach (Light lamp in lamps). Use for when you need the index
  • List isn't available: Ordinary C# List<T> doesn't work in UdonSharp. For data whose count changes at runtime, use VRChat's DataList and DataDictionary
  • Arrays can be synced too: [UdonSynced] shares arrays as well, though it fails if the element count isn't fixed. Always initialize before use
  • Array contents are references: Light[] holds references to the lights themselves. Change one and that light changes (covered in variables and references)
  • Naming from 0 reads better: Lamp0, Lamp1, Lamp2 line up with the Inspector's Element 0. Starting at Lamp1 offsets everything by one and causes confusion

Summary

An array is a mechanism for handling things of the same kind by number.

  • Rather than lining up variables, you put them in one numbered tray
  • Indices start at 0. Three elements means 0, 1, 2
  • Get the count with Length. Don't write numbers directly
  • Empty slots (None) cause errors. Check before using

The question to ask when you want more is: "Can I grow this from the Inspector alone, without editing code?" If you can, you're using arrays correctly.

Next, handle data whose count isn't fixed. In DataList and DataDictionary, let's load external settings. For live data like a headcount, player events is the entry point.

Read nextNext in the learning path
Syntax

VRChat DataDictionary and JSON: Load Settings into Your Lights

Read this note
VRChat Notes in this section63