Soon ⚡
[!NOTE] State instances are not cached and are created each time a new state is transitioned using
IStateFactory
[!NOTE] Zenject is used only for resolving the instance of IStateMachine and calling IStateMachine ticks
- Get
IStateMachine
implementation instance viaZenject
and move to the stateStateA
public class ExampleStateMachine : MonoBehaviour
{
private IStateMachine m_stateMachine;
[Inject]
private void Construct(IStateMachine stateMachine)
{
m_stateMachine = stateMachine;
}
private void Start()
{
m_stateMachine.Enter<StateA>();
}
}
StateA.cs
public class StateA : State
{
public override async UniTask Enter()
{
Debug.Log("[StateA] Enter");
//Example delay await
await UniTask.Delay(TimeSpan.FromSeconds(1));
//Example await UniTasks<T>
int value = await SomeOperation();
//Direct access to State Machine
StateMachine.Enter<StateB, StateBModel>(new StateBModel(value));
}
private UniTask<int> SomeOperation()
{
return UniTask.FromResult(Random.Range(0, 100));
}
public override UniTask Exit()
{
Debug.Log("[StateA] Exit");
return UniTask.CompletedTask;
}
}
StateBModel.cs
public class StateBModel
{
public int Value { get; private set; }
public StateBModel(int value)
{
Value = value;
}
public void Increase()
{
Value += 1;
}
}
StateB.cs
public class StateB : ModelState<StateBModel>
{
public override async UniTask Enter()
{
//Get access to state model via Model property
Debug.Log($"[StateB] Enter with Model value: {Model.Value}");
await UniTask.Delay(TimeSpan.FromSeconds(1));
Debug.Log($"[StateB] Current Model value after 1s: {Model.Value}");
}
public override void Tick(float delta)
{
//Modify model every Tick like MonoBehaviour.Update
Model.Increase();
}
}
public interface IStateMachine
{
ExitableState CurrentState { get; }
UniTaskVoid Enter<TState>() where TState : State;
UniTaskVoid Enter<TState, TModel>(TModel model) where TState : ModelState<TModel>;
}
public interface IStateFactory
{
object Create(Type type);
}
public interface IStateMachineEvents
{
//Where Type = new state
event Action<Type> StateChanged;
//There first Type = old state, and second Type = new state
event Action<Type, Type> StateChangedFrom;
}
public interface IStateMachineTicks
{
void Tick(float delta);
void FixedTick(float delta);
}
public abstract class ExitableState : IState
{
//Called when exiting a state
public virtual UniTask Exit();
//Calling manually
public virtual void Tick(float delta);
//Calling manually Fixed variant
public virtual void FixedTick(float delta);
//Reference to StateMachine
public IStateMachine StateMachine{ get; set; }
//CancellationTokenSources are managed by StateMachine
internal CancellationTokenSource StateLifetimeTokenSource { set; get; }
//.Cancel() called when exiting a state
protected CancellationToken LifetimeToken { get; }
}
public abstract class State : ExitableState
{
//Enter the state
public abstract UniTask Enter();
}
public abstract class ModelState<TModel> : ExitableState
{
//Model required by State, initialized by StateMachine before state.Enter()
public TModel Model { internal set; get; }
//Enter the state
public abstract UniTask Enter();
}
IStateFactory
example implementation
public class ZenjectStateFactory : IStateFactory
{
private readonly IInstantiator m_instantiator;
public ZenjectStateFactory(IInstantiator instantiator)
{
m_instantiator = instantiator;
}
public object Create(Type type)
{
return m_instantiator.Instantiate(type);
}
}
IStateMachineTicks
.Tick()
and.FixedTick()
calling by custom class
public class ZenjectStateMachineTicks : ITickable, IFixedTickable
{
private readonly IStateMachineTicks m_ticks;
public ZenjectStateMachineTicks(IStateMachineTicks ticks)
{
m_ticks = ticks;
}
public void Tick()
{
m_ticks.Tick(Time.deltaTime);
}
public void FixedTick()
{
m_ticks.FixedTick(Time.fixedDeltaTime);
}
}
- Binding to
Container
Container.BindInterfacesTo<AsyncStateMachine>().AsSingle();
Container.BindInterfacesTo<ZenjectStateFactory>().AsSingle();
Container.BindInterfacesTo<ZenjectStateMachineTicks>().AsSingle();
-[ ] Cleanup repeated parts inside StateMachine.cs
[DRY]