Custom Triggers#
A Trigger derives from Trigger, subscribes in OnEnable, and calls Run(args) when the observed event occurs. Its lifecycle must pair every subscription with an unsubscription.
The lifecycle#
Pair setup and cleanup with the Event component's enabled state:
| Override | Called when | Do here |
|---|---|---|
OnEnable() |
The Event component is enabled. | Subscribe. |
OnDisable() |
The Event component is disabled. | Unsubscribe. |
OnDestroy() |
The component is destroyed. | Final cleanup. |
The base class handles OnAwake and assigns Component, which identifies the Event component that owns the Trigger. Access it from OnEnable onward.
Every subscription needs its unsubscription
A Trigger that subscribes in OnEnable and doesn't unsubscribe in OnDisable retains its callback after the Event is disabled. If the object is then destroyed, that callback can run against a destroyed component.
Write both halves at the same time.
A minimal Trigger#
This Trigger subscribes to MySystem.EventHappened while enabled and forwards its GameObject argument as Target. The example assumes your system already defines that static event:
using System;
using UnityEngine;
using VisualScript.Icons;
using VisualScript.Runtime;
[Title("On My Event")]
[Description("Executed when the custom system raises its event")]
[Category("My System/On My Event")]
[Image(typeof(IconBell), ColorTheme.Type.Lime)]
[Serializable]
public class TriggerMyEvent : Trigger
{
public override void OnEnable()
{
base.OnEnable();
MySystem.EventHappened += this.OnHappened;
}
public override void OnDisable()
{
base.OnDisable();
MySystem.EventHappened -= this.OnHappened;
}
private void OnHappened(GameObject who)
{
using Args args = Args.Get(this.Component, who);
this.Run(args);
}
}OnHappened acquires a pooled Args, sets the Trigger owner as Source, and calls the inherited Run method. The using declaration releases the context afterward.
Supplying context#
The Args passed to Run supplies the Source and Target that every Instruction in the Event sees. Accurate context keeps the Event reusable.
Use the Trigger owner as Source and the other involved object as Target:
using Args args = Args.Get(this.Component, otherObject);
this.Run(args);The using declaration disposes the pooled Args after Run returns. See Args.
Target identifies the other object involved
Instructions that resolve Target act on the object your Trigger supplies. Set it to the relevant object from the event so designers can reuse the same Instructions for different targets.
Filtering#
Most built-in Triggers use TValueOrAny<T> to filter events. Any accepts every value, while a specific Signal accepts only its resolved value:
[SerializeField]
private TValueOrAny<GetGameObject> m_Target = new TValueOrAny<GetGameObject>();
private void OnHappened(GameObject who)
{
using Args args = Args.Get(this.Component, who);
if (this.m_Target.IsSpecific && this.m_Target.Value.Get(args) != who) return;
this.Run(args);
}The filter runs after constructing Args because its Signal may depend on the current context.
Reacting to Unity messages#
Unity sends callbacks such as OnTriggerEnter and OnMouseDown to a MonoBehaviour, rather than a serialized Trigger class. Visual Script uses handles: small components added to the game object that receive and forward those messages.
Derive from the base class that manages the handle's lifetime:
[Serializable]
public class TriggerMyCollision : TriggerHandleSourceBase<HandleOnCollisionEnter3D>
{
protected override void OnTrigger(GameObject target)
{
using Args args = Args.Get(this.Component, target);
this.Run(args);
}
}The base class creates the handle on enable and shares it between Triggers that need the same message. It removes the handle when no Trigger needs it.
Watching a value instead#
For fire when this value changes, expose a Signal that reports changes. The existing On Number Change family can then observe it.
Write a Trigger when the thing you're listening to is an event, and a Signal when it's a value.
When a dedicated value-watching Trigger is required, derive from the binding base that owns the subscription lifecycle:
[Serializable]
public class TriggerMyValueChange : TriggerCoreBindBase
{
[SerializeField] private GetNumber m_Value = new GetNumber();
protected override ISubscription Subscription => this.m_Value;
}The base class subscribes to m_Value while the Trigger is enabled and runs the Event when that subscription fires.
Checklist#
[Serializable], derived fromTrigger.- Every
OnEnablesubscription has anOnDisableunsubscription. base.OnEnable()andbase.OnDisable()are called.Argsis pooled withArgs.Getand disposed.- Target is the most relevant object available.
GetTitlereads as an event: the framework prefixes it with On.
Where to go next#
- Custom Signals — expose values instead of events.
- Args — build the context supplied to
Run. - Bindings — inspect change Triggers from the designer's perspective.