- Notifications
You must be signed in to change notification settings - Fork5
UniState is a modern, high-performance, scalable state machine package for Unity.
License
bazyleu/UniState
Folders and files
Name | Name | Last commit message | Last commit date | |
---|---|---|---|---|
Repository files navigation
UniState is a modern, high-performance, scalable state machine package for Unity. It can serve as a core architecturalpattern or be used to address specific tasks.
- Performance: optimized for minimal runtime allocations, seeperformance section for details.
- Modularity: designed to definestates,substates, andstate machines in anisolated way. States can be easily replaced or removed without hidden effects even in big projects.
- Scalability: memory allocations happen only on demand,performance does not degrade with the number ofstates and state machines.
- Asynchronous: modern asynchronous API with async-await andUniTask
- Reliability: allows you to defineglobal error handling at the state machine level,and guarantees that all resources will bedisposed.
- Simplicity: if you usestate base you have to implement only one method for fast start.
- Flexibility: everything in framework core is an abstraction. Can be replaced with your own implementation,seestate creating andcreating a state machine sections for details.
- Testability: UniState is designed to be testable. All abstractions use interfaces that can be easily mocked withNSubstitutes or any other framework. States can be run separately for testingpurposes.
- DI friendly: hasintegration with most popular DI containers
- Getting Started
- Installation
- Performance
- Framework Philosophy
- API Details and Usage
- Integrations
- License
Step 1:Install UniState by adding the following URL to Unity Package Manager:https://github.com/bazyleu/UniState.git?path=Assets/UniState#1.1.0
.
Details on installation are availablehere.
Step 2: Create a state by defining a class that inherits fromStateBase
orStateBase<T>
. Example transition logic:
publicclassMainMenuState:StateBase{publicoverrideasyncUniTask<StateTransitionInfo>Execute(CancellationTokentoken){// Add your state logic herereturnTransition.GoTo<GameplayState>();}}publicclassGameplayState:StateBase{publicoverrideasyncUniTask<StateTransitionInfo>Execute(CancellationTokentoken){// Add your state logic herereturnTransition.GoBack();}}
Detailed information about creating states isavailablehere.
Step 3: Configure Dependency Injection (DI) by registering the state machine and states in the DI container.
builder.RegisterStateMachine<StateMachine>();builder.RegisterState<MainMenuState>();builder.RegisterState<GameplayState>();
Additional information on DI configuration is availablehere.
Step 4: Create and run the state machine by specifying the initial state.
publicclassGame{privateIObjectResolver_objectResolver;privateCancellationTokenSource_ctx;publicasyncvoidRun(){varstateMachine=StateMachineHelper.CreateStateMachine<StateMachine>(_objectResolver.ToTypeResolver());awaitstateMachine.Execute<MainMenuState>(_ctx.Token);}}
More details on running the state machine can be foundhere.
That is it! Your first project with UniState is set up.
- Requires Unity version 2022.3 or higher.
- Requires UniTask package installed. Guide regarding UniTask installation can be foundonCysharp/UniTask README.
You can addhttps://github.com/bazyleu/UniState.git?path=Assets/UniState
to Package Manager.
It is a good practice to specify target version, UniState uses the*.*.*
release tag so you can specify a versionlike#1.1.0
. For examplehttps://github.com/bazyleu/UniState.git?path=Assets/UniState#1.1.0
.You can find latest version numberhere.
You can add"com.bazyleu.unistate": "https://github.com/bazyleu/UniState.git?path=Assets/UniState"
(or with versiontaghttps://github.com/bazyleu/UniState.git?path=Assets/UniState#1.1.0
) toPackages/manifest.json
.
UniState is the fastest and most efficient asynchronous state machine available for Unity. When compared to statemachine implementations based on MonoBehaviour, UniState delivers a performance boost of over 5000x in execution speedand up to a 10x reduction in allocations.
For typical scenarios involving small to medium state chains - the most common use case - UniState can reduce memoryallocations by a factor ranging between 2x and 10x. In cases where state chains exceed 200 states, the benefits inmemory allocation become less pronounced but execution speed remain consistent with 5000x+ boost.
Measurements for Windows PC (with IL2CPP scripting backend):
Benchmark Mono 10 states: 516.4 ms, 120.83 KBBenchmark Mono 50 states: 2520.9 ms, 150.44 KBBenchmark Mono 200 states: 10033.6 ms, 283.83 KBBenchmark UniState 10 states: 0.1 ms, 13.11 KBBenchmark UniState 50 states: 0.2 ms, 68.81 KBBenchmark UniState 200 states: 0.7 ms, 273.20 KBBenchmark UniState with history 10 states: 0.1 ms, 14.34 KBBenchmark UniState with history 50 states: 0.2 ms, 69.58 KBBenchmark UniState with history 200 states: 0.7 ms, 276.95 KB
All dependencies for states, commands, and other entities should be passed through the constructor.UniState supports automatic integration with the most popular DI frameworks for Unity.Refer to theintegration documentation for more details.Dependencies must be registered in your DI framework, and they will automatically be resolved whencreatingstate,state machine.
A state is an abstraction that represents a specific condition or phase of the game, often corresponding to a "screen" that the user interactswith. For example, the main menu is a state, a settings popup is another state, and gameplay itself may take place in aseparateGameplayState
. When the user opens a shop popup, they may transition into aShopState
. However, states arenot always tied to visual elements. Some states, likeGameLoadingState
, may handle background processes such asloading resources.
State class contains all logic related to that state including loading and unloading resources. UniState does not restrict the use of otherframeworks or patterns, meaning you can freely use whatever suits your needs. You could, for example, run controllersand follow an MVC approach, follow MVVM approach, or even execute ECS code within a state.
The key concept of the framework is that once a state is exited, all resources it allocated should be released. Fordetails on how to do this seeDisposables.
It is not recommended to use Unity GameObjects directly inside states, as it reduces testability and increases codecoupling. A better approach is to load GameObjects through an abstraction and use them as an interface (essentially as aView in UniState). Add a handler for unloading to the Disposables of the state that loaded it. All approaches / patternswhich were mentioned above support this, and you can choose any based on your preferences, as this functionality isoutside the scope of UniState.
//Popup prefab (Monobehaviour, view)publicclassSimplePopupView:ISimplePopupView,Monobehaviour{//...}// Simple popup state examplepublicclassSimplePopupState:StateBase{privateISimplePopupView_view;publicoverrideasyncUniTaskInitialize(CancellationTokentoken){_view=LoadPopupView(token);Disposables.Add(UnloadShopView);}publicoverrideasyncUniTask<StateTransitionInfo>Execute(CancellationTokentoken){await_view.Show(token);await_view.WaitForClick(token);returnTransition.GoBack();}publicoverrideasyncUniTaskExit(CancellationTokentoken){await_view.Hide(token);}// The implementation of this method depends on other frameworks/patterns used in the project.privateISimplePopupViewLoadShopView(CancellationTokentoken){// Loading logic}privatevoidUnloadShopView(){// Unloading logic}}
If the popup is complex with multiple features, it could be represented as its own state machine.In cases where you have a complex popup with its own state machine, it’s important to allocate resources specific to the popup before launching the separatestate machine, ensuring they are properly cleaned up after the state machine exits.
// This state loads resources, adds them to Disposables, and runs the internal state machine for ShopPopup.// When the StateMachine completes its execution, RootShopPopupState finishes and releases its resources.publicclassRootShopPopupState:StateBase{publicoverrideasyncUniTaskInitialize(CancellationTokentoken){// Load ShopView (a Unity GameObject) and create an IDisposable handler that// will unload the GameObject after Disposing.// After that, the GameObject will be available as IShopView in internal states.vardisposable=LoadShopView();Disposables.Add(disposable);}publicoverrideasyncUniTask<StateTransitionInfo>Execute(CancellationTokentoken){varstateMachine=StateMachineFactory.Create<StateMachine>();// Run the internal state machine for ShopPopup.// In all states inside this state machine, all resources allocated in this state will be available.awaitstateMachine.Execute<ShopPopupIdleState>(cts.Token);returnTransition.GoBack();}// The implementation of this method depends on other frameworks/patterns used in the project.privateIDisposableLoadShopView(){// Loading logic}}publicclassShopPopupIdleState:StateBase{// IShopView is a Unity GameObject loaded in RootShopPopupState (outside the current state machine).// IShopView will be available as long as RootShopPopupState is running,// meaning throughout the entire internal state machine's operation.privateIShopView_view;publicShopPopupIdleState(IShopViewview){_view=view;}publicoverrideasyncUniTask<StateTransitionInfo>Execute(CancellationTokentoken){varaction=await_view.Show(token);// Transition logic with 'action'}}
A state is a fundamental unit of logic in an application, often representing different screens or states, such as anidle scene, main menu, popup, or a specific state of a popup.
To create your custom state, you can inherit fromStateBase
orStateBase<T>
. UseStateBase<T>
if you need to passparameters to the state.
For highly customized states, you can manually implement theIState<TPayload>
interface. However, in mostcases,StateBase
will suffice.
// Simple State InheritancepublicclassFooState:StateBase{publicoverrideasyncUniTask<StateTransitionInfo>Execute(CancellationTokentoken){// State logic here}}// State with ParameterspublicclassFooStateWithPayload:StateBase<FooPayload>{publicoverrideasyncUniTask<StateTransitionInfo>Execute(CancellationTokentoken){// Get payloadFooPayloadpayload=Payload;// State logic with payload here}}//Custom State ImplementationpublicclassCustomFooState:IState<MyParams>{publicasyncUniTaskInitialize(CancellationTokentoken){// Initialization logic}publicasyncUniTask<StateTransitionInfo>Execute(MyParamspayload,CancellationTokentoken){// Execution logic with payload}publicasyncUniTaskExit(CancellationTokentoken){// Exit logic}publicvoidDispose(){// Cleanup logic}}
The lifecycle of a state consists of four stages, represented by the following methods:
Initialize
- Used for initializing resources, such as loading prefabs, subscribing to events, etc.
Execute
- The only method that must be overridden in
StateBase
. It contains the main logic of the state and remains activeuntil it returns a result with a transition to another state. For example, a state displaying a popup might waitfor button presses and handle the result here. See theState Transitions section for moredetails.
- The only method that must be overridden in
Exit
- Completes the state's work, such as unsubscribing from buttons and closing the popup (e.g., playing a closinganimation).
Dispose
- Cleans up resources. If you inherit from
StateBase
, this method does not need implementation.
- Cleans up resources. If you inherit from
TheExecute
method of a state should return aStateTransitionInfo
object, which dictates the next actions of thestate machine. To simplify its generation, you can use theTransition
property inStateBase
. The possible transitionoptions are:
GoTo
- Used to transition to another state. If the state contains a payload, it should be passed to
GoTo
.
- Used to transition to another state. If the state contains a payload, it should be passed to
GoBack
- Returns to the previous state. If there is no previous state (the current state is the first), it will exit thestate machine. See theState Machine section for more details.
GoToExit
- Exits the current state machine. See theState Machine section for more details.
publicclassExampleState:StateBase{publicoverrideasyncUniTask<StateTransitionInfo>Execute(CancellationTokentoken){vartransition=awaitDoSomeAsyncLogic(token);switch(transition){caseTransitionExample.GoTo:returnTransition.GoTo<FooState>();caseTransitionExample.GoToWithPayload:varpayload=42;returnTransition.GoTo<BarState,int>(payload);caseTransitionExample.GoToAbstract:returnTransition.GoTo<IFooState>();caseTransitionExample.GoBack:returnTransition.GoBack();caseTransitionExample.GoToExit:returnTransition.GoToExit();default:returnTransition.GoToExit();}}privateUniTask<TransitionExample>DoSomeAsyncLogic(CancellationTokentoken){// Some logic herereturnUniTask.FromResult(TransitionExample.GoTo);}}
Disposables are a part ofStateBase
that allow users to tieIDisposable
references and delegates to state'slifetime, guaranteeing disposal and delegate execution on state'sDispose
, without overriding the method
publicclassLoadingState:StateBase<ILoadingScreenView>{privateCancellationTokenSource_loadingCts;publicoverrideasyncUniTask<StateTransitionInfo>Execute(CancellationTokentoken){// State's disposable references_loadingCts=CancellationTokenSource.CreateLinkedTokenSource(token);Disposables.Add(_loadingCts);// Handling of subscriptions with locality of behaviourPayload.CancelClicked+=OnCancelLoadingClicked;Disposables.Add(()=>Payload.CancelClicked-=OnCancelLoadingClicked);try{awaitPayload.PretendToWork(_loadingCts.Token);}catch(OperationCancelledException)when(!token.IsCancellationRequested){returnTransition.GoBack();}returnTransition.GoTo<NextState>();}privatevoidOnCancelLoadingClicked(){_loadingCts.Cancel();}}
It is possible to customize the behavior of a specific state using theStateBehaviour
attribute.
This attribute has the following parameters:
ProhibitReturnToState (default value: false): When enabled, this state cannot be returned tovia
Transition.GoBack()
. The state with this attribute will be skipped, and control will return to the state beforeit. This behavior can be useful for states that represent 'loading', there is no point of returning to loading.InitializeOnStateTransition (default value: false): When enabled, the initialization of the state will beginbefore exiting the previous state. Technically, this means
Initialize()
of the state will be called beforeExit()
of the previous state. This behavior can be useful for seamless transitions in complex animations, where the staterepresents only part of the animation.
[StateBehaviour(ProhibitReturnToState=true)]publicclassFooState:StateBase{//...}[StateBehaviour(InitializeOnStateTransition=true)]publicclassBarState:StateBase{//...}[StateBehaviour(InitializeOnStateTransition=true,ProhibitReturnToState=true)]publicclassBazState:StateBase{//...}
The state machine is the entry point into the framework, responsible for running states.
To create the initial state machine, use thehelperStateMachineHelper.CreateStateMachine<TSateMachine>(ITypeResolver typeResolver)
.
TSateMachine: Any class implementing
IStateMachine
. You can use the standardStateMachine
or create customones by inheriting fromStateMachine
or implementingIStateMachine
.ITypeResolver: Used to create the state machine. It acts as a factory for creating states and other statemachines. You can implement it yourself or use the provided implementation from DI frameworks like VContainer orZenject via the
.ToTypeResolver()
extension. SeeIntegrations for supported frameworksorCustom type resolvers for cases if you DI framework is not supported out of the box oryou do not have DI framework.
After creating the state machine, you can run it with the specified state:
awaitstateMachine.Execute<FooState>(cts.Token);varpayload=newBarPayload();awaitstateMachine.Execute<BarState>(payload,cts.Token);
Any state can create and run a state machine within itself using theStateMachineFactory
property. This is therecommended method for creating a state machine inside a state.
ITypeResolver_newContext;publicUniTask<StateTransitionInfo>Execute(CancellationTokentoken){varstateMachine=StateMachineFactory.Create<StateMachine>();awaitstateMachine.Execute<FooState>(cts.Token);varstateMachineWithNewContext=StateMachineFactory.Create<StateMachine>(_newContext);awaitstateMachineWithNewContext.Execute<FooState>(cts.Token); ...}
The state machine maintains a history of transitions between states, allowing for the use ofTransition.GoBack()
. Thesize of this history can be customized through theStateMachineLongHistory.MaxHistorySize
property (default value is15). If more transitions occur than the history size, only the most recent transitions will be retained, with nooverhead or errors resulting from the limit.
SettingMaxHistorySize = 0
disables the history, causingTransition.GoBack()
to exit the state machine directly.
publicclassStateMachineWithDisabledHistory:StateMachine{protectedoverrideintMaxHistorySize=>0;}
In UniState, state machine error handling can be customized to control how exceptions within states are processed. Theprimary mechanism for this is theHandleError()
method, which you can override in your custom state machine. Thismethod is called whenever an exception occurs, allowing you to define specific logic to handle errors.
Exceptions are caught and processed internally without propagating further, except forOperationCanceledException
,which will stop the state machine.StateMachineErrorData
provides metadata related to exceptions, andStateMachineErrorData.State
may benull
ifStateMachineErrorType
is set toStateMachineFail
.
publicclassBarStateMachine:StateMachine{protectedoverridevoidHandleError(StateMachineErrorDataerrorData){// Custom logic here}}
To halt state machine execution after an exception, include athrow
statement inHandleError()
:In the example provided, the state machine will terminate after encountering a second exception within the same state in a row.
publicclassFooStateMachine:StateMachine{privateType_lastErrorState;protectedoverridevoidHandleError(StateMachineErrorDataerrorData){varstateType=errorData.State?.GetType();if(stateType!=null&&_lastErrorState==stateType){// Stop state mahine execution and throw an exception outthrownewException($"Second exception in same state.",errorData.Exception);}_lastErrorState=stateType;}}
If an exception is encountered in a state’sInitialize()
orExit()
methods, the state machine will continue working.However, if an exception occurs in the state’sExecute()
method, the state machine defaults to aGoBack()
operation, as thoughTransition.GoBack()
were returned. You can override this behavior by customizingBuildRecoveryTransition
, which receives anIStateTransitionFactory
to specify any desired transition for errorrecovery.
When an exception occurs inExecute()
,HandleError
will be invoked first, followed byBuildRecoveryTransition
.
publicclassBarStateMachine:StateMachine{// If exception occurs in the state in the Execute() method, the state machine will go to the ErrorPopupState.protectedoverrideStateTransitionInfoBuildRecoveryTransition(IStateTransitionFactorytransitionFactory)=>transitionFactory.CreateStateTransition<ErrorPopupState>();}
When creating a state machine, you can use your custom interface. Interface should be inherit fromIStateMachine
. Thisallows to implement additional, customized behavior.
publicinterfaceIExtendedStateMachine:IStateMachine{publicvoidRunCustomLogic();}
Once your custom interface is implemented, you can utilize a special version of the API that returns your interface.This can be useful for adding custom logic to the state machine.
// Option 1: Creating ExtendedStateMachine as entry pointvarstateMachine=StateMachineHelper.CreateStateMachine<ExtendedStateMachine,IExtendedStateMachine>(typeResolver);// Option 2: Creating ExtendedStateMachine inside statesvarstateMachine=StateMachineFactory.Create<ExtendedStateMachine,IExtendedStateMachine>();// Custom state machine has extended api that is defined by IExtendedStateMachine interfacestateMachine.RunCustomLogic();// Custom state machine can run states like default state machineawaitstateMachine.Execute<FooState>(cancellationToken);
UniState natively supports sub-containers and sub-contexts available in modern DI frameworks.
When creating a state machine inside a state, you can use two method overloads:
StateMachineFactory.Create<TSateMachine>()
StateMachineFactory.Create<TSateMachine>(ITypeResolver typeResolver)
If the version withoutITypeResolver
is used, the context is inherited from the parent state machine.IfITypeResolver
is passed, it will have a new context.
For smaller projects, it's recommended to use the simplified version without creating a new context:
StateMachineFactory.Create<TSateMachine>();
For larger projects using sub-containers/sub-contexts in your DI framework to manage resources more efficiently, you canpass them intoCreate
to force the state machine to use them for creating states and dependencies. Thus, UniStatesupports this natively without additional actions required from you.
While UniState providesITypeResolver
implementations for modern DI frameworks out of the box, you can create custom implementations, tailored to your needs
An example ofITypeResolver
with automatic state bindings for Zenject/Extenject:
publicclassZenjectAutoBindTypeResolver:ITypeResolver{ ...publicobjectResolve(Typetype){if(!type.IsAbstract&&!type.IsInterface&&!_container.HasBinding(type)){_container.BindState(type);}return_container.Resolve(type);}}
If you do not have DI framework you have to implement ITypeResolver by your own by manually creating requested states andstate machines.
Composite State is essential for complex areas of an application likely to be worked on by multiple peoplesimultaneously. They consist of various independent sub states, each with its own logic.
To create a composite state, inherit fromCompositeStateBase
(or implement theICompositeState
interface for moredetailed control). You can also use the ready-made implementationDefaultCompositeState
(seetheDefaultCompositeState section). No additional actions are needed.
SubStates are states tied to a composite state, created and run simultaneously with it. To create a SubState, inheritfromSubStateBase
or implement theISubState
interface for greater customization. When creating a sub state, specifythe parent composite state as a generic parameter, e.g.,FooSubState : SubStateBase<BarCompositeState>
. In all otheraspects, it functions like a regular state.
A ready-to-use implementation for a composite state that propagatesInitialize
,Execute
, andExit
methods to allSubStates within it. The result of theExecute
method will be the first completedExecute
method among all substates.
If you useDefaultCompositeState
and it is executed without any SubStates, itsExecute
method will throwanInvalidOperationException
.
To useDefaultCompositeState
, simply inherit your composite state from it. Here's an example:
internalclassFooCompositeState:DefaultCompositeState{}internalclassBazSubState:SubStateBase<DefaultCompositeState>{}internalclassBarSubState:SubStateBase<DefaultCompositeState>{}
UniState supports integrations with the most popular DI containers. If these frameworks are installed via UPM,everything will work out of the box, and no additional actions are required.
GitHub:VContainer
If the VContainer is installed via UPM, you can skip this step and proceed to theVContainer Usagesection.If the package is not installed via UPM, you need to manually add theUNISTATE_VCONTAINER_SUPPORT
define symbol inScripting Define Symbols (Player Settings -> Player -> Scripting Define Symbols).
To use it, convertVContainer.IObjectResolver
toUniState.ITypeResolver
by calling the extensionToTypeResolver()
and pass it to the state machine.
// Object resolver with main or child scope from VContainerVContainer.IObjectResolver_objectResolver;// Convert VContainer.IObjectResolver to ITypeResolver.TypeResolvervartypeResolver=_objectResolver.ToTypeResolver();// Create state machine with VContainer supportvarstateMachine=StateMachineHelper.CreateStateMachine<StateMachine>(typeResolver);
All state machines, states and their dependencies should be registered in DI container.For convenient registering of states and state machines, special extension methods are available. The main onesareRegisterStateMachine
andRegisterState
, which register both the classes themselves and all interfaces implemented bythese classes.
However, if you need to implement a transition into a state or launch a state machine via a base/abstract class, youshould useRegisterAbstractStateMachine
andRegisterAbstractState
.
Here's an example code:
privatevoidRegisterStates(IContainerBuilderbuilder){// Use this registration creating state machine via class or interface.// For example: StateMachineHelper.CreateStateMachine<BarStateMachine>(...)// For example: StateMachineHelper.CreateStateMachine<IBarStateMachine>(...)builder.RegisterStateMachine<BarStateMachine>();// Use this registration creating state machine via base/abstract class.// For example: StateMachineHelper.CreateStateMachine<FooStateMachineBase>(...)builder.RegisterAbstractStateMachine<FooStateMachineBase,FooStateMachine>();// Use this registration for transitions to class or interface.// For example: Transition.GoTo<BarState>() or Transition.GoTo<IBarState>()builder.RegisterState<BarState>();// Use this registration for transitions to base/abstract class.// For example: Transition.GoTo<FooStateBase>()builder.RegisterAbstractState<FooStateBase,FooState>();// Singleton version of states, not recommended in general use, but can be handy in some casesbuilder.RegisterStateMachine<BarStateMachine>(Lifetime.Singleton);builder.RegisterAbstractStateMachine<FooStateMachineBase,FooStateMachine>(Lifetime.Singleton);builder.RegisterState<BarState>(Lifetime.Singleton);builder.RegisterAbstractState<FooStateBase,FooState>(Lifetime.Singleton);}
You can always skip the extensions and register directly if you need custom behavior.
If the Zenject / Extenject is installed via UPM, you can skip this step and proceed totheZenject Usage section.If the package is not installed via UPM, you need to manually add theUNISTATE_ZENJECT_SUPPORT
define symbol inScripting Define Symbols (Player Settings -> Player -> Scripting Define Symbols).
To use it, convertZenject.DiContainer
toUniState.ITypeResolver
by calling the extensionToTypeResolver()
andpass it to the state machine.
// Zenject container / sub containerZenject.DiContainercontainer;// Convert Zenject.DiContainer to ITypeResolver.TypeResolvervartypeResolver=container.ToTypeResolver();// Create state machine with Zenject supportvarstateMachine=StateMachineHelper.CreateStateMachine<StateMachine>(typeResolver);
All state machines, states and their dependencies should be registered in DI container.For convenient registering of states and state machines, special extension methods are available. The main onesareBindStateMachine
andBindState
, which bind both the classes themselves and all interfaces implemented bythese classes.
However, if you need to implement a transition into a state or launch a state machine via a base/abstract class, youshould useBindAbstractStateMachine
andBindAbstractState
.
Here's an example code:
privatevoidBindStates(DiContainercontainer){// Use this registration creating state machine via class or interface.// For example: StateMachineHelper.CreateStateMachine<BarStateMachine>(...)// For example: StateMachineHelper.CreateStateMachine<IBarStateMachine>(...)container.BindStateMachine<BarStateMachine>();// Use this registration creating state machine via base/abstract class.// For example: StateMachineHelper.CreateStateMachine<FooStateMachineBase>(...)container.BindAbstractStateMachine<FooStateMachineBase,FooStateMachine>();// Use this registration for transitions to class or interface.// For example: Transition.GoTo<BarState>() or Transition.GoTo<IBarState>()container.BindState<BarState>();// Use this registration for transitions to base/abstract class.// For example: Transition.GoTo<FooStateBase>()container.BindAbstractState<FooStateBase,FooState>();// Singleton version of states, not recommended in general use, but can be handy in some casescontainer.BindStateMachineAsSingle<BarStateMachine>();container.BindAbstractStateMachineAsSingle<FooStateMachineBase,FooStateMachine>();container.BindStateAsSingle<BarState>();container.BindAbstractStateAsSingle<FooStateBase,FooState>();}
This library is under the MIT License. Full text ishere.
About
UniState is a modern, high-performance, scalable state machine package for Unity.