Transferred from:
Status-driven game agent design (I)
Http://edu.gamfe.com/tutor/d/30601.html
Status-driven game agent design (2)
Http://edu.gamfe.com/tutor/d/30600.html
Status-driven game agent design (III)
Http://edu.gamfe.com/tutor/d/39940.html
Status-driven game agent design (III)
As the design stands, it's necessary to create a separate statebase class for each character type to derive its States from. instead, let's make it reusable by turning it into a class template.
As a foothold, it is necessary to construct an independent state base class for each angle class to obtain its own State. We can use a class template to make it reusable:
Template
Class state
{
Public:
Virtual void enter (entity_type *) = 0;
Virtual void execute (entity_type *) = 0;
Virtual void exit (entity_type *) = 0;
Virtual ~ State (){}
};
The Declaration for a concrete State-using the entermineanddigfornuggetminer state as an example-now looks like this:
The following figure shows the entermineanddigfornugget status of the miner class:
Class entermineanddigfornugget: public State
{
Public:
/* Omitted */
};
This, as you will see shortly, makes life easier in the long run.
As you can see, it is short and concise.
Global States and State blips
Global Status and status flashing (sincerely for better translation)
More often than not, when designing finite state machines you will end up with code that is duplicated in every state. for example, in the popular game the Sims by Maxis, a sim may feel the urge of nature come upon it and have to visit the bathroom to relieve
Itself. this urge may occur in any state the sim may be in and at any time. given the current design, to bestow the gold miner with this type of behavior, duplicate conditional logic wowould have to be added to every one of his states, or alternatively, placed
Into the miner: updatefunction. while the latter solution is accept-able, it's better to create a global statethat is called every time the FSM is updated. that way, all the logic for the FSM is contained within the states and not in the agent class that
Owns the FSM.
Generally, when designing a finite state machine, you will end up with duplicate code in all States. For example, in the popular "The Sims (Second Life)" game developed by Maxis, Sim can feel the physiological needs of the internal emergency and must go to the bathroom to solve the problem. No matter where the SIM is or when it is, it may happen in an emergency. According to the current design, when such an action is added to the gold miner, the duplicate conditional logic may be added to every State or placed in the miner: update function. The following describes an acceptable solution, which adds a global state-called when FSM is updated. In this way, all the logic of FSM is included in the state, not in the FSM of the intelligent body class.
To implement a global state, an additional member variable is required:
To implement the Global Status, you need to add a member variable:
// Notice how now that state is a class template we have to declare the entity type
State * m_pglobalstate;
In addition to global behavior, occasionally it will be convenient for an agent to enter a state with the condition that when the State is exited, the agent returns to its previous state. I call this behavior a State blip. for example, just as in the Sims,
You may insist that your agent can visit the bathroom at any time, yet make sure it always returns to its prior state. to give an FSM this type of functionality it must keep a record of the previous State so the state blip can revert to it. this is easy
Do as all that is required is another member variable and some additional logic in the miner: changestatemethod.
Sometimes the agent enters another State from one state. When it exits, it needs to return to its previous state, which I call a state flash. For example, in the Sims, you may have to enable your agent to enter the bathroom at any time before returning to the previous state. To implement this function, you must record the previous status to return the result when the status is flashing. This can be easily achieved by adding member variables and adding some additional logic to the miner: changestate method.
[Note: The concept of State flashing is indeed hard to understand. So I drew the following figure to help you understand it.]
Figure 1 flashing
By now though, to implement these additions, the minerclass has acquired two extra member variables and one additional method. It has ended up looking something like this (extraneous detail omitted ):
To complete these additional functions, two member variables and a method have been added to the miner class. It finally looks like this (ignore irrelevant elements ):
Class Miner: Public basegameentity
{
PRIVATE:
State * m_pcurrentstate;
State * m_ppreviousstate;
State * m_pglobalstate;
...
Public:
Void changestate (State * pnewstate );
Void reverttopreviusstate ();
...
};
Hmm, looks like it's time to tidy up a little.
Well, you do need to sort it out.
Creating a state machine class
Create a state machine class
The design can be made a lot cleaner by encapsulating all the State related data and methods into a state machine class. this way an agent can own an instance of a state machine and delegate the management of current States, global states, and previous states
To It.
Encapsulating all State-related data and methods into a state machine class facilitates simplified design. This allows the agent to have a state machine instance and delegate it to manage the current, global, and previous status.
With this in mind take a look at the following statemachineclass template.
Now let's take a look at the statemachine template class.
Template
Class statemachine
{
PRIVATE:
// A pointer to the agent that owns this instance
Entity_type * m_powner;
State * m_pcurrentstate;
// A record of the last state the agent was in
State * m_ppreviousstate;
// This state logic is called every time the FSM is updated
State * m_pglobalstate;
Public:
Statemachine (entity_type * owner): m_powner (owner ),
M_pcurrentstate (null ),
M_ppreviousstate (null ),
M_pglobalstate (null)
{}
// Use these methods to initialize the FSM
Void setcurrentstate (State * s) {m_pcurrentstate = s ;}
Void setglobalstate (State * s) {m_pglobalstate = s ;}
Void setpreviusstate (State * s) {m_ppreviousstate = s ;}
// Call this to update the FSM
Void Update () const
{
// If a global state exists, call its execute Method
If (m_pglobalstate) m_pglobalstate-> execute (m_powner );
// Same for the current State
If (m_pcurrentstate) m_pcurrentstate-> execute (m_powner );
}
// Change to a new state
Void changestate (State * pnewstate)
{
Assert (pnewstate &&
": Trying to change to a null state ");
// Keep a record of the previous State
M_ppreviousstate = m_pcurrentstate;
// Call the exit method of the existing State
M_pcurrentstate-> exit (m_powner );
// Change state to the new State
M_pcurrentstate = pnewstate;
// Call the entry method of the new State
M_pcurrentstate-> enter (m_powner );
}
// Change State back to the previous state
Void reverttopreviusstate ()
{
Changestate (m_ppreviousstate );
}
// Accessors
State * currentstate () const {return m_pcurrentstate ;}
State * globalstate () const {return m_pglobalstate ;}
State * previusstate () const {return m_ppreviousstate ;}
// Returns true if the current state's type is equal to the type of
// Class passed as a parameter.
Bool isinstate (const State & St) const;
};
Now all an agent has to do is to own an instance of a statemachineand implement a method to update the state machine to get full FSM functionality.
All the smart bodies can now have a statemachine instance. What needs to be done is to implement a method to update the state machine to obtain the complete FSM function.
The translator shows that he has obtained the authorization for the Chinese version. This article is only for the purpose of research and learning. No one shall use the English and Chinese versions for commercial activities without consent. The legal and moral liabilities arising from the reproduction of this document shall be borne by the transcoder, and shall not be related to the translator.
The improved minerclass now looks like this:
The newly implemented miner class looks like this:
Class Miner: Public basegameentity
{
PRIVATE:
// An instance of the state machine class
Statemachine * m_pstatemachine;
/* Extraneous detail omitted */
Public:
Miner (int id): m_location (shack ),
M_igoldcarried (0 ),
M_imoneyinbank (0 ),
M_ithirst (0 ),
M_ifatigue (0 ),
Basegameentity (ID)
{
// Set up state machine
M_pstatemachine = new statemachine (this );
M_pstatemachine-> setcurrentstate (gohomeandsleeptilrested: instance ());
M_pstatemachine-> setglobalstate (minerglobalstate: instance ());
}
~ Miner () {Delete m_pstatemachine ;}
Void Update ()
{
++ M_ithirst;
M_pstatemachine-> Update ();
}
Statemachine * getfsm () const {return m_pstatemachine ;}
/* Extraneous detail omitted */
};
Notice how the current and global states must be set explicitly when a statemachineis instantiated. The class hierarchy is now like that shown in Figure 2.4.
Note how to correctly design the current and global states after statemachine is instantiated. Figure 2.4 shows the current class hierarchy chart.
Figure 2.4. The updated Design
Introducing Elsa
Introduction to Elsa
To demonstrate these improvements, I 've created another project: westworldwithwoman. in this project, west world has gained another inhabitant, Elsa, the gold miner's wife. elsa doesn't do much; she's mainly preoccupied with cleaning the shack and emptying
Her bladder (she drinks way too much cawtion). The state transition divisor for Elsa is shown in Figure 2.5.
To verify these improvements, I created a new project-westworldwithwoman. In this project, Westworld has another character, Elsa, who is the wife of the gold miner Bob. ELSA does not do much, mainly cleaning the house and going to the bathroom (she drank too much coffee ). Figure 2.5 shows the status transition of Elsa.
Figure 2.5. Elsa's state transition dimo-. the global state is not shown in the figure because its logic is refreshing tively implemented in any state and never changed.
When you boot up the project into your ide, notice how the visitbathroomstate is implemented as a blip state (I. E ., it always reverts back to the previous state ). also note that a global state has been defined, wifesglobalstate, which contains the logic required
For Elsa's bathroom visits. This logic is contained in a global state because Elsa may feel the call of nature during any State and at any time.
When you import this project to your ide, pay attention to how the visitbathroom status is implemented in the form of State flashing (that is, how it returns to the previous state ), it is also worth noting that a global state-wifesglobalstate is defined, which contains the logic required for the restroom on the Elsa. This logic is included in the global state because Elsa may feel anxious at any time. This is a natural nature, haha.
Here is a sample of the output from westworldwithwoman.
Here is the output example of the westworldwithwoman project.
Miner BOB: pickin 'up a nugget
Miner BOB: Ah'm leavin' the gold mine with MAH pockets full o' sweet gold
Miner BOB: goin' to the bank. Yes siree
Elsa: walkin 'to the can. Need to powda mah pretty li' l nose
Elsa: Ahhhhhh! Sweet relief!
Elsa: leavin' the John
Miner BOB: Depositin 'gold. Total savings now: 4
Miner BOB: leavin' the bank
Miner BOB: walkin 'to the Gold Mine
Elsa: walkin 'to the can. Need to powda mah pretty li' l nose
Elsa: Ahhhhhh! Sweet relief!
Elsa: leavin' the John
Miner BOB: pickin 'up a nugget
Elsa: moppin 'The floor
Miner BOB: pickin 'up a nugget
Miner BOB: Ah'm leavin' the gold mine with MAH pockets full o' sweet gold
Miner BOB: Boy, Ah sure is thusty! Walkin 'to the saloon
Elsa: moppin 'The floor
Miner Bob: That's mighty fine sippin 'liquor
Miner BOB: leavin 'the saloon, feelin 'good
Miner BOB: walkin 'to the Gold Mine
Elsa: makin 'the bed
Miner BOB: pickin 'up a nugget
Miner BOB: Ah'm leavin' the gold mine with MAH pockets full o' sweet gold
Miner BOB: goin' to the bank. Yes siree
Elsa: walkin 'to the can. Need to powda mah pretty li' l nose
Elsa: Ahhhhhh! Sweet relief!
Elsa: leavin' the John
Miner BOB: Depositin 'gold. Total savings now: 5
Miner BOB: Woohoo! Rich enough for now. Back home to MAH li' l lady
Miner BOB: leavin' the bank
Miner BOB: walkin 'home
Elsa: walkin 'to the can. Need to powda mah pretty li' l nose
Elsa: Ahhhhhh! Sweet relief!
Elsa: leavin' the John
Miner BOB: zzzz...
Well, that's it folks. the complexity of the behavior you can create with finite state machines is only limited by your imagination. you don't have to restrict your game agents to just one finite state machine either. sometimes it may be a good idea to use
Two fsms working in parallel: one to control a character's movement and one to control the weapon selection, aiming, and firing, for example. it's even possible to have a State itself contain a state machine. this is known as a hierarchical state machine.
For instance, your game agent may have the States release E, combat, and patrol. in turn, the combat State may own a state machine that manages the States required for combat such as Dodge, chaseenemy, and shoot.
To be exaggerated, this is amazing. You can use finite state machines to create very complex behaviors. The complexity is limited by your similarity. You don't have to limit that your game intelligence can only have one finite state machine. Sometimes you can use two FSM to work in parallel: one controlling the movement of a role, and the other controlling weapon selection, targeting, and opening. You can even create a state machine that contains a state machine, that is, a hierarchical state machine. For example, your game intelligence may be in the following states: detection, combat, and patrol, while combat) the status can also have a state machine to manage the States required for battles such as Dodge, chaseenemy, and shoot.