Today we will see how to use a Finite State Machine (the one that is integrated into ND2Dx) to implement our game logic.
This will be the last tutorial of the Jelly Smash Tutorial series. Unlike previous tutorials, I won’t go into every details and explain every bit of code that makes up the game logic and instead focus on the Finite State Machine and why it is a very powerful system (as well as an incredible time saver)
As usual, a preview of the current progress of the game as well as the complete source code can be found at the end of this article.
What I call “game logic” is the code that represents all the rules of our game. And for Jelly Smash, we have a couple of rules :
- Toby has to be able to move by following the mouse (already seen in part 1)
- Toby needs to be able to gain momentum and jump (also seen in part 1)
- When Toby lands, he smashes all the bugs around him relative to its momentum force and score points for every bug he smashed
- If Toby smashes more than 1 bug, it does a “combo” and scores even more points
- Bugs need to move in a pseudo random way and avoid each others to fake some sort of real behavior (seen in part 2)
- Toby can’t be touched by any bug, otherwise he will lose one life (he has 5 in total)
- When Toby gains momentum, a circle representing it’s momentum force grows around him. That circle cannot be touched by any bug or he will lose on life as well
- The game ends when Toby has lost all his lives, the goal is to reach the highest score possible.
But what is a Finite State Machine ?
Or FSM in short. Simply put, it is a very convenient system to segment all your game logic into states (FSMState). Only one state can be active at a time in a “machine” (FSM). A state can represent anything you want, it’s totally up to you to decide what a state is doing and when. For more sophisticated explanations: http://en.wikipedia.org/wiki/Finite-state_machine
A state on its own doesn’t do anything, you need to add actions to it (FSMStateAction). There are already a couple of defined actions inside of ND2Dx but you can create your own custom actions (Jelly Smash contains a couple of them). Basically, an action defines the logic of a state.
But let’s take an example from Jelly Smash :
Toby has 6 states:
- IDLE: when he is doing nothing interesting (this one is used when Toby has lost all his lives and is just wandering about)
- MOVE: when Toby is moving (following the mouse)
- MOMENTUM: when he is gaining momentum by pressing the mouse button
- JUMP: when he is jumping (after releasing the mouse button when in MOMENTUM state)
- LAND: when he is landing
- HIT: when he is being hit by a bug
In each state, we have a couple of actions that define what they are doing. Let’s take the MOVE and MOMENTUM states :
// move fsmState = new FSMState("move"); fsmState.addAction(new DispatchEventAction(new FSMEvent("ON_TOBY_MOVE", FSMEvent.EVENT_TYPE_GLOBAL))); fsmState.addAction(new ActivateComponentAction(followMouseComponent)); fsmState.addAction(new NodeMouseEventAction(MouseEvent.MOUSE_DOWN, "$scene", new FSMEvent("MOMENTUM", FSMEvent.EVENT_TYPE_FSM))); fsmComponent.fsm.addState(fsmState); fsmComponent.fsm.registerStateForEventName("MOVE", fsmState); // momentum fsmState = new FSMState("momentum"); fsmState.addAction(new DeactivateComponentAction(followMouseComponent)); fsmState.addAction(new MomentumAction(this, new FSMEvent("JUMP", FSMEvent.EVENT_TYPE_FSM))); fsmState.addAction(new NodeMouseEventAction(MouseEvent.MOUSE_UP, "$scene", new FSMEvent("JUMP", FSMEvent.EVENT_TYPE_FSM))); fsmState.addAction(new DispatchEventAction(new FSMEvent("ON_TOBY_MOMENTUM", FSMEvent.EVENT_TYPE_GLOBAL))); fsmComponent.fsm.addState(fsmState); fsmComponent.fsm.registerStateForEventName("MOMENTUM", fsmState);
- first we create our state
- add an action that dispatches an event “ON_TOBY_MOVE” of type “EVENT_TYPE_GLOBAL” (that means it will be dispatched to all active FSM, we will see that later)
- add an action that activates the followMouseComponent so Toby starts following the mouse
- add an action that listens for a mouse down event and in return fires an event “MOMENTUM” of type “EVENT_TYPE_FSM” (that means it will be dispatched inside this FSM, we will see that later too)
- add the state to the FSM
- and register that state to an event name (in this case “MOVE”)
- again, we create our state
- add an action that deactivates the followMouseComponent so Toby is not following the mouse anymore
- add a custom action “MomentumAction” that scales the momentum circle according to the time that has passed
- add an action that listens for a mouse up event and in return fires an event “JUMP” of type “EVENT_TYPE_FSM”
- add an action that dispatches an event “ON_TOBY_MOMENTUM” of type “EVENT_TYPE_GLOBAL”
- add the state to the FSM
- and register that state to an event name “MOMENTUM”
When we are in the move state, the followMouseComponent gets activated so Toby follows the mouse. In that same state, we are also listening for a MOUSE_DOWN event: whenever the user presses the mouse button, an event with a name “MOMENTUM” is dispatched inside the FSM.
In the momentum state, we register that state to an event name “MOMENTUM”. So what happens is that whenever an event with a name of “MOMENTUM” fires in that FSM, it will change the current state to the state that is linked to that event name, in our case: the momentum state. This is how we switch between state inside of an FSM.
Below, a diagram that shows how fsms (green), states (dark blue) and actions (red) are disposed relative to each others and how events (light blue) are linked to states.
In this diagram, there are 2 FSMs: FSM Toby and FSM Bug. It shows how states are attached to events and how an action dispatches an event to activate the state that is attached to that event.
We saw earlier that events can have different types:
- EVENT_TYPE_FSM: the event will be dispatched inside the same FSM the current state is attached to
- EVENT_TYPE_GLOBAL: the event will be dispatched to all currently active FSMs (this allows us to communicate between FSMs and in this case, to make our bugs react to what Toby is doing)
There are other event types, but we won’t need them for now.
- A Finite State Machine segments logic into states.
- There can only be one active state at a time inside a FSM.
- A state is nothing on its own.
- States can have one or more actions attached to it.
- An action defines the logic of a state.
- A state, in order to be activated, needs to be attached to an event (a state can be activated through code only but this is for particular uses)
- When an event is being dispatched, the state that is attached to it gets activated. If there was a previous active state, that state is being deactivated before the new state gets activated.
A couple of words about the way the Finite State Machine is implemented in ND2Dx
The most common way of using a FSM in ND2Dx is through a component: FSMComponent. Simply add it to a Node2D and you’re done.
The overall look of the system might look a bit frightening at first. Having to add states and actions by code. And why would we use different actions for simple logic we could implement into a single class file you could ask ? well for this I have different answers :
- a Finite State Machine allows us to segment our logic into very specific parts, obliging us to think in a structured way. This allows for a better and clearer vision of your project. Your code is also cleaner: if you need to change only one part of your logic, you don’t need to change it inside a big class file to eventually realize that it has an impact somewhere else. Remove just the action that is not needed anymore or add one wherever your want. Also it allows for more re-usability: the same actions can be reused for different states and different FSMs (and even for different projects)
- writing everything by code might look tedious for some even if, believe me or not, I found it actually pretty fun to do. But the goal of this system is to implement it into an IDE that I’m working on at the moment. Apart from allowing you to edit everything inside a scene (like flash but for Stage3D using ND2Dx) it will also allow you to edit an FSM and add/modify actions, link states to events in a visual way (imagine the diagram above and be allowed to edit it right in an IDE)
The current progress
- avoid bugs
- press mouse button to start gaining momentum
- release mouse button to jump and smash bugs around you
- bugs can’t touch you or your momentum circle
- smash as many bugs as possible, combos make your earn more points
- my personal score is 3690 🙂