[VRChat] Variables and References: Changing Light Settings from the Inspector

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

Grasping value versus reference as 'a note' and 'a pointing finger,' then toggling a light's brightness. Covers why editing code doesn't change the Inspector value, and the classic None-reference dead end.

Attaching an UdonSharp script puts settings fields in the Inspector. Fields where you type numbers, and fields where you drag scene objects. They look similar, and they're completely different things.

Once you can think of them separately, you can change settings without rewriting code. Leave it vague and you'll hit "the code is right but it doesn't work" over and over.

This article gets across the difference between value and reference, and makes a light's brightness switchable from the Inspector.

A line running from an Inspector panel to a desk lamp

What You'll Learn

  • The difference between "value" and "reference"
  • Which wins: the initial value in code, or the value in the Inspector
  • Why to use [SerializeField] private
  • How to spot a reference stuck at None

Start from a scene with a floor, having tried your first UdonSharp.

Sponsored


A value is a note; a reference is a pointing finger

There are two kinds of variable. This distinction pays off repeatedly from here on.

A value is a note in your notebook; a reference is a finger pointing at the real thing in the world

A value is a note written in your own notebook. "Brightness 2.0," "it's on," "the color is orange." It holds the number or the yes/no directly. Rewriting the note changes nothing about the light in the world yet.

A reference is a finger pointing at "that lamp." Touch the real thing at the end of the finger and the world's appearance changes. If the finger points at nobody, no amount of numbers in the notebook does anything.

Cast into VRChat terms:

The switch's script holds "a brightness number." But the number alone doesn't brighten the room. Only once the Inspector decides which light it points at does pressing change the lighting.

There's another important property. Several switches can point at the same light. If the entrance switch and the emergency-exit switch both point at the same ceiling light, either one lights the same real object. That's because they share the real thing rather than copying a value.

Here are the types used in this article.

TypeKindUsed here for
intValuePress count
floatValueLight brightness
boolValueWhether it's in the bright state
stringValueA name for the log
ColorValueLight color
LightReferenceThe light itself
Sponsored

Hands-On: Toggle a light's brightness

Each press moves the room's light between dim (0.2) and bright (2). The numbers stay changeable from the Inspector rather than the code.

1. Place a button and a light

Place two things in a scene with a floor.

NameHow to make it, and settings
VariableButtonCube. Position (0, 1, 1), Scale (0.4, 0.4, 0.4)
VariableLampLight → Point Light. Position (0, 2, 1), Range 5, Mode "Realtime"
Arrangement of the button and the lamp

Set the light's Mode to Realtime. Baked wouldn't change appearance when brightness changes at runtime (covered in lighting and baking).

To make the difference easier to see, temporarily disabling the scene's Directional Light helps.

2. Write a script that holds settings

In Assets/Scripts, choose "Create → U# Script" and name it VariableLightSwitch.

using UdonSharp;
using UnityEngine;

[UdonBehaviourSyncMode(BehaviourSyncMode.None)]
public class VariableLightSwitch : UdonSharpBehaviour
{
    [SerializeField] private Light targetLight;        // Reference: which light to operate
    [SerializeField] private float dimIntensity = 0.2f; // Value: brightness when dim
    [SerializeField] private float brightIntensity = 2f; // Value: brightness when bright
    [SerializeField] private Color lightColor = Color.white;
    [SerializeField] private string logLabel = "VariableLight";

    private bool isBright;   // Whether it's currently bright
    private int pressCount;  // How many times it's been pressed

    private void Start()
    {
        if (targetLight == null)
        {
            Debug.LogWarning("[VariableLight] Target Light is not set.");
            return;
        }
        ApplyLight();
    }

    public override void Interact()
    {
        if (targetLight == null) return;

        isBright = !isBright;
        pressCount++;
        ApplyLight();
        Debug.Log("[" + logLabel + "] count=" + pressCount + " bright=" + isBright);
    }

    // Push "the current state" onto the actual light
    private void ApplyLight()
    {
        targetLight.color = lightColor;
        targetLight.intensity = isBright ? brightIntensity : dimIntensity;
    }
}

The five fields marked [SerializeField] appear in the Inspector. isBright and pressCount don't, since they're only used internally at runtime.

There's a reason for [SerializeField] private. public also puts things in the Inspector, but public additionally declares "other scripts may touch this variable." When you only want a world setting, [SerializeField] private is safer because nothing outside can rewrite it.

