AgentStack
SKILL verified MIT Self-run

State Machine

skill-xeldaralz-everything-claude-unity-state-machine · by XeldarAlz

Generic state machine patterns — IState interface, StateMachine<T>, game state management (menu/gameplay/pause), enemy AI states, hierarchical FSM. Load when implementing state-driven behavior.

No reviews yet
0 installs
16 views
0.0% view→install

Install

$ agentstack add skill-xeldaralz-everything-claude-unity-state-machine

✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access No
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets No
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

Are you the author of State Machine? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

State Machine Patterns

A generic, reusable finite state machine for Unity. Covers player states, enemy AI, game flow (menu/gameplay/pause), hierarchical FSMs, and ScriptableObject-driven states for designer configuration.

IState Interface

The contract every state must fulfill. Keep it minimal: enter, exit, tick, and physics tick.

public interface IState
{
    /// Called once when entering this state.
    void Enter();

    /// Called once when leaving this state.
    void Exit();

    /// Called every frame while this state is active.
    void Update();

    /// Called every fixed timestep while this state is active.
    void FixedUpdate();
}

If your game does not need FixedUpdate in states (e.g., turn-based game), drop it from the interface. Keep the interface as lean as your project requires.


StateMachine Generic Class

A generic state machine that can be used with any state type. The type parameter T is typically the owner (player, enemy, game manager) so states can access it.

using System;
using System.Collections.Generic;
using UnityEngine;

public class StateMachine
{
    public IState CurrentState { get; private set; }
    public IState PreviousState { get; private set; }

    private T _owner;
    private Dictionary _states = new();

    public StateMachine(T owner)
    {
        _owner = owner;
    }

    /// 
    /// Register a state instance. Call during initialization.
    /// 
    public void AddState(IState state)
    {
        _states[state.GetType()] = state;
    }

    /// 
    /// Transition to a new state by type. Calls Exit on current, Enter on new.
    /// 
    public void ChangeState() where TState : IState
    {
        var type = typeof(TState);

        if (!_states.TryGetValue(type, out var newState))
        {
            Debug.LogError($"State {type.Name} not registered in state machine.");
            return;
        }

        if (CurrentState == newState) return; // Already in this state

        PreviousState = CurrentState;
        CurrentState?.Exit();
        CurrentState = newState;
        CurrentState.Enter();
    }

    /// 
    /// Change state by instance (useful when states are not unique by type,
    /// e.g., ScriptableObject states).
    /// 
    public void ChangeState(IState newState)
    {
        if (newState == null || CurrentState == newState) return;

        PreviousState = CurrentState;
        CurrentState?.Exit();
        CurrentState = newState;
        CurrentState.Enter();
    }

    /// 
    /// Return to the previous state.
    /// 
    public void RevertToPreviousState()
    {
        if (PreviousState != null)
            ChangeState(PreviousState);
    }

    /// 
    /// Call from the owner's Update().
    /// 
    public void Update()
    {
        CurrentState?.Update();
    }

    /// 
    /// Call from the owner's FixedUpdate().
    /// 
    public void FixedUpdate()
    {
        CurrentState?.FixedUpdate();
    }

    /// 
    /// Check if the current state is of a given type.
    /// 
    public bool IsInState() where TState : IState
    {
        return CurrentState is TState;
    }

    public T Owner => _owner;
}

Player State Example

A concrete example: player states for a 2D platformer. Each state is a class that holds a reference to the player controller.

Base Player State

public abstract class PlayerState : IState
{
    protected PlayerController Player { get; }
    protected StateMachine StateMachine { get; }

    protected PlayerState(PlayerController player, StateMachine stateMachine)
    {
        Player = player;
        StateMachine = stateMachine;
    }

    public virtual void Enter() { }
    public virtual void Exit() { }
    public virtual void Update() { }
    public virtual void FixedUpdate() { }
}

Idle State

