States

A robust and flexible state machine implementation for Unity

In One Line

States that flow like water, solid as rock.

Overview

The States Package is designed to manage complex state transitions and behaviors in your game objects and systems. It provides a clean, builder-pattern-based API for creating and managing state machines with support for nested states and system integration.

Package Info

display name

AceLand States

package name

com.aceland.states

latest version

2.0.0

namespace

AceLand.States

dependencies

com.aceland.library: 2.0.4
com.aceland.playerloophack: 1.0.5
com.aceland.taskutils: 2.0.2

Key Features

  • Builder pattern for intuitive state machine construction

  • Support for any-state transitions

  • Sub state machine capabilities

  • System integration through Unity's PlayerLoop

  • Automatic resource management and cleanup

  • Asynchronous state machine retrieval

  • Custom state action injection

  • Debug logging support


Core Components

StateMachine

The main class for creating and managing states. Provides:

  • State transitions management

  • Update cycle handling

  • Resource cleanup

  • System integration capabilities

State

Represents individual states within the state machine:

  • Customizable Enter/Update/Exit behaviors

  • Sub State Machine support

  • Action injection capabilities

  • Unique state identification


How It Works

Core Architecture

The States Package operates on a hierarchical system with several key components working together:

  1. State Machine Core

    • Maintains a registry of all states

    • Manages transitions between states

    • Handles the update cycle

    • Controls state lifecycle (Enter → Update → Exit)

  2. State Transitions

    • Uses a double-buffered transition system to prevent race conditions

    • Evaluates transitions in priority order:

      1. Any-state transitions

      2. Specific state transitions

    • Prevents invalid transitions through state validation


Performance Considerations

  1. Update Optimization

    • Minimal garbage collection impact

    • Cached transition evaluations

    • Efficient state lookup through dictionary storage

  2. Memory Efficiency

    • States are shared when possible

    • Action delegates are cached

    • Transition conditions are optimized


Usage Example

This example works with Packages Task Utils, Player Loop Hack and Input for easy settings and handling.

Task Utils and Player Loop Hack is already installed with this package. To try this example, please install AceLand Input.

public enum StateMachineId
{
    Test1 = 900,
    Test2 = 901,
}

Prewarm Provider

State Machine is a core system design. Initialization should be started in very early state.

Prewarm Provider is a scriptable object. System will build the State Machine after assembly ready state. When the scene starts to load, the whole state machines system is already ready. This can also prevent from GetAsync process.

using AceLand.EventDriven.EventSignal;
using AceLand.States;
using AceLand.States.Profiles;
using UnityEngine;

namespace AceLand.Profiles
{
    public enum States
    {
        Init, Title, Story, Play, Pause, Quit,
    }
    
    [CreateAssetMenu(fileName = "State Machine Prewarm", menuName = "Profiles/State Machine Prewarm")]
    public class StateMachinePrewarm : StateMachinePrewarmProvider
    {
        public IStateMachine GetStateMachine() => _stateMachine;
    
        private IStateMachine _stateMachine;
        private Signal<States> _stateSignal;
        
        public override void PrewarmStateMachine()
        {
            _stateSignal = Signal.Builder()
                .WithId("StateMachineState")
                .WithValue(States.Init)
                .Build();

            var states = State.Builders()
                .WithNames<States>()
                .Build();
            var initState = states[0];
            var titleState = states[1];
            var storyState = states[2];
            var playState = states[3];
            var pauseState = states[4];
            var quitState = states[5];

            _stateMachine = StateMachine.Builder()
                .WithStates(states)
                .WithEntryTransition(initState)
                .WithTransition(initState, titleState, () => _stateSignal.Value is States.Title)
                .WithAnyTransition(storyState, () => _stateSignal.Value is States.Story)
                .WithAnyTransition(playState, () => _stateSignal.Value is States.Play)
                .WithAnyTransition(pauseState, () => _stateSignal.Value is States.Pause)
                .WithAnyTransition(quitState, () => _stateSignal.Value is States.Quit)
                .WithId("StateMachine")
                .Build();
        }

        public override void Dispose()
        {
            _stateMachine?.Dispose();
        }
    }
}

With Event Signal, State can be controlled by another class.

Please see Event Driven for details.


Exceptions

On GetAsync or GetReadonlyAsync function, exceptions will be returned on errors.

Exception
Description

StateMachineNotFoundException

When State Machine is not found with given ID

StateNotFoundException

When State with wrong given value type

StateMachine.GetAsync("ID")
    .Then(OnGetStateMachine)
    .Catch<StateMachineNotFoundException>(OnNotFound)
    .Catch(Debug.LogError);

Best Practices

  • Always dispose of state machines when no longer needed

  • Use meaningful state names for better debugging

  • Implement transition conditions as pure functions

  • Utilize sub state machines for complex state hierarchies

  • Consider using system integration for performance-critical scenarios


Last updated