ApplyLight() groups the logic that pushes state onto the light. Since both Start() and Interact() call it, keeping it in one place prevents the accident of fixing only one of them.

3. Assign references and values

Add a Udon Behaviour to VariableButton and set Program Source to VariableLightSwitch. The settings fields appear.

FieldWhat to set
Target LightVariableLamp from the Hierarchy
Dim Intensity0.2
Bright Intensity2
Light ColorWhite
Log LabelVariableLight
Interaction TextChange brightness
Fields you drag into, and fields you type numbers into

Only Target Light is a reference. You drag the actual light from the scene into it, not a number.

Take a moment to see the failure deliberately. Leave Target Light at None and press Play: the Console says "Target Light is not set," and pressing does nothing. That's the true form of "the code is right but it doesn't work."

Once you've seen it, drag VariableLamp in.

4. Run it and confirm

Press Play, click the Game view, and press VariableButton.

ActionExpected result
Right after startThe light's Intensity is 0.2. The room is dim
Press onceIt becomes 2 and brightens. Console shows count=1 bright=True
Press twiceIt returns to 0.2. Console shows count=2 bright=False

Selecting VariableLamp in the Inspector during Play shows the Intensity number actually switching. You get a feel for code writing into a value.

5. Change it without touching code

Here's the main payoff. Stop Play and set Bright Intensity to 5 in the Inspector.

Without altering a single character of code, the brightness on press changes. The settings live in the Inspector, not in the code.

Set Light Color to orange and the lamp's color changes too. Attach the same script to a button in another room, point it at a different light, and you get switches with different brightness per room.

Editing the code doesn't change the Inspector value

Let's get ahead of a phenomenon almost everyone hits once.

The initial value in code versus the value in the Inspector. Touch it once and the Inspector wins

It goes like this.

  1. You write brightIntensity = 2f in code and attach it to a button
  2. 2 appears in the Inspector. You try 8 and it feels right
  3. Days later you decide on 3, edit the code to 3f, and save
  4. Play still shows 8. The code changed and nothing did

Here's why. Once a variable is attached to an object, the value saved in the Inspector becomes the real one. The number in code is close to "the initial value the first time it was attached," and changing it later doesn't overwrite what's already saved.

Three ways to fix it:

  • Edit the Inspector field directly (safest)
  • Right-click the component name in the Inspector and choose Reset (careful: it returns every setting on that component to its initial value)
  • Remove and re-attach the script (references get cleared too, so not recommended for beginners)

The first is enough day to day. Touch the Inspector when you want to change a setting — memorizing that is the fastest route.

Sponsored

Common Pitfalls

  • A "not set" warning appears at startup → Target Light is None. Drag the light in
  • The press log appears but the room doesn't change → It's pointing at the wrong light, or the light's Mode is Baked. Set it to Realtime
  • Code edits don't take effect → The Inspector value wins. See the section above
  • Changed a setting during Play and it reverted on stop → Changes during Play aren't saved. Stop first, then enter them

Bonus: Good to Know Up Front

  • References share the real thing: If two buttons point at the same light, either one changes the same light. To change only one, point at a different light or split the script
  • UdonSynced is a different story: [SerializeField] is a marker for the Inspector and tells other players nothing. Reaching everyone needs a separate mechanism, [UdonSynced]. Covered in networking basics
  • There are markers for tidying the Inspector too: As variables pile up, you can add headings, descriptions, and slider ranges. See tidying the Inspector with attributes
  • Some C# forms don't work in UdonSharp: Properties and generics from ordinary C# sometimes don't compile as-is. Starting within what variables and if cover is the reliable path
  • Build the habit of early null checks: One line of if (targetLight == null) return; turns a forgotten reference into a warning instead of an error

Summary

Variables come in two kinds: ones that hold, and ones that point.

  • A value is a note in your notebook; a reference is a finger pointing at the real thing
  • With a reference at None, nothing happens even with correct code
  • Use [SerializeField] private when you only want it in the Inspector
  • The initial value in code applies only at first attachment. The real value afterwards lives in the Inspector

The question to ask when you're unsure is: "Is this a number, or is it pointing at a real thing?" Separating those speeds up isolating why something doesn't work.

Next, operate the same light from multiple buttons. Head to methods and custom events for splitting and reusing logic. To learn when logic runs, events and execution order comes first.

VRChat Notes in this section63