【VRChat】Implementing Synced Buttons: Local, Toggle, and One-Time Patterns

Created: 2025-12-19

A collection of button implementation patterns for world creation. How to create local buttons, toggle switches synced across all players, and one-time-only buttons.

Overview

The most intuitive and fundamental way to trigger gimmicks in a world is through "buttons." When players click (Interact) a button, it can serve as a trigger for various actions—turning on lights, opening doors, starting games, and more.

This article combines all the knowledge covered so far to explain specific procedures for implementing interactive buttons in UdonSharp, broken down into several practical patterns.

  1. Local Button: The simplest button that only affects the player who pressed it.
  2. Synced Toggle Button: A button that switches ON/OFF with each press, with the state shared across all players.
  3. One-Time-Only Button: A button that triggers processing that should only execute once in the world.

Basic Unity Editor Setup (Common to All Patterns)

First, prepare a GameObject to function as a button in the Unity Editor. Here, we'll use a simple Cube as an example button.

  1. Create Object: Right-click in the Hierarchy window and select [3D Object] > [Cube] to create a cube. Rename it to something like "InteractiveButton" for clarity. Adjust the size and position as needed.
  2. Verify Collider: The created cube has a Box Collider component attached by default. This is required to detect player clicks.
  3. Add Udon Behaviour: Select the button object and click [Add Component] in the Inspector window. Add an "Udon Behaviour."
  4. Assign Script: Create the corresponding UdonSharp script and drag & drop it onto the Udon Behaviour component's Program Source field.
  5. Set Interaction Text: After assigning the script, an Interaction Text field appears in the Inspector. Enter text like "Press" or "Click" here. This becomes the tooltip displayed to players, and the Interact event will not fire without this setting.
Setting up Interaction Text

Pattern 1: Local Button (Sound Playback Button)

A button that plays sound only on the client of the player who pressed it. It doesn't affect other players.

Script: LocalSoundButton.cs

using UdonSharp;
using UnityEngine;

public class LocalSoundButton : UdonSharpBehaviour
{
    [Tooltip("Assign the AudioSource to play when clicked.")]
    public AudioSource targetSound;

    public override void Interact()
    {
        // Only execute if targetSound is set
        if (targetSound != null)
        {
            targetSound.Play();
        }
        else
        {
            Debug.LogWarning("No sound is set to play.");
        }
    }
}

Unity Setup

  1. Create the LocalSoundButton.cs script and assign it to the button object's Udon Behaviour.
  2. Create an empty GameObject in the scene and name it something like "SoundPlayer." Add an AudioSource component to this object.
  3. Assign the audio file you want to play to the AudioSource component's AudioClip field, and uncheck Play On Awake.
  4. Return to the button object's Inspector and drag & drop the GameObject containing the AudioSource onto the Local Sound Button component's Target Sound field.

Now, only the player who clicks the button will hear the specified sound play.

Pattern 2: Synced Toggle Button (Room Light Switch)

Lighting switch operation test

A switch that toggles room lighting ON/OFF with each press, with the state synchronized across all players in the instance. This is a practical implementation of the basic network synchronization pattern.

Script: SyncedLightSwitch.cs

using UdonSharp;
using UnityEngine;
using VRC.SDKBase;

public class SyncedLightSwitch : UdonSharpBehaviour
{
    [Header("Settings")]
    [Tooltip("Assign the Light GameObject to toggle ON/OFF.")]
    public GameObject roomLight;

    // Variable to sync light state across all players
    [UdonSynced]
    private bool isLightOn = true; // Initial state is ON

    void Start()
    {
        // Reflect current sync state to light when joining world
        UpdateLightState();
    }

    public override void Interact()
    {
        // Request ownership of this object for yourself
        Networking.SetOwner(Networking.LocalPlayer, this.gameObject);

        // Toggle the light state
        isLightOn = !isLightOn;

        // Immediately reflect changed state on yourself
        UpdateLightState();

        // Notify all other players of the change
        RequestSerialization();
    }

