Skip to content

Animation Integration

GOAP strategies control agent behavior, and animations bring that behavior to life. This page covers how to trigger, manage, and clean up animations within the strategy lifecycle.


Accessing the Animator

The Animator component is accessed through the capability system, which provides O(1) cached lookups:

var animator = context.GetCapability<Animator>();
if (animator == null)
{
    Debug.LogWarning(
        $"[{GetType().Name}] No Animator on '{context.gameObject.name}'. "
        + "Add an Animator component to the agent's GameObject.");
    return GoapActionStatus.Failure;
}

The default GoapAgentContext pre-caches the Animator automatically. If you're using a custom context, ensure you call CacheCapability<Animator>() in PreCacheCommonComponents().


The Strategy Lifecycle for Animations

OnStart — Trigger the Animation

Set animation parameters or triggers when the action begins:

public override void OnStart(IGoapAgentContext context, GoapBlackboard blackboard,
                             BaseStrategySettings settings)
{
    var animator = context.GetCapability<Animator>();
    if (animator == null) return;

    animator.SetTrigger("Attack");
}

OnUpdate — Wait for Completion

Poll the animation state to determine when the action is done:

public override GoapActionStatus OnUpdate(IGoapAgentContext context, GoapBlackboard blackboard,
                                           float deltaTime, BaseStrategySettings settings)
{
    var animator = context.GetCapability<Animator>();
    if (animator == null) return GoapActionStatus.Failure;

    // Check if the attack animation has finished
    var stateInfo = animator.GetCurrentAnimatorStateInfo(0);
    if (stateInfo.IsName("Attack") && stateInfo.normalizedTime >= 1.0f)
        return GoapActionStatus.Success;

    return GoapActionStatus.Running;
}

OnStop — Clean Up Animation State

This is critical. OnStop is called on success, failure, and external interruption (goal preemption, FSM transition). You must reset animation state to prevent stuck animations:

public override void OnStop(IGoapAgentContext context, GoapBlackboard blackboard,
                            BaseStrategySettings settings)
{
    var animator = context.GetCapability<Animator>();
    if (animator == null) return;

    // Reset triggers to prevent them from firing on re-entry
    animator.ResetTrigger("Attack");
}

Danger

If you set a trigger in OnStart but don't reset it in OnStop, the trigger persists. When the action is re-entered after an interruption, the animation may fire twice or at the wrong time.


Complete Animation Strategy Template

using System;
using UnityEngine;
using RGS.GOAP.Core;
using RGS.GOAP.Core.Interfaces;
using RGS.GOAP.Core.Strategies;

[Serializable]
public class AnimationStrategySettings : BaseStrategySettings
{
    [Tooltip("Name of the Animator trigger to fire.")]
    public string TriggerName = "Action";

    [Tooltip("Name of the animation state to wait for.")]
    public string StateName = "Action";

    [Tooltip("Animator layer to check (usually 0).")]
    public int Layer = 0;
}

[CreateAssetMenu(menuName = "RGS/GOAP/Strategies/Animation Strategy")]
public class AnimationStrategy : BaseGoapActionStrategy
{
    public override Type GetSettingsType() => typeof(AnimationStrategySettings);

    public override void OnStart(IGoapAgentContext context, GoapBlackboard blackboard,
                                 BaseStrategySettings settings)
    {
        var s = settings as AnimationStrategySettings;
        if (s == null) return;

        var animator = context.GetCapability<Animator>();
        if (animator == null)
        {
            Debug.LogWarning(
                $"[AnimationStrategy] No Animator on '{context.gameObject.name}'. "
                + "Add an Animator component.");
            return;
        }

        animator.SetTrigger(s.TriggerName);
    }

    public override GoapActionStatus OnUpdate(IGoapAgentContext context, GoapBlackboard blackboard,
                                               float deltaTime, BaseStrategySettings settings)
    {
        var s = settings as AnimationStrategySettings;
        if (s == null) return GoapActionStatus.Failure;

        var animator = context.GetCapability<Animator>();
        if (animator == null) return GoapActionStatus.Failure;

        var stateInfo = animator.GetCurrentAnimatorStateInfo(s.Layer);

        // Wait for the target state to start playing
        if (!stateInfo.IsName(s.StateName))
            return GoapActionStatus.Running;

        // Wait for the animation to finish
        if (stateInfo.normalizedTime >= 1.0f)
            return GoapActionStatus.Success;

        return GoapActionStatus.Running;
    }

    public override void OnStop(IGoapAgentContext context, GoapBlackboard blackboard,
                                BaseStrategySettings settings)
    {
        var s = settings as AnimationStrategySettings;
        if (s == null) return;

        var animator = context.GetCapability<Animator>();
        if (animator != null)
            animator.ResetTrigger(s.TriggerName);
    }
}

Multi-Agent Safety

The Animator component is per-agent (each GameObject has its own instance), so it's safe to query and modify from shared strategy SOs. This is one of the cases where per-agent state naturally lives on an existing component rather than the blackboard.

Tip

Remember: strategies are shared singletons. You can safely read/write the Animator because it's a per-agent component accessed via GetCapability<T>(). Never store animation state on the strategy SO itself.


Common Patterns

Movement + Animation

Combine NavMeshAgent movement with walk/run animation blending:

public override void OnStart(IGoapAgentContext context, GoapBlackboard blackboard,
                             BaseStrategySettings settings)
{
    var animator = context.GetCapability<Animator>();
    var navAgent = context.GetCapability<NavMeshAgent>();

    if (animator != null)
        animator.SetBool("IsMoving", true);

    // Set destination...
}

public override void OnStop(IGoapAgentContext context, GoapBlackboard blackboard,
                            BaseStrategySettings settings)
{
    var animator = context.GetCapability<Animator>();
    if (animator != null)
        animator.SetBool("IsMoving", false);

    var navAgent = context.GetCapability<NavMeshAgent>();
    if (navAgent != null && navAgent.isOnNavMesh)
    {
        navAgent.isStopped = true;
        navAgent.ResetPath();
    }
}

Speed-Based Blend

Update a speed parameter each frame for smooth animation blending:

public override GoapActionStatus OnUpdate(IGoapAgentContext context, GoapBlackboard blackboard,
                                           float deltaTime, BaseStrategySettings settings)
{
    var animator = context.GetCapability<Animator>();
    var navAgent = context.GetCapability<NavMeshAgent>();

    if (animator != null && navAgent != null)
        animator.SetFloat("Speed", navAgent.velocity.magnitude);

    // ... arrival check ...
}

What's Next