Your first world

A button anyone can press and a count every player sees, from an empty project to an uploaded world, in about fifteen minutes.

Updated 2026-09-13

You will build a counter: a cube anyone can press, a label that shows how many times it has been pressed, and the same number on every player's screen, including a player who walks in an hour later. It touches the compiler, the networking package and a plain UI label, which is most of what a real world does, in about twenty lines of C#.

1. Install

  1. In the VRChat Creator Companion, open Settings → Packages → Add Repository, paste https://udonite.github.io/vpm/index.json and click Add.
  2. Create or open a world project. Unity 2022.3 with the Worlds SDK 3.10 or newer.
  3. In the project's package list, add Udonite and Udonite Net.

Udonite compiles your C# to Udon on its own from here on; the Net package is the synced value the counter needs.

2. Build the scene

  1. GameObject → 3D Object → Cube, name it Counter and put it where a player can reach it. The cube's collider is what makes it pressable.
  2. GameObject → UI → Canvas, set its Render Mode to World Space, scale it down to something readable (a scale of 0.005 is a fair start) and place it above the cube.
  3. Inside the canvas, UI → Legacy → Text, name it Label, and type 0 as its text. The legacy component is enough here and needs nothing imported.

3. Write the behaviour

Create Assets/Counter.cs:

using Udonite;
using Udonite.Net;
using UnityEngine;
using UnityEngine.UI;

public class Counter : UdoniteBehaviour
{
    public Text label;

    private Synced<int> count = new Synced<int>();

    public override void Start()
    {
        InteractionText = "Press";
        count.Subscribe(OnCountChanged);
        label.text = count.Value.ToString();
    }

    public override void Interact()
    {
        Ownership.Claim(gameObject);
        count.Value = count.Value + 1;
    }

    private void OnCountChanged(int value)
    {
        label.text = value.ToString();
    }
}

Three things carry the whole world:

  • Interact runs when a player presses the object. It is an ordinary member of UdoniteBehaviour, so a typo in the name is a compile error rather than a silent no-op.
  • Synced<int> is the count every client holds. Writing .Value stores it and asks VRChat to replicate it; a player who joins later receives the current value without anyone resending it. Only the object's owner may write, which is what Ownership.Claim is for: whoever presses becomes the owner, then writes.
  • Subscribe runs the handler on every client when the value arrives, including the one that wrote it, so the label is updated in exactly one place. The label.text line in Start covers the moment before the first change.

Nothing here is Udon-specific syntax. There is no sync attribute to pair with a sync mode, no serialization method to remember to call, and no list of keywords to avoid.

4. Attach and compile

  1. Drag Counter.cs onto the Counter cube.
  2. Drag the Label object into the Label field in the inspector.
  3. Save the scene.

Saving compiles. Udonite attaches an Udon program next to the C# component; a successful compile prints nothing, and anything Udon cannot run is refused by name in the console with a UDN code and a suggested rewrite. Udonite → Open Window → Compile compiles everything by hand and prints a summary line, which is useful right after dragging a script onto an object.

5. Try it

Press Play. ClientSim gives you a local player; walk to the cube and press it. The label counts up, and the C# component is disabled while the Udon program runs, so what you are testing is what ships.

ClientSim does not run serialization, so the count is local in the editor. To watch it cross the network, use Build & Test from the SDK control panel with two clients: press in one, and the other's label follows.

6. Upload

Build and upload from the VRChat SDK control panel as you would any world. The Udon programs Udonite attached are what ships; the C# stays in your project for the next edit.

Where to go next

  • Make the count survive everyone leaving and coming back: Persistence.
  • Tell a Discord channel when the count passes a hundred: the Net package's messages, and the hosted relay from the dashboard.
  • See what compiles: Language support is the list, and it is longer than you expect.
  • When something is refused: Diagnostics explains every code.
Something went wrong Reload