GameObject Action
GameObject-based actions are the recommended way to create custom actions in State Designer unless profiling shows you need ECS-level optimization. They are easy to author, have a familiar lifecycle, and run inside an Action State.
/// <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 action is started. /// </summary> public virtual void OnStart() /// <summary> /// Executes the action logic. /// </summary> /// <returns>The status of the action.</returns> public virtual StateStatus OnUpdate() /// <summary> /// Callback when the action 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()
A custom GameObject action derives from Opsive.StateDesigner.Runtime.Actions.Action.
Physics Callbacks
For performance, physics callbacks are only registered when needed. Override the corresponding receive property with true:
- ReceiveCollisionEnterCallback
- ReceiveCollisionExitCallback
- ReceiveCollisionEnter2DCallback
- ReceiveCollisionExit2DCallback
- ReceiveTriggerEnterCallback
- ReceiveTriggerExitCallback
- ReceiveTriggerEnter2DCallback
- ReceiveTriggerExit2DCallback
- ReceiveControllerColliderHitCallback
Then implement the callback method (OnTriggerEnter, OnCollisionEnter, etc.).
Save/Load API
If your action needs runtime persistence, implement 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: Move Towards a Random Point
This action chooses a random destination in a radius around a center, moves toward it, and finishes once it arrives. It also saves/restores the chosen destination.
using Opsive.GraphDesigner.Runtime;
using Opsive.GraphDesigner.Runtime.Variables;
using Opsive.Shared.Utility;
using Opsive.StateDesigner.Runtime.Actions;
using Opsive.StateDesigner.Runtime.States;
using Unity.Entities;
using UnityEngine;
[Category("Movement")]
[NodeDescription("Moves the agent towards a random position within the specified radius.")]
[NodeIcon("Assets/MyIcon.png")]
public class MoveTowardsRandomPoint : Action
{
[Tooltip("The center point of the random position.")]
[SerializeField] protected SharedVariable<Vector3> m_Center;
[Tooltip("The radius that contains the random position.")]
[SerializeField] protected SharedVariable<float> m_Radius = 10f;
[Tooltip("The speed that the agent should move towards the destination.")]
[SerializeField] protected SharedVariable<float> m_MoveSpeed = 5f;
[Tooltip("Distance threshold used to consider the destination reached.")]
[SerializeField] protected SharedVariable<float> m_ArriveDistance = 0.5f;
private Vector3 m_Destination;
/// <summary>
/// Callback when the action is started.
/// </summary>
public override void OnStart()
{
m_Destination = m_Center.Value + Random.insideUnitSphere * Random.Range(0f, m_Radius.Value);
}
/// <summary>
/// Executes the action logic.
/// </summary>
/// <returns>The status of the action.</returns>
public override StateStatus OnUpdate()
{
if (Vector3.Distance(transform.position, m_Destination) <= m_ArriveDistance.Value) {
return StateStatus.Finished;
}
transform.position = Vector3.MoveTowards(transform.position, m_Destination, m_MoveSpeed.Value * Time.deltaTime);
return StateStatus.Running;
}
/// <summary>
/// Specifies the type of reflection that should be used to save the action.
/// </summary>
public override MemberVisibility GetSaveReflectionType(int index)
{
// Save manually.
return MemberVisibility.None;
}
/// <summary>
/// Returns the current action state.
/// </summary>
public override object Save(World world, Entity entity)
{
return m_Destination;
}
/// <summary>
/// Loads the previous action state.
/// </summary>
public override void Load(object saveData, World world, Entity entity)
{
m_Destination = (Vector3)saveData;
}
}
That’s all you need for a typical custom GameObject action.