World Utilities / Concepts
Scripting with Values
Read, write and subscribe to World Utilities values from your own UdonSharp behaviours.
Every component in World Utilities is a plain UdonSharp behaviour, so your own scripts can use values just like the built-in applicators do. This lets designers keep tweaking values and UI in the inspector while your code reacts to them.
Namespaces
| Namespace | Contents |
|---|---|
VRCTools.World.LocalValues |
LocalBoolean, LocalColor, LocalFloat, LocalInt, … |
VRCTools.World.SynchronizedValues |
SynchronizedBoolean, SynchronizedFloat, … |
VRCTools.World.PersistedValues |
PersistedBoolean, PersistedFloat, … |
VRCTools.World.Values |
ValueType, ValueSource, ValueEvents, math operation enums |
VRCTools.World.Values.Converters |
Generic converters & BooleanOperation |
VRCTools.World.Values.Applicators |
Generic applicators & MaterialPropertyTarget |
VRCTools.World.Player |
Interact trigger, teleport target, trigger volume, PlayerType |
VRCTools.World.Utils |
EventBroadcaster, DelayMode, ValueUtility |
VRCTools.Event |
AbstractEventEmitter (see Event System) |
Your script’s assembly needs to reference the VRCTools.World assembly definition (and VRCTools.Event when you
subscribe to events). If your scripts live in the default Assembly-CSharp, no setup is needed.
Reading & writing
using UdonSharp;
using VRCTools.World.LocalValues;
using VRCTools.World.SynchronizedValues;
public class Scoreboard : UdonSharpBehaviour {
public SynchronizedInt score; // shared by everyone
public LocalBoolean showDebugInfo; // per player
public void _AddPoint() {
// writing State notifies all listeners - and, for synchronized values, every player in the instance
this.score.State = this.score.State + 1;
// or simply: this.score._Increment();
}
public void _ToggleDebug() {
this.showDebugInfo._Toggle();
}
}
Subscribing to changes
Values emit events through the VRCTools Event System. Register a handler with
_RegisterHandler(eventId, behaviour, methodName):
using UdonSharp;
using UnityEngine;
using VRCTools.World.LocalValues;
public class MusicVolume : UdonSharpBehaviour {
public LocalFloat volume;
public AudioSource music;
private void Start() {
this.volume._RegisterHandler(LocalFloat.EVENT_STATE_UPDATED, this, nameof(this._OnVolumeChanged));
// values publish their initial state from their own Start() which may have run already - apply it explicitly
this._OnVolumeChanged();
}
private void OnDestroy() {
// optional for objects which live as long as the world, but good practice
this.volume._UnregisterHandler(this);
}
// handlers are invoked via SendCustomEvent and therefore MUST be public
public void _OnVolumeChanged() {
this.music.volume = this.volume.State;
}
}
Handlers are invoked by name using
SendCustomEvent. Aprivatehandler compiles fine but is never called. Prefix handler names with an underscore to keep them from being triggered over the network by other players.
Events by kind
| Constant | Local | Synchronized | Persisted | Emitted when |
|---|---|---|---|---|
EVENT_STATE_UPDATED |
✓ | ✓ | ✓ | the state changed (locally or received from network) |
EVENT_STATE_RESET |
✓ | ✓ | _Reset() was called |
|
EVENT_LOADED |
✓ | stored data has been restored | ||
EVENT_PERSIST |
✓ | the state has been written to player data |
Always use the constants of the class you reference (PersistedFloat.EVENT_LOADED, not a literal number) - their
numeric ids differ between kinds.
Handling several values with one handler
Inside a handler, IsUpdatingHandlers is true on the value that is currently emitting. This lets one handler serve
many values without registering a separate method for each:
public LocalBoolean[] switches;
public void _OnAnySwitchChanged() {
for (var i = 0; i < this.switches.Length; i++) {
if (!this.switches[i].IsUpdatingHandlers) continue;
Debug.Log($"Switch #{i} is now {this.switches[i].State}");
}
}
Handlers cannot be registered while an emitter is notifying its handlers. Register in
Start()(or later, from outside a handler of the same value).
Generic code: local or synchronized
If you want your own component to support both kinds like the built-in ones do, follow the same pattern: expose a
ValueType plus one field per kind and let ValueUtility pick the right one.
using UdonSharp;
using VRCTools.World.LocalValues;
using VRCTools.World.SynchronizedValues;
using VRCTools.World.Utils;
using ValueType = VRCTools.World.Values.ValueType;
public class FeatureFlag : UdonSharpBehaviour {
public ValueType source;
public LocalBoolean localValue; // also accepts persisted booleans
public SynchronizedBoolean synchronizedValue;
private void Start() {
ValueUtility.RegisterUpdateHandler(this.source, this.localValue, this.synchronizedValue, this,
nameof(this._OnStateUpdated));
this._OnStateUpdated();
}
public void _OnStateUpdated() {
var enabled = ValueUtility.GetValue(this.source, this.localValue, this.synchronizedValue);
// ...
}
}
ValueTypecollides withSystem.ValueType. Alias it as shown above or use its fully qualified name.