public class PlayerIdleState : PlayerState
{
    public PlayerIdleState(PlayerController player, StateMachine sm)
        : base(player, sm) { }

    public override void Enter()
    {
        Player.Animator.Play("Idle");
        Player.Rb.velocity = new Vector2(0f, Player.Rb.velocity.y);
    }

    public override void Update()
    {
        if (!Player.IsGrounded)
        {
            StateMachine.ChangeState();
            return;
        }

        if (Player.JumpRequested)
        {
            StateMachine.ChangeState();
            return;
        }

        if (Mathf.Abs(Player.MoveInput.x) > 0.1f)
        {
            StateMachine.ChangeState();
            return;
        }

        if (Player.DashRequested)
        {
            StateMachine.ChangeState();
            return;
        }
    }
}

Jump State

public class PlayerJumpState : PlayerState
{
    public PlayerJumpState(PlayerController player, StateMachine sm)
        : base(player, sm) { }

    public override void Enter()
    {
        Player.Animator.Play("Jump");
        Player.ExecuteJump();
    }

    public override void Update()
    {
        // Transition to fall when velocity turns downward
        if (Player.Rb.velocity.y ();
            return;
        }

        // Variable jump height: cut velocity on release
        if (Player.JumpReleased && Player.Rb.velocity.y > 0f)
        {
            Player.CutJumpShort();
        }

        // Allow wall slide if touching wall
        if (Player.IsTouchingWall && !Player.IsGrounded)
        {
            StateMachine.ChangeState();
            return;
        }
    }

    public override void FixedUpdate()
    {
        Player.ApplyHorizontalMovement();
    }
}

Wiring It Up in the Player Controller

using UnityEngine;

public class PlayerController : MonoBehaviour
{
    // Public properties that states read
    public Rigidbody2D Rb { get; private set; }
    public Animator Animator { get; private set; }
    public Vector2 MoveInput { get; private set; }
    public bool IsGrounded { get; private set; }
    public bool IsTouchingWall { get; private set; }
    public bool JumpRequested { get; set; }
    public bool JumpReleased { get; set; }
    public bool DashRequested { get; set; }

    private StateMachine _stateMachine;

    private void Awake()
    {
        Rb = GetComponent();
        Animator = GetComponent();

        _stateMachine = new StateMachine(this);
        _stateMachine.AddState(new PlayerIdleState(this, _stateMachine));
        _stateMachine.AddState(new PlayerRunState(this, _stateMachine));
        _stateMachine.AddState(new PlayerJumpState(this, _stateMachine));
        _stateMachine.AddState(new PlayerFallState(this, _stateMachine));
        _stateMachine.AddState(new PlayerWallSlideState(this, _stateMachine));
        _stateMachine.AddState(new PlayerDashState(this, _stateMachine));

        _stateMachine.ChangeState();
    }

    private void Update()
    {
        ReadInput();
        CheckGround();
        CheckWall();
        _stateMachine.Update();
    }

    private void FixedUpdate()
    {
        _stateMachine.FixedUpdate();
    }

    // Movement methods called by states
    public void ExecuteJump() { /* see character-controller skill */ }
    public void CutJumpShort() { /* see character-controller skill */ }
    public void ApplyHorizontalMovement() { /* see character-controller skill */ }

    private void ReadInput() { /* read from Input System */ }
    private void CheckGround() { /* overlap circle check */ }
    private void CheckWall() { /* raycast check */ }
}

Game State Management

Use the same state machine pattern for high-level game flow.

Game States

MainMenu → Loading → Gameplay → Pause → GameOver
                       ↑          |
                       +----------+  (resume)
public class GameManager : MonoBehaviour
{
    public static GameManager Instance { get; private set; }

    private StateMachine _stateMachine;

