-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
- Loading branch information
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
# Morpeh.StateMachine [](#) [](#)  | ||
_State Machine for Morpeh ECS_ | ||
|
||
## How to use? | ||
|
||
```csharp | ||
using System; | ||
using Morpeh; | ||
|
||
// === Components === | ||
[Serializable] | ||
public struct UnitComponent : IComponent { } | ||
|
||
public static class UnitFsmEvents { | ||
[Serializable] | ||
public struct TargetFound : IComponent { public Entity target; } | ||
|
||
[Serializable] | ||
public struct TargetLost : IComponent { } | ||
} | ||
|
||
public static class UnitFsmStates { | ||
[Serializable] | ||
public struct Idle : IComponent { } | ||
|
||
[Serializable] | ||
public struct FollowTarget : IComponent { public Entity target; } | ||
} | ||
|
||
// === StateMachine System === | ||
public class UniBehaviourSystem : UpdateSystem { | ||
private Fsm fsm; | ||
|
||
public override void OnAwake() { | ||
this.fsm = new Fsm(World, Filter.With<UnitComponent>()); | ||
|
||
this.fsm.AtState<UnitFsmStates.Idle>() | ||
.Transition((ref UnitFsmEvents.TargetFound evt) => new UnitFsmStates.FollowTarget { | ||
target = evt.target, | ||
}); | ||
|
||
this.fsm.AtState<UnitFsmStates.FollowTarget>() | ||
.OnEnter(entity => Debug.Log("Enter FollowTarget state")) | ||
.OnExit(entity => Debug.Log("Exit FollowTarget state")) | ||
.Transition((ref UnitFsmEvents.TargetLost evt) => new UnitFsmStates.Idle()); | ||
} | ||
|
||
public override void OnUpdate(float deltaTime) { | ||
this.fsm.Execute(); | ||
} | ||
} | ||
|
||
// === State Logic Systems === | ||
public class UnitIdleSystem : UpdateSystem { | ||
private Filter unitsFilter; | ||
|
||
public override void OnAwake() { | ||
this.unitsFilter = Filter.With<UnitComponent>().With<UnitFsmStates.Idle>(); | ||
} | ||
|
||
public override void OnUpdate(float deltaTime) { | ||
foreach (var entity in this.unitsFilter) { | ||
if (TryGetTarget(out var target)) { | ||
entity.AddComponent<UnitFsmEvents.TargetFound>(); | ||
} | ||
} | ||
} | ||
|
||
private bool TryGetTarget(out Entity target) { /* ... */ } | ||
} | ||
|
||
public class UnitFollowTargetSystem : UpdateSystem { | ||
private Filter unitsFilter; | ||
|
||
public override void OnAwake() { | ||
this.unitsFilter = Filter.With<UnitComponent>().With<UnitFsmStates.FollowTarget>(); | ||
} | ||
|
||
public override void OnUpdate(float deltaTime) { | ||
foreach (var entity in this.unitsFilter) { | ||
ref var followTargetState = ref entity.GetComponent<UnitFsmStates.FollowTarget>(); | ||
|
||
if (followTargetState.target.IsNullOrDisposed()) { | ||
entity.AddComponent<UnitFsmEvents.TargetLost>(); | ||
} | ||
} | ||
} | ||
} | ||
``` | ||
|
||
## How to Install | ||
Minimal Unity Version is 2020.1. | ||
|
||
Library distributed as git package ([How to install package from git URL](https://docs.unity3d.com/Manual/upm-ui-giturl.html)) | ||
<br>Git URL: `https://github.com/codewriter-packages/Morpeh.StateMachine.git` | ||
|
||
## License | ||
|
||
Morpeh.StateMachine is [MIT licensed](./LICENSE.md). |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
using System.Collections.Generic; | ||
using JetBrains.Annotations; | ||
|
||
namespace Morpeh | ||
{ | ||
public sealed class Fsm | ||
{ | ||
internal readonly World World; | ||
internal readonly Filter Filter; | ||
internal readonly List<FsmRunnerBase> StateRunners = new List<FsmRunnerBase>(); | ||
internal readonly List<FsmRunnerBase> TransitionRunners = new List<FsmRunnerBase>(); | ||
|
||
public Fsm(World world, Filter filter) | ||
{ | ||
World = world; | ||
Filter = filter; | ||
} | ||
|
||
[PublicAPI] | ||
public FsmStateBuilder<TState> AtState<TState>() | ||
where TState : struct, IComponent | ||
{ | ||
var stateRunner = new FsmStateRunner<TState>(this); | ||
|
||
StateRunners.Add(stateRunner); | ||
|
||
return new FsmStateBuilder<TState>(this, stateRunner); | ||
} | ||
|
||
[PublicAPI] | ||
public void Execute() | ||
{ | ||
foreach (var runner in StateRunners) | ||
{ | ||
runner.Run(); | ||
} | ||
|
||
foreach (var runner in TransitionRunners) | ||
{ | ||
runner.Run(); | ||
} | ||
} | ||
} | ||
|
||
internal abstract class FsmRunnerBase | ||
{ | ||
public abstract void Run(); | ||
} | ||
} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
namespace Morpeh | ||
{ | ||
internal sealed class FsmCustomTransitionRunner<TState, TEvent> : FsmRunnerBase | ||
where TState : struct, IComponent | ||
where TEvent : struct, IComponent | ||
{ | ||
private readonly FsmTransitionCustom<TEvent> _transition; | ||
private readonly ComponentsCache<TEvent> _eventCache; | ||
private readonly ComponentsCache<TState> _stateCache; | ||
private readonly Filter _transitionFilter; | ||
|
||
public FsmCustomTransitionRunner(Fsm fsm, FsmTransitionCustom<TEvent> transition) | ||
{ | ||
_transition = transition; | ||
_eventCache = fsm.World.GetCache<TEvent>(); | ||
_stateCache = fsm.World.GetCache<TState>(); | ||
_transitionFilter = fsm.Filter.With<TState>().With<TEvent>(); | ||
} | ||
|
||
public override void Run() | ||
{ | ||
foreach (var entity in _transitionFilter) | ||
{ | ||
ref var evt = ref _eventCache.GetComponent(entity); | ||
|
||
_transition.Invoke(ref evt, entity); | ||
|
||
_stateCache.RemoveComponent(entity); | ||
_eventCache.RemoveComponent(entity); | ||
} | ||
} | ||
} | ||
|
||
public delegate void FsmTransitionCustom<TEvent>(ref TEvent evt, Entity entity); | ||
} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
namespace Morpeh | ||
{ | ||
internal sealed class FsmSimpleTransitionRunner<TState, TEvent, TNextState> : FsmRunnerBase | ||
where TState : struct, IComponent | ||
where TEvent : struct, IComponent | ||
where TNextState : struct, IComponent | ||
{ | ||
private readonly FsmTransition<TEvent, TNextState> _transition; | ||
private readonly ComponentsCache<TState> _stateCache; | ||
private readonly ComponentsCache<TEvent> _eventCache; | ||
private readonly ComponentsCache<TNextState> _nextStateCache; | ||
private readonly Filter _transitionFilter; | ||
|
||
public FsmSimpleTransitionRunner(Fsm fsm, FsmTransition<TEvent, TNextState> transition) | ||
{ | ||
_transition = transition; | ||
_stateCache = fsm.World.GetCache<TState>(); | ||
_eventCache = fsm.World.GetCache<TEvent>(); | ||
_nextStateCache = fsm.World.GetCache<TNextState>(); | ||
_transitionFilter = fsm.Filter.With<TState>().With<TEvent>(); | ||
} | ||
|
||
public override void Run() | ||
{ | ||
foreach (var entity in _transitionFilter) | ||
{ | ||
ref var evt = ref _eventCache.GetComponent(entity); | ||
|
||
var nextState = _transition.Invoke(ref evt); | ||
_nextStateCache.SetComponent(entity, nextState); | ||
|
||
_stateCache.RemoveComponent(entity); | ||
_eventCache.RemoveComponent(entity); | ||
} | ||
} | ||
} | ||
|
||
// ReSharper disable once TypeParameterCanBeVariant | ||
public delegate TState FsmTransition<TEvent, TState>(ref TEvent evt); | ||
} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
using System; | ||
using JetBrains.Annotations; | ||
|
||
namespace Morpeh | ||
{ | ||
public readonly struct FsmStateBuilder<TState> | ||
where TState : struct, IComponent | ||
{ | ||
internal readonly Fsm Fsm; | ||
internal readonly FsmStateRunner<TState> StateRunner; | ||
|
||
internal FsmStateBuilder(Fsm fsm, FsmStateRunner<TState> stateRunner) | ||
{ | ||
Fsm = fsm; | ||
StateRunner = stateRunner; | ||
} | ||
|
||
[PublicAPI] | ||
public FsmStateBuilder<TState> OnEnter(Action<Entity> callback) | ||
{ | ||
StateRunner.StateEnterCallbacks.Add(callback); | ||
return this; | ||
} | ||
|
||
[PublicAPI] | ||
public FsmStateBuilder<TState> OnExit(Action<Entity> callback) | ||
{ | ||
StateRunner.StateExitCallbacks.Add(callback); | ||
return this; | ||
} | ||
|
||
[PublicAPI] | ||
public FsmStateBuilder<TState> Transition<TEvent, TNextState>(FsmTransition<TEvent, TNextState> t) | ||
where TEvent : struct, IComponent | ||
where TNextState : struct, IComponent | ||
{ | ||
Fsm.TransitionRunners.Add(new FsmSimpleTransitionRunner<TState, TEvent, TNextState>(Fsm, t)); | ||
return this; | ||
} | ||
|
||
[PublicAPI] | ||
public FsmStateBuilder<TState> Transition<TEvent>(FsmTransitionCustom<TEvent> t) | ||
where TEvent : struct, IComponent | ||
{ | ||
Fsm.TransitionRunners.Add(new FsmCustomTransitionRunner<TState, TEvent>(Fsm, t)); | ||
return this; | ||
} | ||
} | ||
} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.