Install
$ agentstack add skill-xeldaralz-everything-claude-unity-state-machine ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
Security review
✓ PassedNo 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.
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.
Write a review
Versions
- v0.1.0 Imported from the upstream source.