[VRChat] A Synchronized Door: The Same Open State on Every Screen

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

Lining up a button-operated door across everyone's screens. Covers why you sync a bool rather than the Animator's state, how late joiners get it, and handling ownership.

You built a button-operated door. Then you go in with a friend and it's open on your screen while theirs shows it closed.

Things like doors, where everyone should see the same state, need their open state shared. And if a late joiner doesn't get the current state, one side sees a wall that isn't there.

This article builds a door that shows the same open state on every screen.

A door one person pressed, open on both screens

What You'll Learn

  • Why you sync a bool rather than the Animator's state
  • The mechanism that reaches late joiners
  • The shape where the presser takes ownership
  • What happens on simultaneous presses

Start from having tried networking basics and driving a door with Animator and Udon.

Sponsored


You send only "is it open"

Trying to sync a door brings up the idea of "send the animation state to everyone." You don't need to.

What you send is one boolean, isOpen.

Sending only the open-state bool, with each receiver applying it to their Animator

Three reasons.

  • Only simple values can be synced: the Animator's state itself can't be sent
  • The connection stays light: one boolean is a tiny amount of data
  • It handles late joiners: synced variables deliver the latest value, so arrivals know it's currently open

The flow looks like this.

Presser: flip isOpen → apply to their own Animator → ask for sending
Receiver: isOpen arrives → apply to their own Animator

Each person drives their own door from the value they received. That's exactly the same shape as the light in networking basics. Only the target changed, from a light to a door.

Sponsored

Hands-On: Build a door that opens for everyone

Press a button and the door opens on every screen. Late joiners get the state as it stands.

1. Prepare the door and button

Build the same structure as driving a door with Animator and Udon. If you haven't, follow those steps first.

DoorPivot (empty object at the door's edge, with the Animator)
└─ DoorPanel (the door panel)

The Animator Controller has a Bool parameter IsOpen and two states, DoorClosed and DoorOpen. Has Exit Time is unchecked on the transitions.

Place the button near the door as DoorButton.

2. Write the code

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

using UdonSharp;
using UnityEngine;
using VRC.SDKBase;

[UdonBehaviourSyncMode(BehaviourSyncMode.Manual)]   // Send only when it changes
public class SyncedDoor : UdonSharpBehaviour
{
    [SerializeField] private Animator doorAnimator;

    [UdonSynced] private bool isOpen;   // The value shared by everyone

    private void Start()
    {
        ApplyState();   // Apply the local value first
    }

    public override void Interact()
    {
        if (!Utilities.IsValid(Networking.LocalPlayer)) return;

        // 1. Take ownership (the presser becomes the one who can write)
        if (!Networking.IsOwner(gameObject))
        {
            Networking.SetOwner(Networking.LocalPlayer, gameObject);
        }
        if (!Networking.IsOwner(gameObject)) return;

        // 2. Change the value and apply it on your own screen
        isOpen = !isOpen;
        ApplyState();

        // 3. Ask for it to be sent
        RequestSerialization();

        Debug.Log("[SyncedDoor] open=" + isOpen);
    }

    // Called when a value arrives (not called on the person who pressed)
    public override void OnDeserialization()
    {
        ApplyState();
    }

    // Read the value and build the door's state. Same result however often it's called
    private void ApplyState()
    {
        if (doorAnimator != null)
        {
            doorAnimator.SetBool("IsOpen", isOpen);
        }
    }
}

The structure matches the light from the networking introduction. Only the body of ApplyState() changestargetLight.enabled becomes SetBool.

Noticing that the same shape gets reused makes synchronization far easier to handle. Design it as "hold one state and build the appearance from it" and you can sync anything with the same code.

Calling ApplyState() from Start() too is the late-joiner handling. Someone arriving later either reaches Start() with the delivered value already in hand, or receives it in OnDeserialization(). Either way the door ends up correct.

3. Assign it

Add a Udon Behaviour to DoorButton and set SyncedDoor.

FieldWhat to set
Door AnimatorDoorPivot from the Hierarchy
Interaction TextOpen the door
Dragging the door's pivot into the Animator field

The Sync Mode field is either hidden or set to Manual, because the attribute in code specifies it.

Check with two people

Once ClientSim confirms your own screen works, check in actual VRChat.

Watching whether both screens line up across two clients

In the SDK's Builder, set Number of Clients to 2, enable Force Non-VR, and run Build & Test.

OrderActionExpected result
1A and B enterThe door is closed for both
2A presses the buttonA's door opens, and B's opens shortly after
3B presses the buttonB takes ownership and both close
4A opens it, then B re-entersB enters with it open, without having pressed

The fourth row is the most important check in this article. Because you're using a synced variable, the latest state reaches whoever arrives later.

If the door looks closed here, check that you're calling ApplyState() in Start() or OnDeserialization().

Common Pitfalls

  • Your own screen doesn't move either → This is a pre-synchronization problem. Check the Animator's parameter name and the Door Animator assignment
  • Only you move; it doesn't reach others → Check the Manual attribute and RequestSerialization()
  • Only the presser doesn't move → Check that you also call ApplyState() inside Interact(). OnDeserialization() isn't called on the presser
  • A late joiner sees it closed → You aren't calling ApplyState() in Start(), or you're conveying it with Network Events alone
  • The other person pressing reverts it → Check whether another script is touching the same Animator
Sponsored

Bonus: Good to Know Up Front

  • On simultaneous presses, the last action wins: Two people pressing at nearly the same time means one takes ownership first. For a door, everyone converges, so there's no real harm. Uses where losing an update matters (scores) need a different design
  • Automatic doors are a step harder: Syncing "open when someone enters the volume" requires sharing "is anyone in the volume right now" with everyone. Counting people and handing over when someone leaves get tangled up, which goes beyond this article
Sharing whether anyone is in the volume increases what you have to track
  • A locking door is the same shape: Just add "is it unlocked" to the synced variables alongside "is it open." More state doesn't change how you write it
  • Play the sound locally: Playing the opening sound inside ApplyState() makes it play on everyone's screen. There's no need to sync the sound itself
  • It's useful all over: An escape game's door, an event venue's entry gate, a hidden room's mechanism, a shop world's shutter. All the same shape

Summary

A synchronized door is built with the same shape as the light in the networking introduction.

  • You send one boolean: "is it open"
  • The presser takes ownership, then changes the value and asks for sending
  • Gather the visual application into ApplyState() and call it at startup, on press, and on receive
  • With a synced variable, late joiners get the latest state

The question to ask while building is: "Should someone arriving later see this door's state?" Yes means a synced variable.

To share held props' positions, go to VRC Object Sync. To convey a momentary effect, go to network events.

VRChat Notes in this section63