GameObject Condition

GameObject-based conditions are the recommended way to create custom conditions in State Designer unless profiling shows you need ECS-specific optimization. They are simple to write, easy to debug, and use a familiar callback-based API. A GameObject condition derives from Opsive.StateDesigner.Runtime.Conditions.Condition Conditions are attached to transitions. They do not run behavior directly like actions. Instead, they return a boolean from IsValid() to tell the transition whether it can trigger.

GameObject Condition API

A custom condition can implement the following callbacks:

/// <summary>
/// Callback when the state machine is initialized.
/// </summary>
public virtual void OnAwake()

/// <summary>
/// Callback when the state machine is started.
/// </summary>
public virtual void OnStateMachineStarted()

/// <summary>
/// Callback when the condition starts.
/// </summary>
public virtual void OnStart()

/// <summary>
/// Is the condition valid? If it is valid then the transition will occur.
/// </summary>
/// <returns>True if the condition is valid.</returns>
public abstract bool IsValid()

/// <summary>
/// Callback when the condition stops.
/// </summary>
public virtual void OnEnd()

/// <summary>
/// Callback when the state machine is stopped.
/// </summary>
/// <param name="pause">Is the state machine paused?</param>
public virtual void OnStateMachineStopped(bool pause)

/// <summary>
/// Callback when the state machine is destroyed.
/// </summary>
public virtual void OnDestroy()

Physics Callbacks

Physics callbacks are opt-in for efficiency. If your condition needs one, override the corresponding receive property with true:

  • ReceiveCollisionEnterCallback
  • ReceiveCollisionExitCallback
  • ReceiveCollisionEnter2DCallback
  • ReceiveCollisionExit2DCallback
  • ReceiveTriggerEnterCallback
  • ReceiveTriggerExitCallback
  • ReceiveTriggerEnter2DCallback
  • ReceiveTriggerExit2DCallback
  • ReceiveControllerColliderHitCallback

Then implement the matching callback method (OnTriggerEnterOnCollisionEnter, etc.).

Save/Load API

Conditions support runtime save/load:

/// <summary>
/// Specifies the type of reflection that should be used to save the action.
/// </summary>
/// <param name="index">The index of the sub-action.</param>
public virtual MemberVisibility GetSaveReflectionType(int index)

/// <summary>
/// Returns the current action state.
/// </summary>
/// <param name="world">The DOTS world.</param>
/// <param name="entity">The DOTS entity.</param>
/// <returns>The current action state.</returns>
public virtual object Save(World world, Entity entity)

/// <summary>
/// Loads the previous action state.
/// </summary>
/// <param name="saveData">The previous action state.</param>
/// <param name="world">The DOTS world.</param>
/// <param name="entity">The DOTS entity.</param>
public virtual void Load(object saveData, World world, Entity entity)

MemberVisibility options:

  • MemberVisibility.All: Save public and private fields via reflection.
  • MemberVisibility.Public: Save public and serialized private fields via reflection.
  • MemberVisibility.None: No reflection-based save. Implement Save and Load manually.

Example: HasEnteredTrigger

This condition returns true after a matching trigger is entered.

using Opsive.GraphDesigner.Runtime;
using Opsive.GraphDesigner.Runtime.Variables;
using Opsive.Shared.Utility;
using Opsive.StateDesigner.Runtime.Conditions;
using Unity.Entities;
using UnityEngine;

[NodeDescription("Returns true after a matching trigger is entered.")]
public class HasEnteredTrigger : Condition
{
    [Tooltip("Optional tag filter. Leave empty to accept any trigger.")]
    [SerializeField] protected SharedVariable<string> m_Tag;

    private bool m_EnteredTrigger;

    protected override bool ReceiveTriggerEnterCallback => true;

    /// <summary>
    /// Callback when the condition starts.
    /// </summary>
    public override void OnStart()
    {
        m_EnteredTrigger = false;
    }

    /// <summary>
    /// Is the condition valid?
    /// </summary>
    public override bool IsValid()
    {
        return m_EnteredTrigger;
    }

    /// <summary>
    /// Trigger enter callback.
    /// </summary>
    protected override void OnTriggerEnter(Collider other)
    {
        if (!string.IsNullOrEmpty(m_Tag.Value) && !other.gameObject.CompareTag(m_Tag.Value)) {
            return;
        }

        m_EnteredTrigger = true;
    }

    /// <summary>
    /// Save manually.
    /// </summary>
    public override MemberVisibility GetSaveReflectionType(int index)
    {
        return MemberVisibility.None;
    }

    public override object Save(World world, Entity entity)
    {
        return m_EnteredTrigger;
    }

    public override void Load(object saveData, World world, Entity entity)
    {
        m_EnteredTrigger = (bool)saveData;
    }
}