Custom Conditions#
A Condition derives from Condition and implements a synchronous method that returns bool. The editor discovers the compiled type and adds it to Conditions lists.
A minimal Condition#
This Condition compares a Game Object Signal's tag with a String Signal:
using System;
using UnityEngine;
using VisualScript.Icons;
using VisualScript.Runtime;
[Title("Game Object Tag")]
[Description("Compares a Game Object tag with another value")]
[Category("Game Object/Tag")]
[Parameter("Game Object", "The Game Object targeted")]
[Parameter("Tag", "The value to compare against")]
[Image(typeof(IconTag), ColorTheme.Type.TextLight)]
[Serializable]
public class ConditionMyGameObjectTag : Condition
{
[SerializeField] private GetGameObject m_GameObject = new GetGameObject();
[SerializeField] private GetString m_Tag = new GetString();
protected override string GetTitle(GameObject source) => $"{this.m_GameObject} Tag = {this.m_Tag}";
protected override bool Run(Args args)
{
GameObject gameObject = this.m_GameObject.Get(args);
string tag = this.m_Tag.Get(args);
return gameObject != null && gameObject.CompareTag(tag);
}
}Run returns true when the resolved object exists and its tag matches m_Tag. The attributes, serialized Signals, and title follow the same shape as an Instruction, with a synchronous bool Run(Args) contract.
Conditions must be pure#
The caller evaluates Run whenever it needs an answer. This can happen several times per frame, including every frame inside While or Wait Until.
Never change state in a Condition
A Condition that increments a counter, spawns an object, or writes a variable produces results based on evaluation frequency. The caller controls that frequency, so the resulting state is unpredictable.
Use an Instruction for operations that change state.
The inversion toggle negates the result after calling Run. It doesn't prevent any side effects inside Run.
Keep it cheap#
Because Conditions can be evaluated every frame, choose an operation whose cost fits that frequency:
| Cheap | Expensive |
|---|---|
| Comparing values | Searching the scene |
| Reading a component | Physics queries |
| Reading a variable | Iterating a large collection |
| String equality | Allocating |
Use an expensive Condition only when the check requires it. Add a [Note] that identifies the cost so designers avoid evaluating it every frame through Wait Until.
Null safety#
Signals resolve to null when an object was destroyed, a variable was never set, or a lookup found nothing.
A Condition should treat that as false rather than throwing. Guard the resolved value before running the remaining check, which is omitted here:
protected override bool Run(Args args)
{
GameObject gameObject = this.m_GameObject.Get(args);
if (gameObject == null) return false;
// …
}The null guard returns a valid Condition result and keeps the containing Event running.
Condition or Bool Signal?#
Both return a boolean result. Choose based on where designers need to use the check:
| Write a | When |
|---|---|
| Bool Signal | The value is useful anywhere a boolean is — inside other Signals, as a field on a component, stored in a variable. |
| Condition | The check reads more clearly as a guard in a Conditions list. |
Prefer the Signal when both forms read well. A Bool Signal composes, can be stored, supports bindings, and still works as a guard through the Condition that wraps it.
Checklist#
[Serializable], derived fromCondition.Runis pure — no state changes anywhere.nullresults infalse, not an exception.- Evaluation cost fits the expected frequency, with expensive operations documented in
[Note]. GetTitlereads as a statement: Target has tag Enemy.
Where to go next#
- Custom Instructions — perform work instead of checking state.
- Custom Signals — provide a composable boolean value.
- Args — follow the context
Runreceives.