|
| 1 | +# Fluid State Machine |
| 2 | + |
| 3 | +Fluid State Machine is a Unity plugin aimed at creating state machines in pure code. It allows state actions to be re-used and customized on a per-project basis. |
| 4 | + |
| 5 | +* Extendable, write your own re-usable state actions |
| 6 | +* Heavily tested with TDD |
| 7 | +* Open source and free |
| 8 | + |
| 9 | +Get the [latest release](https://github.com/ashblue/fluid-state-machine/releases). |
| 10 | + |
| 11 | +## Getting Started |
| 12 | + |
| 13 | +State machines are formatted as so. For example here we have a door that demonstrates a simple open and close mechanism. |
| 14 | + |
| 15 | +```c# |
| 16 | +using UnityEngine; |
| 17 | +using CleverCrow.FluidStateMachine; |
| 18 | + |
| 19 | +public class Door : MonoBehaviour { |
| 20 | + private IFsm _door; |
| 21 | + public bool Open { private get; set; } |
| 22 | + |
| 23 | + public enum DoorState { |
| 24 | + Opened, |
| 25 | + Closed, |
| 26 | + } |
| 27 | + |
| 28 | + private void Start () { |
| 29 | + _door = new FsmBuilder() |
| 30 | + .Owner(gameObject) |
| 31 | + .Default(DoorState.Closed) |
| 32 | + .State(DoorState.Closed, (close) => { |
| 33 | + close.SetTransition("open", DoorState.Opened) |
| 34 | + .Update((action) => { |
| 35 | + if (Open) action.Transition("open"); |
| 36 | + }); |
| 37 | + }) |
| 38 | + .State(DoorState.Opened, (open) => { |
| 39 | + open.SetTransition("close", DoorState.Closed) |
| 40 | + .Update((action) => { |
| 41 | + if (!Open) action.Transition("close"); |
| 42 | + }); |
| 43 | + }) |
| 44 | + .Build(); |
| 45 | + } |
| 46 | + |
| 47 | + private void Update () { |
| 48 | + // Update the state machine every frame |
| 49 | + _door.Tick(); |
| 50 | + } |
| 51 | +} |
| 52 | +``` |
| 53 | + |
| 54 | +### Examples |
| 55 | + |
| 56 | +More complex usage examples can be found in `Assets/FluidStateMachine/Examples` you'll find multiple example projects and code snippets. If you plan on running any of the example scenes you'll want to read `Assets/FluidStateMachine/Examples/README.md` to add any missing dependencies. |
| 57 | + |
| 58 | +### Releases |
| 59 | + |
| 60 | +To get the latest build simply grab a copy from the [releases](https://github.com/ashblue/fluid-state-machine/releases) page. If you're using Node.js you can keep this package up to date by installing it with the following code via NPM. If you use the NPM package it's strongly recommended to exclude the built files `Assets/Plugins/FluidStateMachine` from version control. |
| 61 | + |
| 62 | +CODE COMING SOON |
| 63 | + |
| 64 | +## Table of Contents |
| 65 | + |
| 66 | +* [Getting Started](#getting-started) |
| 67 | + + [Examples](#examples) |
| 68 | + + [Releases](#releases) |
| 69 | +* [Action Library](#action-library) |
| 70 | + + [Defaults](#defaults) |
| 71 | + - [Enter](#enter) |
| 72 | + - [Exit](#exit) |
| 73 | + - [Update](#update) |
| 74 | + - [RunFsm](#runfsm) |
| 75 | + + [Triggers](#triggers) |
| 76 | + - [Enter](#enter-1) |
| 77 | + - [Exit](#exit-1) |
| 78 | + - [Stay](#stay) |
| 79 | + + [Animators](#animators) |
| 80 | + - [Set Animator Bool](#set-animator-bool) |
| 81 | + - [Set Animator Float](#set-animator-float) |
| 82 | + - [Set Animator Int](#set-animator-int) |
| 83 | + - [Set Animator Trigger](#set-animator-trigger) |
| 84 | +* [Creating Custom Actions](#creating-custom-actions) |
| 85 | + |
| 86 | +## Action Library |
| 87 | + |
| 88 | +Pre-made actions included in this library are as follows. |
| 89 | + |
| 90 | +### Defaults |
| 91 | + |
| 92 | +Actions targeted at hooking the default state machine lifecycle. |
| 93 | + |
| 94 | +#### Enter |
| 95 | + |
| 96 | +Triggers whenever a state is initially entered. |
| 97 | + |
| 98 | +```c# |
| 99 | +.State(MyEnum.MyState, (state) => { |
| 100 | + state.Enter((action) => Debug.Log("Code goes here")); |
| 101 | +}) |
| 102 | +``` |
| 103 | + |
| 104 | +#### Exit |
| 105 | + |
| 106 | +Triggers whenever a state is exited. |
| 107 | + |
| 108 | +```c# |
| 109 | +.State(MyEnum.MyState, (state) => { |
| 110 | + state.Exit((action) => Debug.Log("Code goes here")); |
| 111 | +}) |
| 112 | +``` |
| 113 | + |
| 114 | +#### Update |
| 115 | + |
| 116 | +Every frame a FSM's `Fsm.Tick()` method is called and the state is active this will run. |
| 117 | + |
| 118 | +```c# |
| 119 | +.State(MyEnum.MyState, (state) => { |
| 120 | + state.Update((action) => Debug.Log("Code goes here")); |
| 121 | +}) |
| 122 | +``` |
| 123 | + |
| 124 | +#### RunFsm |
| 125 | + |
| 126 | +Used to run a nested FSM inside of a state. This action will continue running until the nested FSM triggers an Exit event through `Fsm.Exit()`. When exit is triggered the passed transition will automatically fire. |
| 127 | + |
| 128 | +```c# |
| 129 | +var nestedFsm = new FsmBuilder() |
| 130 | + .Default(OtherStateId.A) |
| 131 | + .State(OtherStateId.A, (state) => { |
| 132 | + state.Enter((action) => Debug.Log("Nested FSM triggered")); |
| 133 | + // This will notify the fsm that triggered nestedFsm to stop running it |
| 134 | + state.Update((action) => action.ParentState.ParentFsm.Exit()); |
| 135 | + }) |
| 136 | + .Build(); |
| 137 | + |
| 138 | +var fsm = new FsmBuilder() |
| 139 | + .Default(StateId.A) |
| 140 | + .State(StateId.A, (state) => { |
| 141 | + state.SetTransition("next", StateId.B); |
| 142 | + // First argument is the transition triggered when `nestedFsm.Exit()` is detected |
| 143 | + state.RunFsm("next", nestedFsm); |
| 144 | + }) |
| 145 | + .State(StateId.B, (state) => { |
| 146 | + state.Enter((action) => Debug.Log("Success")); |
| 147 | + }) |
| 148 | + .Build(); |
| 149 | +``` |
| 150 | + |
| 151 | +### Triggers |
| 152 | + |
| 153 | +Hook's Unity's collider trigger system. Note that a collider component set to trigger must be included in order for this to work. |
| 154 | + |
| 155 | +#### Enter |
| 156 | + |
| 157 | +Logic fired when trigger is entered with a specific tag. |
| 158 | + |
| 159 | +```c# |
| 160 | +.State(MyEnum.MyState, (state) => { |
| 161 | + state.TriggerEnter("Player", (action) => Debug.Log("Code goes here")); |
| 162 | +}) |
| 163 | +``` |
| 164 | + |
| 165 | +#### Exit |
| 166 | + |
| 167 | +Logic fired when trigger is exited with a specific tag. |
| 168 | + |
| 169 | +```c# |
| 170 | +.State(MyEnum.MyState, (state) => { |
| 171 | + state.TriggerExit("Player", (action) => Debug.Log("Code goes here")); |
| 172 | +}) |
| 173 | +``` |
| 174 | + |
| 175 | +#### Stay |
| 176 | + |
| 177 | +Logic fired when trigger is exited with a specific tag. |
| 178 | + |
| 179 | +```c# |
| 180 | +.State(MyEnum.MyState, (state) => { |
| 181 | + state.TriggerExit("Player", (action) => Debug.Log("Code goes here")); |
| 182 | +}) |
| 183 | +``` |
| 184 | + |
| 185 | +### Animators |
| 186 | + |
| 187 | +Talks to the current Animator. Note that an Animator component must be included on the passed GameObject owner. |
| 188 | + |
| 189 | +#### Set Animator Bool |
| 190 | + |
| 191 | +Sets an animator bool by string. |
| 192 | + |
| 193 | +```c# |
| 194 | +.State(MyEnum.MyState, (state) => { |
| 195 | + state.SetAnimatorBool("myBool", true); |
| 196 | +}) |
| 197 | +``` |
| 198 | + |
| 199 | +#### Set Animator Float |
| 200 | + |
| 201 | +Sets an animator float by string. |
| 202 | + |
| 203 | +```c# |
| 204 | +.State(MyEnum.MyState, (state) => { |
| 205 | + state.SetAnimatorFloat("myFloat", 2.2); |
| 206 | +}) |
| 207 | +``` |
| 208 | + |
| 209 | +#### Set Animator Int |
| 210 | + |
| 211 | +Sets an animator int by string. |
| 212 | + |
| 213 | +```c# |
| 214 | +.State(MyEnum.MyState, (state) => { |
| 215 | + state.SetAnimatorInt("myInt", 7); |
| 216 | +}) |
| 217 | +``` |
| 218 | + |
| 219 | +#### Set Animator Trigger |
| 220 | + |
| 221 | +Sets an animator trigger by string. |
| 222 | + |
| 223 | +```c# |
| 224 | +.State(MyEnum.MyState, (state) => { |
| 225 | + state.SetAnimatorTrigger("myInt"); |
| 226 | +}) |
| 227 | +``` |
| 228 | + |
| 229 | +## Creating Custom Actions |
| 230 | + |
| 231 | +Here we'll cover how to create a custom action and use it in a way that gets free updates from this library. It's important you create new actions this way to prevent new versions from causing errors. |
| 232 | + |
| 233 | + |
| 234 | +The first thing you'll need to do is create a **custom action**. |
| 235 | + |
| 236 | +```c# |
| 237 | +using UnityEngine; |
| 238 | +using CleverCrow.FluidStateMachine; |
| 239 | + |
| 240 | +public class MyAction : ActionBase { |
| 241 | + public MyAction (string newName) { |
| 242 | + Name = newName; |
| 243 | + } |
| 244 | + |
| 245 | + // Triggers when entering the state |
| 246 | + protected override void OnEnter () { |
| 247 | + Debug.Log($"Custom action {Name} activated"); |
| 248 | + } |
| 249 | + |
| 250 | + // Triggers when exiting the state |
| 251 | + protected override void OnExit () { |
| 252 | + } |
| 253 | + |
| 254 | + // Runs every time `Fsm.Tick()` is called |
| 255 | + protected override void OnUpdate () { |
| 256 | + } |
| 257 | +} |
| 258 | +``` |
| 259 | + |
| 260 | +After the custom action is complete you'll need to create a **state builder** that inherits from the default state builder class. |
| 261 | + |
| 262 | +```c# |
| 263 | +using CleverCrow.FluidStateMachine; |
| 264 | + |
| 265 | +public class CustomStateBuilder : StateBuilderBase<CustomStateBuilder> { |
| 266 | + public CustomStateBuilder MyAction (string newName) { |
| 267 | + _actions.Add(new MyAction(newName)); |
| 268 | + return this; |
| 269 | + } |
| 270 | +} |
| 271 | +``` |
| 272 | + |
| 273 | +The state builder must then be plugged into an **FSM builder** class to properly encapsulate newly created states. |
| 274 | + |
| 275 | +```c# |
| 276 | +using CleverCrow.FluidStateMachine; |
| 277 | + |
| 278 | +public class FsmBuilderCustom : FsmBuilderBase<FsmBuilderCustom, CustomStateBuilder> { |
| 279 | +} |
| 280 | +``` |
| 281 | + |
| 282 | +You've created a custom extendable FSM and state class that can be used anywhere in your code base. Try it out with a snippet like this. |
| 283 | + |
| 284 | +```c# |
| 285 | +using UnityEngine; |
| 286 | +using CleverCrow.FluidStateMachine; |
| 287 | + |
| 288 | +public class FsmBuilderCustomUsage : MonoBehaviour { |
| 289 | + private enum StateId { |
| 290 | + A, |
| 291 | + } |
| 292 | + |
| 293 | + private void Awake () { |
| 294 | + var fsmBuilder = new FsmBuilderCustom() |
| 295 | + .State(StateId.A, (state) => { |
| 296 | + state |
| 297 | + .MyAction("custom name") |
| 298 | + .Update((action) => { }); |
| 299 | + }); |
| 300 | + |
| 301 | + var fsm = fsmBuilder.Build(); |
| 302 | + fsm.Tick(); |
| 303 | + } |
| 304 | +} |
| 305 | +``` |
0 commit comments