A simple, extensible state machine framework for Unity.
Monostate provides a lightweight Machine component and a base GameState class for organizing game flow, gameplay modes, UI states, combat phases, menus, cutscenes, and other state-driven systems.
- Minimal state machine implementation for Unity
Machinecomponent that runs the active stateGameStatebase class with lifecycle hooks- Per-frame
Update(float deltaTime)forwarding - Physics-step
FixedUpdate(float fixedDeltaTime)forwarding - Extensible design for project-specific machines and state types
- No external dependencies
Install through Unity Package Manager using the Git URL:
https://github.com/ZellyDev-Games/monostate.git
Or add it directly to your Unity project's Packages/manifest.json:
{
"dependencies": {
"com.github.zellydev-games.monostate": "https://github.com/ZellyDev-Games/monostate.git"
}
}{
"name": "com.github.zellydev-games.monostate",
"version": "1.0.0",
"displayName": "Monostate",
"unity": "6000.3"
}Monostate has two primary types:
Machine is a Unity MonoBehaviour that owns the current state.
It exposes:
public GameState CurrentState { get; private set; }
public void ChangeState(GameState newState)Each frame, the machine forwards Unity updates to the current state:
CurrentState?.Update(Time.deltaTime);
CurrentState?.FixedUpdate(Time.fixedDeltaTime);GameState is the base class for individual states.
Override any of these lifecycle methods:
public virtual void OnEnter(Machine machine)
public virtual void OnExit()
public virtual void Update(float deltaTime)
public virtual void FixedUpdate(float fixedDeltaTime)OnEnter receives the owning Machine and stores it for later use.
Create a state by deriving from GameState:
using UnityEngine;
using ZellyDevGames.Monostate;
public class TitleState : GameState
{
public override void OnEnter(Machine machine)
{
base.OnEnter(machine);
Debug.Log("Entered title state.");
}
public override void Update(float deltaTime)
{
if (Input.GetKeyDown(KeyCode.Space))
{
Machine.ChangeState(new GameplayState());
}
}
public override void OnExit()
{
Debug.Log("Leaving title state.");
}
}Create another state:
using UnityEngine;
using ZellyDevGames.Monostate;
public class GameplayState : GameState
{
public override void OnEnter(Machine machine)
{
base.OnEnter(machine);
Debug.Log("Gameplay started.");
}
public override void Update(float deltaTime)
{
// Gameplay update logic
}
public override void FixedUpdate(float fixedDeltaTime)
{
// Physics-timed gameplay logic
}
}Attach a Machine component to a GameObject, then change to your starting state:
using UnityEngine;
using ZellyDevGames.Monostate;
public class GameBootstrap : MonoBehaviour
{
[SerializeField] private Machine machine;
private void Start()
{
machine.ChangeState(new TitleState());
}
}States are responsible for their own transition logic.
The base Machine does not validate transition graphs. This keeps the framework simple and makes it easy to define your own rules inside individual states or in a derived machine class.
public override void Update(float deltaTime)
{
if (ShouldStartBattle())
{
Machine.ChangeState(new BattleState());
}
}When ChangeState is called, the machine:
- calls
OnExit()on the current state, if one exists - assigns the new state to
CurrentState - calls
OnEnter(this)on the new state
The base Machine intentionally contains no project-specific data stores.
For larger projects, create your own machine type with references to the systems your states need:
using UnityEngine;
using ZellyDevGames.Monostate;
public class GameMachine : Machine
{
public PlayerController Player;
public Camera MainCamera;
public Transform SpawnPoint;
}Then create a specialized state base class so states do not need to cast repeatedly:
using ZellyDevGames.Monostate;
public abstract class GameStateBase : GameState
{
protected GameMachine GameMachine { get; private set; }
public override void OnEnter(Machine machine)
{
base.OnEnter(machine);
GameMachine = (GameMachine)machine;
}
}Now project states can access the typed machine directly:
public class ExploreState : GameStateBase
{
public override void OnEnter(Machine machine)
{
base.OnEnter(machine);
GameMachine.Player.enabled = true;
}
}- Always call
base.OnEnter(machine)when overridingOnEnter. - The base
OnEnterstores the owning machine reference. - Calling
base.OnExit(),base.Update(...), orbase.FixedUpdate(...)is optional because the base implementations are empty. - The default
Machineperforms no transition validation. - State graph rules should live in your states, a custom machine, or another project-specific layer.
GameStateinstances are plain C# objects, notMonoBehaviourcomponents.Machineis the Unity component that connects the state system to Unity'sUpdateandFixedUpdateloops.
Monostate works well for:
- Game flow
- Main menu flow
- Combat phases
- Turn-based battle states
- Player control modes
- UI screens
- Cutscene sequencing
- Tool/editor workflows
- Puzzle or minigame modes
BootState
-> TitleState
-> MainMenuState
-> GameplayState
-> PauseState
-> BattleState
-> GameOverState
public GameState CurrentState { get; private set; }The currently active state.
public void ChangeState(GameState newState)Exits the current state, assigns the new state, and enters it.
public virtual void OnEnter(Machine machine)Called when the machine activates this state.
public virtual void OnExit()Called before the machine switches away from this state.
public virtual void Update(float deltaTime)Called from the owning machine's Unity Update.
public virtual void FixedUpdate(float fixedDeltaTime)Called from the owning machine's Unity FixedUpdate.
MIT