There are only just a few relevant classes and the rest are test cases, where you can see the usage. If you just dive in, you will probably find it less to digest than you think. In any case, here is a super-quick FSM extracted from a test:
//---------------------------
// Declarations...
Diagram diagram;
State open;
State closed;
Transition openToClosed;
Transition closedToOpen;
Transition startToClosed;
SimpleContext context;
// Construction...
open = new State ("open");
closed = new State ("closed");
openToClosed = new Transition (closed);
closedToOpen = new Transition (open);
startToClosed = new Transition (closed);
open.AddTransition (openToClosed);
closed.AddTransition (closedToOpen);
closedToOpen.SetCondition (new IsUnlocked ());
diagram = new Diagram ();
diagram.AddState (open);
diagram.AddState (closed);
diagram.GetStartState ().AddTransition (startToClosed);
// Create a Context instance... (as many as you like)
context = new SimpleContext (diagram.GetStartState ());
//---------------------------
// Lock/Unlock door based on events; this would be unique to your context
context.isLocked = false;
//---------------------------
// Step the logic (in Update, FixedUpdate, or wherever)
diagram.Step (context);
//---------------------------
Incidentally, this mini-library has nothing to do with GameObjects directly, unless you choose to use the component Update methods to tick your logic.
Also it is important to note:
The advantage of this implementation is that the LOGIC is separate from the CONTEXT. So, you design your logic once (i.e. enemy AI behavior) and instantiate as many contexts as you like (each individual enemy). Saving complex game state is also made easier by being able to save the contexts, independent of their logic.
And, if you want to get wild and crazy, you could mix and match contexts and logic, although you would have to be disciplined enough to maintain compatibility or handle exceptions for odd cases. This means you could update logic without touching the contexts in future versions, and vice versa. This could take the form of in-app purchased upgrades, etc.
Additionally, the ACTIONS are only loosely coupled, allowing them to be shared and offering a high level of modularity. Actions are also of a composite nature allowing you to create hierarchical, functional action sets (which can be cached in an ActionPool) that can be plugged or unplugged at runtime and also don’t need to be serialized to get your gamestate back after a load. It is quite simple to implement a Strategy Pattern with the Actions, which can be very useful for things like dynamically swapping out input handing Actions on game mode switches.
States and Transitions can trigger various actions (enter, exit, conditions, etc.) although as a rule-of-thumb, it is a good idea to attach your Actions to transitions. Many game developers trigger logic while IN a particular state (which ‘can’ be ok) but a truly event-driven system reacts to changes in the Context. If nothing changed, nothing happens. I usually prefer a cyclic transition that reacts to an event over continuously updating logic while never leaving a state. If you do choose to update logic while in a state over many steps, you should ensure that this logic can gracefully recover from being interrupted if a transition happens to be triggered.
Hope that gets your foot in the door…