    private void Awake()
    {
        Instance = this;
        DontDestroyOnLoad(gameObject);

        _stateMachine = new StateMachine(this);
        _stateMachine.AddState(new MainMenuState(this, _stateMachine));
        _stateMachine.AddState(new LoadingState(this, _stateMachine));
        _stateMachine.AddState(new GameplayState(this, _stateMachine));
        _stateMachine.AddState(new PauseState(this, _stateMachine));
        _stateMachine.AddState(new GameOverState(this, _stateMachine));

        _stateMachine.ChangeState();
    }

    private void Update()
    {
        _stateMachine.Update();
    }

    // Convenience methods for external callers
    public void StartGame() => _stateMachine.ChangeState();
    public void PauseGame() => _stateMachine.ChangeState();
    public void ResumeGame() => _stateMachine.RevertToPreviousState();
    public void GameOver() => _stateMachine.ChangeState();
    public void ReturnToMenu() => _stateMachine.ChangeState();
}

Pause State Example

public class PauseState : IState
{
    private GameManager _owner;
    private StateMachine _sm;

    public PauseState(GameManager owner, StateMachine sm)
    {
        _owner = owner;
        _sm = sm;
    }

    public void Enter()
    {
        Time.timeScale = 0f;
        // Show pause menu UI
    }

    public void Exit()
    {
        Time.timeScale = 1f;
        // Hide pause menu UI
    }

    public void Update() { }

    // Called by InputView when the Pause action fires (UI action map while paused).
    public void OnPausePressed() => _sm.RevertToPreviousState();

    public void FixedUpdate() { }
}

Enemy AI States

A patrol/chase/attack AI using the same state machine.

Idle → Patrol → Detect → Chase → Attack → Flee → Dead

Patrol State

public class EnemyPatrolState : IState
{
    private EnemyController _enemy;
    private StateMachine _sm;
    private int _waypointIndex;

    public EnemyPatrolState(EnemyController enemy, StateMachine sm)
    {
        _enemy = enemy;
        _sm = sm;
    }

    public void Enter()
    {
        _enemy.Animator.Play("Walk");
        _enemy.MoveSpeed = _enemy.PatrolSpeed;
    }

    public void Exit() { }