    // Called when receiving data from other players
    public override void OnDeserialization()
    {
        // Reflect the latest synced state to the light
        UpdateLightState();
    }

    // Method that consolidates the processing to toggle the Light GameObject
    private void UpdateLightState()
    {
        if (roomLight != null)
        {
            roomLight.SetActive(isLightOn);
        }
    }
}

Unity Setup

  1. Create the SyncedLightSwitch.cs script and assign it to the button object's Udon Behaviour.
  2. Place a GameObject with a Light component (e.g., Point Light) in the scene to serve as the lighting.
  3. In the button object's Inspector, drag & drop the created light object onto the Synced Light Switch component's Room Light field.

Testing Tip: To easily verify light ON/OFF, it's effective to darken the scene.

  1. Open [Window] > [Rendering] > [Lighting] from the menu
  2. In the "Environment" tab, set Skybox Material to None
  3. Change Environment Lighting > Source to Color
  4. Set Ambient Color to black (#000000)
  5. Disable or delete the Directional Light in the scene

This makes the scene completely dark, making the light's effect easy to see.

Now, whenever anyone presses the button, the light will toggle ON/OFF in everyone's view within the instance.

Pattern 3: One-Time-Only Button (Game Start Button)

A button that triggers processing that should not be executed again once run, such as starting a game. Using synchronization, it becomes unpressable for all players once someone has pressed it once.

Script: OneTimeGameStartButton.cs

using UdonSharp;
using UnityEngine;
using VRC.SDKBase;

public class OneTimeGameStartButton : UdonSharpBehaviour
{
    [Header("Settings")]
    [Tooltip("MeshRenderer to display as button")]
    public MeshRenderer buttonRenderer;
    [Tooltip("Material after button is pressed")]
    public Material pressedMaterial;

    // Variable to sync whether button has already been pressed
    [UdonSynced]
    private bool isPressed = false;

    void Start()
    {
        // Check state on startup
        CheckButtonState();
    }

    public override void Interact()
    {
        // Only execute if not yet pressed
        if (!isPressed)
        {
            Networking.SetOwner(Networking.LocalPlayer, this.gameObject);
            isPressed = true;

            // Update state and notify of change
            CheckButtonState();
            RequestSerialization();

            // --- Write game start processing here ---
            Debug.Log("Starting the game!");
            // Example: Notify GameManager with SendCustomEvent, etc.
        }
    }

    public override void OnDeserialization()
    {
        // Receive info that someone else pressed it and update state
        CheckButtonState();
    }

    private void CheckButtonState()
    {
        // If already pressed
        if (isPressed)
        {
            // Disable interaction on the button
            this.DisableInteractive = true;
            // Change appearance to "after pressed" material
            if (buttonRenderer != null && pressedMaterial != null)
            {
                buttonRenderer.material = pressedMaterial;
            }
        }
    }
}

Unity Setup

  1. Create the OneTimeGameStartButton.cs script and assign it to the button object's Udon Behaviour.
  2. Create a separate Material asset for the button's post-press appearance (e.g., a darker material).
  3. In the button object's Inspector, assign the button's own MeshRenderer to Button Renderer and the created material to Pressed Material.

Now, once anyone in the instance presses the button, it becomes non-interactable and its appearance changes. This allows all players to recognize that "this button has already been used."

Summary

  • Buttons are fundamentally implemented using the Interact event as the starting point.
  • It's important to be conscious of whether processing is local or synced, and choose the appropriate implementation pattern.
  • For synced buttons, you need to accurately implement the flow of "acquire ownership → change value → request serialization → receive and update."
  • By combining the DisableInteractive property and material changes, you can visually communicate button state to players.

Buttons are simple gimmicks, but they contain all the fundamental elements of UdonSharp condensed together. By applying these patterns, you can build increasingly complex interactions.