Multiplayer experiences are the heart of VRChat, so creating a world that reacts to players and synchronizes the data between them is key.
This page introduces the concepts that power our networking system. Once you've understood the basics, you can dig into specifics:
The three main concepts used for networking in Udon are Variables, Events and Ownership.
Variables are containers for values - like a number, a set of colors or a 3D position.
Events are things that happen at a moment in time.
Ownership is the system that decides which user can update a variable, which is then sent to every other user.
For a scoreboard in a game, you might use a variable to store and update user scores, and an event to trigger fireworks for the winner.
Objects in a world are local by default. That means that an object you pick up only moves for you, no one else sees it moving. To synchronize the object, you need to tell VRChat that you want it to be a Networked Object.
To make an object Networked, you can add an UdonBehaviour and/or a VRC Object Sync component to it
The first player who opens a world becomes the owner of all the Networked Objects. They can make changes to those objects and the changes will be sent to everyone else. When you change the Owner of an object, the new owner is in charge of the network data and everyone else will listen for their changes.
If you have a 3D object with a renderer and a collider, you can easily make it something that people can pick up and sync.
All you need to do is add a VRCPickup component and a VRCObjectSync Component to its GameObject.
VRCPickup adds a Rigidbody to your GameObject if it doesn't already have one, and signals VRChat to allow the item to be picked up, and transfer ownership of the item to whoever grabs it.
VRCObjectSync automatically syncs the object - sending its position, rotation, scale and some physics properties to the other players so that it looks the same to everyone. To sync other data, you need variables.
A variable is a container for a value. UdonBehaviours run Udon Programs, and you can add variables to these programs.
In the image above, I've made three different variables, and you can see that I've checked the 'synced' box for the 'sliderValue' variable. The Owner of this GameObject will be in charge of this variable value, and their changes will be sent to everyone else.
In this example, the Owner of a Slider syncs its value to everyone else. Note that this is meant to illustrate the concepts - we'll release a separate example that goes into the nitty-gritty 'how-to' details.
To sync the Slider, we just need to get its number value. This will be a number with a decimal point between 0 and 1, which we call a floating point value, or a float for short. So we make a variable called sliderValue, with a type of float.
We set up our slider to update this value whenever the slider is moved. For the owner, this is straightforward - when the slider moves, we get its new value, and update our variable. This value will be packed up and sent to everyone else, which is called Serialization. When it's received and unpacked by the other users, that's called Deserialization.
So the owner moves the slider and sets sliderValue. VRChat updates sliderValue on the other players, and triggers an event called OnDeserialization on the other users. When this event is triggered, they use sliderValue to update the position of the slider and the text in the readout.
Owner: Moves Slider > OnValueChanged > set sliderValue from UISlider.value > Update readout.
Others: sliderValue updated by VRChat > OnDeserialization triggered > set UISlider.value > OnValueChanged > Update readout.
Events happen, and then they're gone. Unlike variables, which can only be updated by the Owner of an object, anyone can call an event on an Object. You can choose to send it to everyone, or just to the owner of that object. This is done by selecting target: All or target: Owner when sending the event.
In this example, we have an object with a particle system and an animator that spins its bubble wand and generates bubble particles. We want this to happen for everyone in the world when the user holding the wand presses the trigger.
In our Udon Graph, we have a custom event we call "Trigger" which Plays the 'Spin' animation and triggers 22 Particles to Emit - this is just a local event in our graph.
To make this happen for everyone, we tie the OnPickupUseDown event which is triggered when someone presses Use while holding our Bubble Gun, and we use SendCustomNetworkEvent witrh a target of All to fire the "Trigger" event for everyone, including the Owner of the object.
What happens to people who join your world after some synchronization has happened? It's straightforward: Variables will be updated, events will not. When someone joins your world, the OnDeserialization event will fire for every Networked Object in the world with the latest data, and they'll run whatever logic you have in place to update things based on that data. Events are gone, however - there's no reason for them to fire off the bubble particles an hour after someone pressed the trigger.
Sync is done through Variables and Events. For variables, the owner of a Networked Object updates a variable and sends that data to all the other players who Deserialize it. Anyone who enters a world gets the latest data to Deserialize. For events, anyone can send a NetworkEvent. It will either be received by the owner or by everyone in the world at that time.
We've included the three examples above in a simple package you can import into any project which has the Udon SDK in order to see them working and explore the graphs yourself.
This first section serves as a broad overview of networking with Udon in VRChat. Once you feel like you've got a grasp of the concepts and you've explored the example package above, you can learn further details of each aspect of the system below.
There are four ways you can synchronize data and events in your world:
Use this when you have a variable that you want to update frequently, and it's ok if it sometimes doesn't update to save bandwidth for other things. This will sync for late joiners.
Example: A tree that grows as someone waters it, with a continuous 'size' variable. It's ok if you miss a few updates since it will jump to the right position on the next update you get. See Using Variables below.
Use this when you have a variable that will update less frequently, and it's super important that its value is always up-to-date. This will sync for late joiners. This option is not compatible with Object Sync. See Using Variables below.
Example: The 'score' of each team in a basketball game. This only changes when someone makes a basket and you definitely don't want to miss an update.
Use this to trigger an event for every player currently in the instance, or for the owner of an object. It is guaranteed to arrive, but will have a fair amount of delay and overhead. It will not be received by anyone who joins after the event was sent. See Using Events below.
Example: A laser effect that fires as part of your Dance Club. You want everyone in the club to see it around the same time, but if someone comes in 20 minutes later, it's ok that they missed it.
Some VRChat-specific objects are automatically synced. This includes:
- Avatars: Includes their colliders, voice, and IK movement.
- VRCObjectSync: Includes the Transform and Rigidbody of the object.
In VRChat, every GameObject is 'owned' by one player (VRCPlayerApi) at a time. Only the Owner of an object can change its values. Those changes are then sent to everyone else in the instance.
These are the steps to change ownership of an object:
- Someone calls Networking.SetOwner(VRCPlayerApi player, GameObject obj) (this can be the current owner or anyone else)
- The event OnOwnershipRequest(VRCPlayerApi requester, VRCPlayerApi newOwner) is called on the UdonBehaviour of the object in question for everyone in the instance. Both the requesting player and the owner of the object can set a return value of true or false to allow or deny the transfer.
- If the requesting player sets the Return Value to false, the transfer is immediately canceled and the event isn't called for anyone else in the instance. Otherwise, all other players including the owner will receive the event.
- If the owner sets the Return Value to false, nothing changes. Otherwise, ownership is transferred, and the event OnOwnershipTransferred(VRCPlayerApi player) is called on the GameObject for everyone in the instance, where player is a reference to the new owner of the object.
Note that setting the Return Value to true has the same effect as not setting the return value at all. So if you don't want the requesting player to block the transfer, you don't have to set the value. This is why all transfers are approved if you don't include any logic to SetReturnValue in your program.
If you want a player to be able to change a value on an object, make sure to check or request ownership first.
Note that on an UdonBehaviour using Manual Sync, you can call SetOwner, change a synced Variable, then called RequestSerialization, and the new value of the variable should be properly serialized to everyone else. When using Continuous Sync, there's a possibility that the Variable change could be dropped.
When creating the logic which allows transfer of Ownership by using SetReturnValue, it's helpful to know that this actually runs locally, on the client of whoever called Networking.SetOwner. So if your transfer relies on a variable, make sure it's a Synced variable so that the logic will be the same for each Player. If not, there could be a desync of data, which would hopefully resolve.
Using a variable to sync data takes three steps:
- Create the variable.
- Update the value on the Owner.
- React to changes in value received from the Owner.
- Click the + button in the Variables window
- Choose your variable type
- Rename your variable (optional, but do it)
- Click the arrow next to the variable name to show more options, turn on 'synced'. (The default value of 'none' is fine - this just means the value is not automatically smoothed out)
- Drag and drop the variable onto your graph while holding 'Ctrl' to make a 'Set Variable' node.
- Connect an event or flow to the Flow Port on this node, and connect a new value to the Value Port.
- If this UdonBehaviour is using Continuous Sync (selected on the UdonBehaviour itself in the inspector) then you're done with updating the value. If you're using Manual Sync, then you need to add an "UdonBehaviour.RequestSerialization" node and connect the output from your Set Variable Flow Port to the Flow Input port on this node. You can leave the 'instance' Value Port on this node empty, it will default to the current UdonBehaviour, which is what we want.
- Add an "OnDeserialization" node to this same graph.
- Drag and Drop the variable onto your graph without holding Ctrl to create a 'Get Variable' node.
- Use the flow coming from the OnDeserialization node and the value from the Get Variable node to update another node with this new value.
This node is used in Manual Sync mode to flag the variables on the target UdonBehaviour for Serialization during the next Network Tick, which does not happen every frame. This node works will with the OnPreSerialization Event node. You trigger "RequestSerialization" and then the OnPreSerialization event will trigger during the next Network Tick. At that point, you can update any variables to the values you would like to be synced.
You can sync variables of the following types:
bool, char, byte, sbyte, short, ushort, int, uint, long, ulong, float, double, string, Vector2, Vector3, Vector4, VRCUrl, Quaternion, VRCUrl, Color and Color32.
You can sync variables which are arrays for the following types:
bool, char, byte, sbyte, short, ushort, int, uint, long, ulong, float, double, Vector2, Vector3, Vector4, Quaternion, Color and Color32.
Using an Event to fire a change takes 2 steps:
- Add a Custom Event node
- Use a SendCustomNetworkEvent node to trigger this event on your target(s).
- Create an "Event Custom" node.
- Give this node a unique name using its input box
- Add a "Send Custom Network Event Node"
- Enter this same event name in the 'eventName' input.
- Leave the default 'All' as the target to trigger this event on each Player in your room, or change it to 'Owner' to only fire this event on the Owner.
- You can leave the 'instance' input empty to target the current UdonBehaviour, or connect a reference to another UdonBehaviour to fire a Custom Event on that one instead.
SendCustomNetworkEvent will work as a 'SendCustomEvent' node in the Editor to allow for some basic testing.
If you start your Event names with an underscore, you will not be able to call them over the network. We do this to safeguard our internal methods like _start, _update, _interact against malicious network calls. We have plans to add an attribute to events to mark them as 'local-only' without the need for an underscore. If you want to block events from remote execution in the meantime, you can use a unique underscore prefix like '_u_eventName' to make sure it doesn't match any existing or future VRC methods.
You can view some information about your networked objects in the client if you launch with
--enable-debug-gui and press RightShift + ` + 8 while in the client.
These overlays show you the NetworkId, name of the GameObject, Ping time, Quality of the data (100% is no dropped packets) and Owner of the GameObject.
You can see some per-object information in list form using RightShift + ` + 6 in the client:
The latest build & SDK have these issues:
Updated about a month ago
Now that you know the concepts behind Networking - Ownership, Variables, Custom Events and Debugging, you can learn more about Network-Specific Components, Events and Properties below.
|Network Specs and Tips|