    public void Update()
    {
        // Check for player detection
        if (_enemy.CanSeePlayer())
        {
            _sm.ChangeState();
            return;
        }

        // Move toward current waypoint
        Vector3 target = _enemy.Waypoints[_waypointIndex].position;
        _enemy.MoveToward(target);

        // Advance to next waypoint when close enough
        if (Vector3.Distance(_enemy.transform.position, target)  _sm;
    private float _losePlayerTimer;

    public EnemyChaseState(EnemyController enemy, StateMachine sm)
    {
        _enemy = enemy;
        _sm = sm;
    }

    public void Enter()
    {
        _enemy.Animator.Play("Run");
        _enemy.MoveSpeed = _enemy.ChaseSpeed;
        _losePlayerTimer = 0f;
    }

    public void Exit() { }

    public void Update()
    {
        float distToPlayer = Vector3.Distance(
            _enemy.transform.position, _enemy.Player.position);

        // Close enough to attack
        if (distToPlayer ();
            return;
        }

        // Lost sight of player
        if (!_enemy.CanSeePlayer())
        {
            _losePlayerTimer += Time.deltaTime;
            if (_losePlayerTimer > _enemy.LosePlayerDelay)
            {
                _sm.ChangeState();
                return;
            }
        }
        else
        {
            _losePlayerTimer = 0f;
        }

        // Low health: flee
        if (_enemy.HealthPercent ();
            return;
        }

        _enemy.MoveToward(_enemy.Player.position);
    }

    public void FixedUpdate() { }
}

Hierarchical FSM

A hierarchical state machine is a state that contains its own sub-state machine. This avoids state explosion when behaviors have nested phases.

Example: A "Combat" state that internally has Approach, Attack, and Retreat sub-states.

public class EnemyCombatState : IState
{
    private EnemyController _enemy;
    private StateMachine _parentSM;
    private StateMachine _subSM;

    public EnemyCombatState(EnemyController enemy, StateMachine parentSM)
    {
        _enemy = enemy;
        _parentSM = parentSM;

        _subSM = new StateMachine(enemy);
        _subSM.AddState(new CombatApproachSubState(enemy, _subSM));
        _subSM.AddState(new CombatAttackSubState(enemy, _subSM));
        _subSM.AddState(new CombatRetreatSubState(enemy, _subSM));
    }

    public void Enter()
    {
        _subSM.ChangeState();
    }

    public void Exit()
    {
        _subSM.CurrentState?.Exit();
    }

    public void Update()
    {
        // Parent-level transitions (flee, death) take priority
        if (_enemy.HealthPercent ();
            return;
        }

        // Delegate to sub-state
        _subSM.Update();
    }

    public void FixedUpdate()
    {
        _subSM.FixedUpdate();
    }
}

This keeps the parent state machine simple (Patrol, Combat, Flee, Dead) while Combat handles its own complexity internally.


Transition Conditions

For more complex state machines, define explicit transition rules rather than embedding transition logic in each state.

using System;
using System.Collections.Generic;

public class TransitionRule
{
    public Type FromState;
    public Type ToState;
    public Func Condition;
}

public class StateMachineWithTransitions
{
    private StateMachine _inner;
    private List _transitions = new();

    public StateMachineWithTransitions(T owner)
    {
        _inner = new StateMachine(owner);
    }

    public void AddState(IState state) => _inner.AddState(state);

    public void AddTransition(Func condition)
        where TFrom : IState where TTo : IState
    {
        _transitions.Add(new TransitionRule
        {
            FromState = typeof(TFrom),
            ToState = typeof(TTo),
            Condition = condition
        });
    }

    /// 
    /// Add a transition that can fire from ANY state.
    /// 
    public void AddAnyTransition(Func condition) where TTo : IState
    {
        _transitions.Add(new TransitionRule
        {
            FromState = null, // null means "any"
            ToState = typeof(TTo),
            Condition = condition
        });
    }

    public void Update()
    {
        // Check transitions before updating current state
        foreach (var rule in _transitions)
        {
            if (rule.FromState != null &&
                _inner.CurrentState?.GetType() != rule.FromState)
                continue;

            if (rule.Condition())
            {
                // Use reflection or a lookup to change state by Type
                // (Simplified here; in production, store state instances by Type)
                break;
            }
        }

        _inner.Update();
    }
}

This approach centralizes transition logic and makes it easier to visualize the state graph.


Event-Driven Transitions

Some transitions are triggered by external events (taking damage, picking up an item) rather than polled conditions.

// In the state machine owner:
public void OnDamageTaken(int amount)
{
    if (amount > _staggerThreshold)
    {
        _stateMachine.ChangeState();
    }
}

public void OnHealthDepleted()
{
    _stateMachine.ChangeState();
}

Prefer event-driven transitions for one-shot triggers (damage, death, interaction). Use polled transitions for continuous conditions (grounded check, distance to enemy).


ScriptableObject States

For designer-configurable behavior, define state parameters in ScriptableObjects. The SO holds data; a runtime state class reads it.

[CreateAssetMenu(menuName = "AI/Patrol Config")]
public class PatrolStateConfig : ScriptableObject
{
    public float moveSpeed = 2f;
    public float waitTimeAtWaypoint = 1.5f;
    public float detectionRange = 8f;
    public string animationName = "Walk";
}

public class ConfigurablePatrolState

…

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [XeldarAlz](https://github.com/XeldarAlz)
- **Source:** [XeldarAlz/everything-claude-unity](https://github.com/XeldarAlz/everything-claude-unity)
- **License:** MIT
- **Homepage:** https://github.com/XeldarAlz/everything-claude-unity#readme

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet — be the first.

Versions

  • v0.1.0 Imported from the upstream source.