Custom variable types#
Variables store a fixed set of built-in entry types. A custom variable value class adds another type that designers can store, compare, list, and save.
When you need one#
Prefer an existing type when it already carries the required data:
| Instead of a new type | Consider |
|---|---|
| A data asset of your own | Scriptable Object, which accepts any asset |
| A reference to a component | Game Object, then resolve the component |
| A small struct | Several entries, or a String you parse |
| An enum | A Number or a String |
Scriptable Object accepts any data asset and exposes it to designers without a custom variable type.
Write a new type when designers need to store, compare, list, and save the value across multiple systems.
Writing one#
Derive from TVariableValue<T>. This example assumes you've already defined the MyThing type you want to store:
using System;
using UnityEngine;
using VisualScript.Icons;
using VisualScript.Runtime;
[Title("My Thing")]
[Image(typeof(IconCube), ColorTheme.Type.Blue)]
[Serializable]
public class VariableValueMyThing : TVariableValue<MyThing>
{
[SerializeField] private MyThing m_Value;
public override MyThing Value
{
get => this.m_Value;
set => this.m_Value = value;
}
public override VariableValueBase Clone() => new VariableValueMyThing
{
m_Value = this.m_Value
};
}The type then appears in the entry-type dropdown of both Local Variables and Global Variables.
Cloning matters#
The variable container calls Clone() when it initializes, creating the values it uses at runtime from the authored entries.
A shallow clone of a mutable object is a shared object
If your type is a class rather than a struct, copying the reference means every instance of the prefab shares one object. Changing it on one enemy changes it on all of them.
Either make the type immutable, or deep-copy it in Clone().
Conversion#
Variable values participate in conversion, so a Signal asking for a number can read an entry that holds something convertible. Implement conversion where it's meaningful for your type; where it isn't, the default returns the type's default value rather than throwing.
Complete the value type#
A stored value also needs ways for designers to read, edit, and use it. Add the integrations your type requires:
| Also write | So that |
|---|---|
| A Signal type with variable get and set sources | Designers can read and write it |
| A Condition comparing two of them | Designers can branch on it |
A PropertyDrawer for the type |
Designers can edit the value in the Inspector |
| A Memory, if it isn't already serializable | It survives saving |
Scriptable Object already provides these integrations, which avoids maintaining a new value type and its supporting nodes.
Saving#
Variable values are serialized by the save system along with everything else in the container, so a type Unity can serialize saves without extra work. A type that holds a reference to a scene object needs the same care as any other scene reference — see Custom memories.
Where to go next#
- Custom Signals — add the Get and Set sources a new type needs.
- Variables — inspect the designer-facing containers.
- Custom memories — persist values that need explicit reconstruction.