English | Русский
Async states with awaited transitions.
A lightweight async state machine for Unity and pure C#. States are plain classes — by default created with a parameterless constructor. Entering a state awaits the current state's OnExit, then the next state's OnEnter. Parameterless and payload-carrying states are both supported, and a transition requested mid-transition is queued. Need dependencies inside your states? Plug in an IStateFactory backed by your DI container of choice.
- .unitypackage — Releases
- UPM —
Window → Package Manager→+→Add package from git URL:https://github.com/SST-Systems/StateMachine.gitAppend#tagto pin a version. - Manual — clone or download, copy to
Assets/.
Unity 2021.3+
The core is dependency-free — no DI framework required.
IState — base state. Task OnExit() (default no-op).
IDefaultState : IState — a parameterless state. Task OnEnter().
IPayloadState<TPayload> : IState — a state that receives a payload on entry. Task OnEnter(TPayload payload).
All methods have default (no-op) implementations — implement only the ones you need.
Abstract base you derive your machine from.
| Member | Purpose |
|---|---|
RegisterState<T>() |
Pre-instantiate and cache a state (through the state factory). Optional. |
Enter<TState>() |
Transition to a parameterless IDefaultState. |
Enter<TState, TPayload>(payload) |
Transition to an IPayloadState<TPayload>. |
Enter(Type) / Enter<TPayload>(Type, payload) |
Same transitions, addressed by Type. |
CurrentState |
The currently active state. |
StateChanged |
Raised after each successful transition. |
States are produced by an IStateFactory:
ActivatorStateFactory(default) — creates states viaActivator.CreateInstance, so each state needs a parameterless constructor. Used when you construct the machine withnew GameStateMachine().- Custom — implement
IStateFactoryto build states through your DI container (so they can receive injected dependencies) and pass it to the base constructor:public GameStateMachine(IStateFactory factory) : base(factory) { }. A ready Zenject factory ships in the sample.
// 1. Define states — plain classes with a parameterless constructor
public class MenuState : IDefaultState
{
public Task OnEnter() => ShowMenu();
public Task OnExit() => HideMenu();
}
public class LevelState : IPayloadState<int>
{
public Task OnEnter(int levelId) => LevelLoader.Load(levelId);
}
// 2. Derive a machine and register the states
public class GameStateMachine : StateMachine
{
public GameStateMachine() // uses the default Activator factory
{
RegisterState<MenuState>();
RegisterState<LevelState>();
}
}
// 3. Create and drive it — transitions are awaited
var machine = new GameStateMachine();
await machine.Enter<MenuState>();
await machine.Enter<LevelState, int>(3);Need injected dependencies inside your states? Pass an IStateFactory to the base constructor — see the Zenject for StateMachine sample.
Zenject for StateMachine — a ZenjectStateFactory (backed by IInstantiator, so states can [Inject] dependencies), an installer, and a sample machine. Import via Window → Package Manager → select StateMachine → Samples tab.
- By default states are created with
ActivatorStateFactoryand therefore need a parameterless constructor; pass a customIStateFactoryto inject dependencies into states. RegisterState<T>()is optional pre-registration; an unregistered state is created lazily on its firstEnter.- Re-entering the current state is a no-op. Calling
Enterduring an in-progress transition queues the target and runs it once the current transition completes (only the most recent request is kept).
Distributed under the MIT License. Free for personal and commercial use.
Author — Egor Shesterikov.
