Control Types
A Control Type changes how a value or attribute is presented in the State Designer editor. Create one when the default field is not clear enough for your project, such as when a value needs a purpose-built selector, a compact toggle, additional layout, or a control that stays synchronized with another editor value.
Control Types affect the authoring interface only. They do not change how the state machine behaves at runtime.
What changes in the editor
A Type Control draws the editor field for a C# type. The bool example on this page presents a boolean value as a labeled toggle. The same pattern can support a project-specific data type or a more complex value such as a Shared Variable.
An Attribute Control changes the interface for a field carrying a particular C# attribute. The Space example adds the amount of vertical space specified by [Space] while leaving the field’s normal Type Control available.
State Designer uses these controls in places such as state and Shared Variable inspectors. The control system can also provide content for other editor areas, including a node view.
Choose the extension point
- Create a Type Control when every field of a particular C# type needs the same editor interface.
- Create an Attribute Control when the interface should change only for fields marked with a particular attribute.
- Set an Attribute Control’s
OverrideTypeControltotrueonly when it should replace the field’s Type Control. Returnfalsewhen both controls should contribute to the interface.
Cross-product note: Control Types belong to the shared Opsive editor system. A control may also affect installed Opsive editors such as Ultimate Character Controller, Ultimate Inventory System, or Behavior Designer. The examples below are used here for State Designer; verify a project-specific control in every Opsive editor that can display its target type or attribute.
Author a Type Control
- Place the control script in an editor-only assembly.
- Add
[ControlType]with the C# type that the control represents. - Derive from
TypeControlBase. - Set
UseLabelaccording to whether State Designer should draw a field label. - Implement
GetControland return the required UIElements control. - Send user changes through
input.OnChangeEventand restore the previous value when the change is rejected. - Use
BindingUpdaterwhen the displayed value can change outside this control.
The following implementation draws a bool as a toggle:
using Opsive.Shared.Editor.UIElements.Controls.Types;
using UnityEngine.UIElements;
[ControlType(typeof(bool))]
public class BoolControl : TypeControlBase
{
/// <summary>
/// Does the control use a label?
/// </summary>
public override bool UseLabel { get { return true; } }
/// <summary>
/// Returns the control that should be used for the specified ControlType.
/// </summary>
/// <param name="input">The input to the control.</param>
/// <returns>The created control.</returns>
protected override VisualElement GetControl(TypeControlInput input)
{
var toggle = new Toggle();
toggle.value = (bool)input.Value;
// Ensure the control is kept up to date as the value changes.
if (input.Field != null) {
System.Action<object> onBindingUpdateEvent = (object newValue) => toggle.SetValueWithoutNotify((bool)newValue);
toggle.RegisterCallback<AttachToPanelEvent>(c =>
{
BindingUpdater.AddBinding(input.Field, input.ArrayIndex, input.Target, onBindingUpdateEvent);
});
toggle.RegisterCallback<DetachFromPanelEvent>(c =>
{
BindingUpdater.RemoveBinding(onBindingUpdateEvent);
});
}
toggle.RegisterValueChangedCallback(c =>
{
if (!input.OnChangeEvent(c.newValue)) {
toggle.SetValueWithoutNotify(c.previousValue);
}
c.StopPropagation();
});
return toggle;
}
}
[ControlType(typeof(bool))] registers the target type. UseLabel keeps the field label visible, and BindingUpdater refreshes the persistent UIElements control when another part of the editor changes the value.
Author an Attribute Control
- Place the control script in an editor-only assembly.
- Add
[ControlType]with the attribute type. - Derive from
AttributeControlBase. - Decide whether the attribute should replace the normal Type Control with
OverrideTypeControl. - Set
UseLabel, then implementGetControland return the additional UIElements content.
This implementation reads Unity’s [Space] attribute and draws a VisualElement with the requested height:
using Opsive.Shared.Editor.UIElements.Controls.Attributes;
using System.Reflection;
using UnityEngine.UIElements;
[ControlType(typeof(UnityEngine.SpaceAttribute))]
public class SpaceAttributeControl : AttributeControlBase
{
/// <summary>
/// Does the attribute override the type control?
/// </summary>
public override bool OverrideTypeControl { get { return false; } }
/// <summary>
/// Does the control use a label?
/// </summary>
public override bool UseLabel { get { return false; } }
/// <summary>
/// Returns the attribute control that should be used for the specified AttributeControlType.
/// </summary>
/// <param name="input">The input to the control.</param>
/// <returns>The created control.</returns>
protected override VisualElement GetControl(AttributeControlInput input)
{
var spaceAttribute = input.Field.GetCustomAttribute<UnityEngine.SpaceAttribute>();
if (spaceAttribute == null) {
return null;
}
var visualElement = new VisualElement();
visualElement.style.height = spaceAttribute.height;
return visualElement;
}
}
OverrideTypeControl is false, so the spacing is added without removing the field’s normal control. UseLabel is also false because the spacing itself does not need a label.
Verify the editor result
After Unity compiles the editor assembly, open State Designer and select an element containing the target type or attribute. Confirm that the custom control appears in the Inspector, accepts an edit, and leaves the underlying field with the new value.
For a Type Control that uses BindingUpdater, change the same value from another supported editor surface and confirm that the visible control refreshes without closing and reopening the Inspector. For an Attribute Control, confirm both the attributed layout and whether the normal Type Control is present according to OverrideTypeControl.
The bool and Space controls shown above illustrate the registration pattern for standard types that already have editor behavior. Give a project-specific control its own class name and register only the type or attribute that it is intended to customize.
Related pages
- Extending the Editor helps choose an editor extension point.
- Node Views adds custom information directly to State Designer graph nodes.
- GameObject Action shows how to author configurable state behavior.
- GameObject Condition shows how to author configurable transition checks.
- Blackboard shows Shared Variables in the State Designer editor.