- Notifications
You must be signed in to change notification settings - Fork6
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
- Continuous Testing: fully covered by tests. All tests runautomatically to verify each change.
- Getting Started
- Installation
- Performance
- Framework Philosophy
- API Details and Usage
- Tutorials
- Integrations
- License
Step 1:Install UniState by adding the following URL to Unity Package Manager:https://github.com/bazyleu/UniState.git?path=Assets/UniState
.
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<IStateMachine,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{// Note that you must resolve the interface but not the implementationprivatereadonlyIStateMachine_stateMachine;publicGame(IStateMachinestateMachine){_stateMachine=stateMachine;}publicvoidStart(){_stateMachine.Execute<StartGameState>(CancellationToken.None).Forget();}}
More details on running the state machine can be foundhere.
That is it! Your first project with UniState is set up. Intutorials section more detailed tutorial can befound.
- 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.3.0
. For examplehttps://github.com/bazyleu/UniState.git?path=Assets/UniState#1.3.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.3.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. - Note: If you inherit state from StateBase, do not override the Dispose method. UseDisposablesinstead.
- 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.
GoBackTo
- Returns to specified previous state, dropping all intermediate states from theState Machine's History.
- If specified state isn't found in the history, it will exit the state machine.
- If multiple states with specified type are present in the history, the latest state will be selected.
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.GoBackTo:returnTransition.GoBackTo<BarState>();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.
You can work with the built-inStateMachine
class or supply a custom implementation by either deriving fromStateMachine
or implementingIStateMachine
.Custom interfaces that extendIStateMachine
are fully supported and can be registered side-by-side.
publicclassStateMachineWithoutHistory:StateMachine{protectedoverrideintMaxHistorySize=>0;}publicinterfaceIBarMachine:IStateMachine{publicvoidBar();}publicclassBarMachine:StateMachine,IBarMachine{publicvoidBar(){Debug.Log("Bar");}}
To use a state machine, resolve it through its interface and invokeExecute<TInitialState>(cancellationToken)
with thedesired entry state.
awaitstateMachine.Execute<FooState>(cts.Token);varpayload=newBarPayload();awaitstateMachine.Execute<BarState>(payload,cts.Token);
A state machine supports only one active execution flow.
CallingExecute()
again while the current run has not finished raisesAlreadyExecutingException
to preventconcurrent execution.
You can determine whether the machine is already running by checking propertyIsExecuting
.
Any state can launch any number of nested state machines.
Simply inject the machines through the state’s constructor, no additional action required.
publicclassRootGameplayState:StateBase{privatereadonlyIStateMachine_uiMachine;privatereadonlyIStateMachine_logicMachine;publicGameplayState(IStateMachineuiMachine,IStateMachinelogicMachine){_uiMachine=uiMachine;_logicMachine=_logicMachine;}publicoverrideasyncUniTask<StateTransitionInfo>Execute(CancellationTokentoken){// Run UI-related flow in parallel_uiMachine.Execute<UiRootState>(token).Forget();// Run logic and await completionawait_logicMachine.Execute<LogicRootState>(token);returnTransition.GoBack();}}
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>();}
During the lifetime of UniState state machine may raise state-machine-specific exceptions:
AlreadyExecutingException
— derived fromInvalidOperationException
. Thrown whenExecute()
is called while thestate machine is already executing, preventing a second concurrent run and indicating an incorrect lifecycle invocation.NoSubStatesException
— derived fromInvalidOperationException
. Thrown byDefaultCompositeState
if itsExecute()
method starts without any SubStates being present.
UniState natively supports sub-containers and sub-contexts available in modern DI frameworks.
A state machine uses thecontainer scope in which it was registered:
- Registered in the root container → its context is the root.
- Registered in a child container → its context is that child.
All states created by the machine—and every dependency those states request—are resolved through this context.
To switch the context at runtime callSetResolver(ITypeResolver)
with a resolver obtained from any container or sub-container:
IObjectResolvercontainer;varnewResolver=container.ToTypeResolver();stateMachine.SetResolver(newResolver);
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 (seeWorking Without a DI Framework.
UniState is engineered to integrate seamlessly with modern DI containers.
However, if your project does not use a DI framework you can still adopt UniState bysupplying a manual implementation ofITypeResolver
.
An example ofITypeResolver
without DI framework and state machine running:
publicclassCustomResolver:ITypeResolver{publicobjectResolve(Typetype){if(typeof(BarState)==type){returnnewBarState();}if(typeof(FooState)==type){returnFooState();}if(typeof(StateMachine)==type){returnnewStateMachine();}thrownewNotImplementedException();}}publicclassEntryPoint:MonoBehaviour{publicasyncUniTaskRun(){varresolver=newCustomResolver();varstateMachine=resolver.Resolve<StateMachine>();stateMachine.SetResolver(resolver);awaitstateMachine.Execute<FooState>(CancellationToken.None);}}}
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>{}
In this hands‑on tutorial you will create a tiny, self‑playingdice game that demonstrates the simple UniStateworkflow — from defining states to wiring everything together withVContainer.
Goal
Roll a six‑sided die until the value is 5 or 6.StartGameState
→RollDiceState
5,6 →WinState
→ Exit
1,2,3,4 →LostState
→RollDiceState
You can find codehere.
Each state inherits fromStateBase
and returns a transition that drives the flow.
internalclassStartGameState:StateBase{publicoverrideasyncUniTask<StateTransitionInfo>Execute(CancellationTokentoken){Debug.Log("Welcome to the game! Your game will be loaded in 2 seconds!");awaitUniTask.Delay(TimeSpan.FromSeconds(2),cancellationToken:token);returnTransition.GoTo<RollDiceState>();}}
publicclassRollDiceState:StateBase{publicoverrideasyncUniTask<StateTransitionInfo>Execute(CancellationTokentoken){Debug.Log("Need to roll 5+. Rolling the dice...");awaitUniTask.Delay(TimeSpan.FromSeconds(2),cancellationToken:token);vardice=Random.Range(0,7);Debug.Log($"Dice is{dice}");if(dice>4)returnTransition.GoTo<WinState>();returnTransition.GoTo<LostState>();}}
publicclassLostState:StateBase{publicoverrideasyncUniTask<StateTransitionInfo>Execute(CancellationTokentoken){Debug.Log("You lost. You will have a another chance in...");Debug.Log("3 seconds");awaitUniTask.Delay(TimeSpan.FromSeconds(1),cancellationToken:token);Debug.Log("2 seconds");awaitUniTask.Delay(TimeSpan.FromSeconds(1),cancellationToken:token);Debug.Log("1 second");awaitUniTask.Delay(TimeSpan.FromSeconds(1),cancellationToken:token);returnTransition.GoBack();}}
publicclassWinState:StateBase{publicoverrideUniTask<StateTransitionInfo>Execute(CancellationTokentoken){Debug.Log("Congratulations! You won this game!");returnUniTask.FromResult(Transition.GoToExit());}}
DiceEntryPoint runs on scene start, converts IObjectResolver into an ITypeResolver, creates the state machine, and runsStartGameState.
publicclassDiceEntryPoint:IStartable{privatereadonlyIStateMachine_stateMachine;publicDiceEntryPoint(IStateMachinestateMachine){_stateMachine=stateMachine;}publicvoidStart(){_stateMachine.Execute<StartGameState>(CancellationToken.None).Forget();}}
DiceScope is a LifetimeScope that registers the state machine and all states.The helper extensions RegisterStateMachine and RegisterState is used for registering.Note that for a state machine you must register an interface (or abstract class) and an implementation, and resolve theinterface, not the implementation.
publicclassDiceScope:LifetimeScope{protectedoverridevoidConfigure(IContainerBuilderbuilder){builder.RegisterEntryPoint<DiceEntryPoint>();builder.RegisterStateMachine<IStateMachine,StateMachine>();builder.RegisterState<StartGameState>();builder.RegisterState<RollDiceState>();builder.RegisterState<LostState>();builder.RegisterState<WinState>();}}
Create a new Unity scene (e.g., DiceGameScene).Add an empty GameObject and attach the DiceScope component.Press Play — all interaction happens in the Console:
Welcometo the game! Your game will be loadedin2 seconds!Need to roll5+. Rolling the dice...Diceis2Youlost.You will haveanother chancein...3 seconds2 seconds1 secondNeedto roll5+.Rolling the dice...Diceis6Congratulations! You wonthis game!
The 1.5.0 release removes several helper APIs and unifies state-machine usage. The table below lists each breakingchange and its direct replacement.
Removed API | Use Instead | Notes |
---|---|---|
StateMachineHelper | Inject the state machine directly via interface into the state and callExecute | Helper no longer required. |
StateMachineFactory | Inject the state machine directly via interface into the state and callExecute | Helper no longer required. |
IExecutableStateMachine | IStateMachine | Single interface for all operations. |
RegisterAbstractState /BindAbstractState and variants | RegisterState<TBase, TImpl> /BindState<TBase, TImpl> | Same functionality without theAbstract prefix. |
- Register and inject state machines by the
IStateMachine
(or your own) interface. - Replace factory/utility calls (
StateMachineHelper
,StateMachineFactory
) with state machine interface injection. - Update container bindings to the two-parameter
RegisterState
/BindState
overloads. - Remove references to
IExecutableStateMachine
, useIStateMachine
everywhere.
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).
No extra setup is required - simply resolve the state machine from the DI container and invoke its Execute method.
publicclassGameEntryPoint:IStartable{privatereadonlyIStateMachine_stateMachine;publicGameEntryPoint(IStateMachinestateMachine){_stateMachine=stateMachine;}publicvoidStart(){_stateMachine.Execute<StartGameState>(CancellationToken.None).Forget();}}
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.
Here's an example code:
privatevoidRegisterStates(IContainerBuilderbuilder){// Use these registering in general usebuilder.RegisterStateMachine<IStateMachine,BarStateMachine>();builder.RegisterState<BarState>();builder.RegisterState<IBarState,BarState>();// Singleton version of registering, not recommended in general usebuilder.RegisterStateMachine<IStateMachine,BarStateMachine>(Lifetime.Singleton);builder.RegisterState<BarState>(Lifetime.Singleton);builder.RegisterState<IBarState,BarState>(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).
No extra setup is required - simply resolve the state machine from the DI container and invoke its Execute method.
publicclassGameEntryPoint:IStartable{privatereadonlyIStateMachine_stateMachine;publicGameEntryPoint(IStateMachinestateMachine){_stateMachine=stateMachine;}publicvoidStart(){_stateMachine.Execute<StartGameState>(CancellationToken.None).Forget();}}
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.
Here's an example code:
privatevoidBindStates(DiContainercontainer){// Use these bindings in general usecontainer.BindStateMachine<IStateMachine,BarStateMachine>();container.BindState<BarState>();container.BindState<IBarState,BarState>();// Singleton version of bindings, not recommended in general usecontainer.BindStateMachineAsSingle<IStateMachine,BarStateMachine>();container.BindStateAsSingle<BarState>();container.BindStateAsSingle<IBarState,BarState>();}
This library is under the MIT License. Full text ishere.
About
UniState is a modern, high-performance, scalable state machine package for Unity.
Topics
Resources
License
Uh oh!
There was an error while loading.Please reload this page.
Stars
Watchers
Forks
Uh oh!
There was an error while loading.Please reload this page.
Contributors3
Uh oh!
There was an error while loading.Please reload this page.