Automata, again - 12/12/2010, 17:59About a year ago I published a video of a small project concerning automata to simulate pigeons in a plaza, and showing a draft version of the support in motorj for automata.
Lately I've been working in another project which also has automata, and I've changed the way motorj implements them. The new way I'm doing it is very convenient in my opinion, and though it looks quite simple it took me some time to design it.
So I'll explain the current state of the design.
Automata are supported through the mjPigeon class, which looks like this:
--//! Test conditions to switch between states
- virtual int Test() =0;
//! Execute current state
void Update(float t_elapsed);
--//! Holds the states
std::vector<mjPigeonState *> states;
--//! Current state's position in the states vector
--//! Time spent executing the current state (reset when switching)
--//! Current state
To create an automaton, you must make a subclass mjPigeon and implement mjPigeon() as well as implement at least 2 states.
These states are subclasses of mjPigeonState, and must be added to the state vector in the mjPigeon subclass's constructor.
This subclass should also contain relevant information (the amount of health, position, etc.). This must be specified by the progammer, mjPigeon makes no assumptions on this.
The Test() method should be implemented in every subclass of mjPigeon.
Test() is executed on every cycle the automata is updated, and is expected to contain decision criteria for what the automata should be doing. These criteria could be distance to the player character, amount of health or simply the time that it has spent without changing the current state.
Test() returns an integer which is the position in the states vector that should be executed right afterwards. For ease of use I usually place an enum which lists the states in the exact order as they are added to the vector.
Update() will execute the current state. This method is already implemented in mjPigeon and will call the Test() method and subsequently the Execute() methods in the mjPigeonState subclasses.
The mjPigeonState class is fairly simple:
--//! Set the data common to most states (in derivation 1)
virtual void SetContext(void* data)=0;
--//! Implement the specific action for each state (in derivation 2)
virtual void Execute(float t_elapsed)=0;
mjPigeonStates are fairly simple to implement - for each automaton type you should first subclass the mjPigeonState and implement SetContext(), but not Execute(). Then you should create a subclass from the mjPigeonState subclass, for each state you wish to implement.
This way each state will have access to the same data via a variable established through SetContext() (you should call SetContext() from the mjPigeon subclass's constructor for each state).
This variable could even be the mjPigeon subclass itself so all its states have access to what the mjPigeon subclass is "seeing" and "hearing".
Then, from the mjPigeonState subclass, you should implement another subclass for each state you wish the automaton to have.
This mjPigeonState sub-subclass should implement the Execute() method and modify the automaton's data as appropriate. For example, a "walk" state could modify the automaton's position in small steps while a "run" state could modify the automaton's positon in bigger steps. Or a "chase" state could exist, modifying the automaton's position specifically to go towards the player's character's position.
The mjPigeon class is in fact very general, so it is not limited to sentient entities. One other use that I think of, is menus. I will investigate if this is convenient in its current shape.
< Back to blog