-
Notifications
You must be signed in to change notification settings - Fork 5
Attributes
A collection of attributes to create custom UIs for your own behaviours
The following attributes are only available to use with classes. Make sure to always inherit from UTController
base class or they will not do anything.
[CustomName(string name)]
Sets a custom name shown in the controller's header bar.
Shows <NameOfControllerClass>
with Controller
part stripped by default, e.g. MyBehaviourController
would be shown as MyBehaviour
by default
[CustomName("My Fancy Controller")]
public class MyBehaviourController : UTController {}
[HelpMessage(string message)]
Displays a box with some text below the controller's header bar and above the rest of the UI.
Usually used to describe the purpose of the behaviour if it is not obvious, or to warn user about anything in particular.
[HelpMessage("This behaviour should be enabled at all times")]
public class MyBehaviourController : UTController {}
[ControlledBehaviour(Type udonSharpBehaviour)]
Automatically adds an UdonBehaviour of the specified type to the game object if no Udon Behaviour component is provided. Makes the setup easier for the end user, allowing them to just add the Controller which will take care of everything else.
[ControlledBehaviour(typeof(MyBehaviour))]
public class MyBehaviourController : UTController {}
Note: this works by searching through UdonSharpProgramAssets to find the corresponding class. Make sure to create the program asset and the class before adding this attribute.
These attributes are meant to go on public fields of your Controller. They serve as UI buiding blocks and provide an ability to react to value changes, for example if you would like to do something specific in the scene when user checks a checkbox.
Note: these attributes use a property modifier approach, as unity doesn't allow to stack multiple property drawers otherwise. Make sure to use them in combination with a [UTEditor]
or an [UdonPublic]
attribute or they will not do anything. Both of those should go last in the chain, right before the public
keyword.
Many of them can be combined together to form more elaborate UI systems, so something like this is completely normal
[OnValueChanged("HandleChange"]
[HelpBox("Value cannot be negative", "CheckValueValidity")]
[HideIf("@!transition")]
[HideLabel] [UdonPublic]
public float duration;
Some attributes share parameter names. One of such parameters is methodName
. In most cases you will be able to pass either a method or a variable name to it (where it makes sense, there are exceptions and they are mentioned separately).
// Will execute `ShouldShowBox` to determine whether the box should be visible
[HelpBox("", "ShouldShowBox")] [UdonPublic];
public bool active;
// Must return `bool`
public bool ShouldShowBox() {
return active;
}
To use a variable instead of a method - prefix the variable name with @
, you can also invert the variable value by adding !
after that.
// Will show if active is `true`
[HelpBox("", "@active")] [UdonPublic];
public bool active;
// Will show if active is `false`
[HelpBox("", "@!active")] [UdonPublic];
public bool active;
This can be any public variable of a class, doesn't have to be specifically the same variable. You can use something like [HideInInspector]
for storing the state of the box without displaying it in the editor.
[SectionHeader(string title)]
Displays a visual separator box with some text
[SectionHeader("General")][UdonPublic]
public Transform sourceTransform;
[HelpBox(string message, [string methodName])]
Displays a box with a help message. You can conditionally hide and show the box based on a method or variable. See Common Parameters for more info.
// Always visible
[HelpBox("This option disables rotation transfer")]
[UdonPublic]
public bool ignoreRotation;
// Only visible if provided variable is set to `true`
[HelpBox("This option disables rotation transfer", "@ignoreRotation")]
[UdonPublic]
public bool ignoreRotation;
// Only visible if provided variable is set to `false`
[HelpBox("This option disables rotation transfer", "@!ignoreRotation")]
[UdonPublic]
public bool ignoreRotation;
// Only visible if the provided method returns `true`
[HelpBox("This option disables rotation transfer", "ShowIgnoreHelp")]
[UdonPublic]
public bool ignoreRotation;
public bool ShowIgnoreHelp() {
return ignoreRotation;
}
[HideIf(string methodName)]
Hides the field based on the provided method or variable. See Common Parameters for more info.
public bool enableEffects = true;
// Hides the field if `enableEffects` is unchecked
[HideIf("@!enableEffects")]
[UdonPublic]
public float effectsDuration;
public bool skipTransition;
// Hides the field if `skipTransition` is checked
[HideIf("This option disables rotation transfer", "@skipTransition")]
[UdonPublic]
public float transitionDuration;
// Hides the fields if the provided method returns true
public bool enableEffects;
public float effectsDuration;
[HideIf("HideExtras")]
[UdonPublic]
public bool extraOption1;
[HideIf("HideExtras")]
[UdonPublic]
public bool extraOption2;
public bool HideExtras() {
return enableEffects && effectsDuration > 0;
}
[HideLabel]
Simply hides the field label and draws the value field only. Helpful in combinations with things like Horizontal
attribute
// Will show the checkbox only
[HideLabel] [UdonPublic]
public bool active;
[OnValueChanged(string methodName)]
Calls provided method when the value of the variable changes so you can react to it in the UI.
Has some special behaviour when used together with ListView.
For regular fields the method signature should look like public void MethodName(object value)
// Will call the provided method with the fresh value every time it changes
[OnValueChanged("ToggleLights")] [UdonPublic]
public bool lightsActive;
// Incoming value will be the new value, while the current variable will not be updated yet.
// This allows you to compare old and new values.
public void ToggleLights(object value) {
// cast to the expected type first
var val = ((SerializedProperty) value)?.boolValue;
if (value) {
// do something here
} else {
// do something here
}
}
For array type fields the method signature is public void MethodName(object value, int index)
[OnValueChanged("HandleArrayChange")] [UdonPublic]
public string[] namesList;
public void HandleArrayChange(object value, int index) {
// if value is `null` - the value at `index` was removed
if (value == null) {
// handle removal
return;
}
// if `index` is out of range of the current array - a new value was added
if (index == namesList.length) {
// handle addition of new value
return;
}
// handle change of an existing value
}
When used with ListView
the method signature should be different: public void MethodName(object lValue, object rValue, int index)
, where lValue
and rValue
represent the left and right variable of the list view at the changed index.
You only need to attach [OnValueChanged]
to the first instante of a particular ListView
.
[OnValueChanged("HandleChange")
[ListView("Events List")][UdonPublic]
public UdonBehaviour[] udonTargets;
[ListView("Events List")][UdonPublic]
public string[] udonEvents;
// Handle a change of row values
public void HandleChange(object lValue, object rValue, int index) {
// when the value is removed - both will be null
if (lValue == null && rValue == null) {
// handle the removal of a row at `index` here
return;
}
// when the value is added - `index` will be out of range of the current list
if (index == udonTargets.Length) {
// handle addition of new row
return;
}
var lCasted = (UdonBehaviour) ((SerializedProperty) lValue)?.objectReferenceValue;
var rCasted = ((SerializedProperty) rValue)?.stringValue;
// handle change of an existing values
}
[Horizontal(string groupName)]
Combines two fields into a horizontal group based on the provided name.
You can define variables in any order, they can even have other variables between them. The fetching is done purely by the group name.
// When using object types its pretty common to use a `[HideLabel]` attribute to make the UI cleaner
[Horizontal("Group")] [HideLabel] [UdonPublic]
public GameObject varA;
// Only 2 variables per group are supported at this time
[Horizontal("Group")] [HideLabel] [UdonPublic]
public string varB;
// You can have many groups in a controller
[Horizontal("OtherGroup")] [Udon Public]
public int varC;
[Horizontal("OtherGroup")] [Udon Public]
public int varD;
[ListView(string name, [string addMethodName], [string addButtonText])]
Combines two arrays into a connected list of elements.
This is a cornerstone attribute of UdonToolkit. Due to dictionaries not being exposed in Udon we have to split things into separate arrays which makes navigating logically connected pieces of data very annoying, as well as forcing you to manually keep track of having enough elements in both arrays.
ListView covers that use case and provides some extras when combined with the Popup attribute.
[ListView("Udon Events List")] [UdonPublic]
public UdonBehaviour[] udonTargets;
[ListView("Udon Events List")] [UdonPublic]
public string[] udonEvents;
You can provide a custom add method to have full control on how the arrays are populated. It is also a good way to create something in the scene, like an instance of a prefab or a basic GameObject and set it as the list value at the same time.
You only need to configure the extra parameters in the first instance of ListView
for a particular group.
// You can also customize the add button text via an extra argument
[ListView("Udon Events List", "AddEvent", "Add new Event")] [UdonPublic]
public UdonBehaviour[] udonTargets;
// No need to define extra configuration here, the first instance of `ListView` for that group name is going to be used
[ListView("Udon Events List")] [UdonPublic]
public string[] udonEvents;
// The addition is fully relegated to your custom method, you can do whatevery you find suitable in here
public void AddEvent() {
var newTargets = udonTargets.ToList();
newTargets.Add(null);
udonTargets = newTargets.ToArray();
var newEvents = events.ToList();
newEvents.Add($"NewEvent_{events.Length}");
events = newEvents.ToArray();
}
Combining it with a [Popup]
attribute is the way this is used the most across Udon Toolkit's behaviours. Creating list of Udon events and Animator triggers becomes a matter of a single line of code that handles everything behind the scenes.
[ListView("Udon Events List")] [UdonPublic]
public UdonBehaviour[] udonTargets;
// You can combine many attributes together, here we use `Popup` to automatically populate the events list for us
[ListView("Udon Events List")]
[Popup(PopupAttribute.PopupSource.UdonBehaviour, "@udonTargets", true)]
public string[] udonEvents;
You can read more about [Popup]
and how it can be populated right here
[RangeSlider(float min, float max)]
Provides a slider to control the value in a predefined range, supports both floats and ints.
[RangeSlider(1, 10)] [UdonPublic]
public float floatValue;
// If the value is int - slider will auto snap to only int values
[RangeSlider(3, 20)] [UdonPublic]
public int intValue;
[Popup(string methodName)]
Shows a popup with options to choose from and support for multiple sources of data. Only supported on string variable types at this time.
You can provide a method or a variable to populate the popup options list. See Common Parameters for more info.
// Use a variable to populate the popup options
[SectionHeader("Popups")] [Popup("@popupOptions")] [UdonPublic]
public string popupVar;
[NonSerialized] public string[] popupOptions = {"foo", "bar", "fizz", "buzz"};
// You can also use a method to calculate options dynamically
[SectionHeader("Popups")] [Popup("GetOptions")] [UdonPublic]
public string popupVar;
public string[] GetOptions() {
return new [] { "foo", "bar", "fizz", "buzz" };
}
[Popup(PopupSource sourceType, string methodName, [bool hideLabel])]
By providing an explicit sourceType
other than Method
you can use the built-in Toolkit's ability to fetch a list of Animator Triggers or Udon Behaviour Custom Events. If there are no triggers or events available - the UI will inform you about it.
There are plans to add moure sources in the future, like Material properties and other native elements.
You can combine this with [ListView]
attribute to achieve dictionary-style results for Udon Behaviours.
// We use `[Horizontal]` to visually connect the source and the popup
[Horizontal("AnimationTrigger")] [HideLabel] [UdonPublic]
public Animator animator;
// Using an Animator source type to auto fetch Triggers from the `animator` variable
[Horizontal("AnimationTrigger")] [Popup(PopupAttribute.PopupSource.Animator, "@animator", true)]
public string somePopupVar;
[Horizontal("BehaviourTrigger")] [HideLabel] [UdonPublic]
public UdonBehaviour behaviour;
// Using an UdonBehaviour source type to auto fetch Custom Events from the `behaviour` variable
[Horizontal("BehaviourTrigger")] [Popup(PopupAttribute.PopupSource.UdonBehaviour, "@behaviour", true)]
public string behaviourPopupVar;
[Toggle([string label])]
Toggle-type button for boolean fields.
Combines well with things like [HideIf]
attribute to toggle parts of the UI on and off to hide things that are unused in a particular toggle state.
// Use the variable name as the toggle label
[Toggle] [UdonPublic]
public bool addSFX;
// Combine with [HideIf] to hide a variable that is irrelevant if the toggle is `false`
[HideIf("@!addSFX")] [Udon Public]
public float someThirdVar;
// Use a custom toggle label
[Toggle("Custom Label")] [UdonPublic]
public bool extraToggle;
[UdonPublic([string customVarName])]
Specifies that the variable should be synced into the actual Udon Behaviour variables.
You can provide a customVarName
as an argument if you want to use different variable names in the Udon Behaviour and in its controller. That might be useful if you already have a written behaviour but you want better / more readable names in the inspector without refactoring the original code.
// In MyBehaviourController.cs
[UdonPublic] public bool active = true;
// Pass a custom variable name
[UdonPublic("crypticVar")] public float readableVariableName;
...
// In MyBehaviour.cs
public bool active = true;
// This will get its value from `readableVariableName`
public float crypticVar;
[UTEditor]
A helper attribute that allows to utilize the properties provided by UdonToolkit without syncing them to Udon Behaviour but with all the UI benefits. Use it same way you would use [UdonPublic]
.
Has no visual effects by itself.
// This will display a section header despite not using `[UdonPublic]` and thus not syncing into Udon Behaviour
[SectionHeader("General")] [UTEditor]
public bool active = true;
These attributes are meant to be added to your public methods and are handled separately. The UI for these is rendered after the main inspector is done, so they will always be at the bottom.
[Button(string text)]
Displays a button that calls the specified method, useful for in-editor testing.
The main usecase here is to expose your custom events as buttons in the inspector or provide some extra functionality to speed up iteration process.
Currently these buttons are disabled unless you enter playmode.
// Creates and Interact button to trigger the builtin Udon Interact event
[Button("Interact")]
public void Interact() {
// UTController base class stores the currently attached UdonBehaviour here
if (uB == null) return;
uB.Interact();
}
// Creates a button that will send a custom event to your behaviour on click
[Button("Activate")]
public void Activate() {
if (uB == nul) return;
uB.SendCustomEvent("Activate");
}
🚨 Found an issue? File an issue
💬 Join the Discord server
🕶 Support the project if you found it useful