6.5 Implementing a State Machine Language. State Machine in game AI The most used software pattern...
-
Upload
jemima-hampton -
Category
Documents
-
view
221 -
download
0
Transcript of 6.5 Implementing a State Machine Language. State Machine in game AI The most used software pattern...
6.5 Implementing a State Machine Language
State Machine in game AI
The most used software pattern Simple to program Easy to comprehend Easy to debug Completely general to any problem Might not always provide the best solution
But few can deny that they get the job done with minimal risk to the project
State Machine in game AI
Disadvantage of state machine No consistent structure Development cycle churns on Poor structure
This article presents Robust way to structure your state machine with a simple
language Make programming games much easier
Game Developer-Style State Machine
void RunLogic (int *state) {
switch(*state){
case 0: //Wander
Wander();
if(SeeEnemy()){
if(GetRandomChance() <0.8) *state =1;
else *state =2;
}
if(Dead()) *state =3;
break;
case 1: //Attack
Attack();
if(Dead()) *state =3;
break;
case 2: //RunAway
RunAway();
if(Dead()) *state =3;
break;
case 3: //Dead
SlowlyRot();
break;
}
}
Game Developer-Style State Machine Serious weaknesses
The state changes are poorly regulated States are of type int and would be more robust and debugg
able as enums The omission of a single break keyword would couse hard-to
-find bugs Redundant logic appears in multiple states No way to tell that a state has been entered for the first time No way to monitor or log how the state machine has behave
d over time
What is a poor game programmer to do? Provide some structure
A State Machine Language Be created with the help of macros
Have six keyword BeginStateMachine EndStateMachine State OnEnter OnExit OnUpdate
The State Machine Language Example structure of the State Machine Language
BeginStateMachine
State(STATE_Wander)
OnEnter
// C or C++ code for state entry
OnUpdate
// C or C++ code executed every tick
OnExit
// C or C++ code for state clean-up
State(STATE_Attack)
OnEnter
// C or C++ code for state entry
EndStateMachine
Actual Implementation The six macro keywords
#define BeginStateMachine if(state < 0){if(0){
#define EndStateMachine return(true);}}else(assert(0); \
return(false);}return(false);
#define State(a) return(true);}}else if(a == state){if(0){
#define OnEvent(a) return(true);}else if(a == event){
#define OnEnter OnEvent(EVENT_Enter)
#define OnUpdate OnEvent(EVENT_Update)
#define OnExit OnEvent(EVENT_Exit)
Using macro
Nice properties Expand in a building-block fashion
Handled event Provide an easy way to monitor how the state machine is
Can't mess up the state machine by forgetting something like a break keyword.
Easier to read
Support functionvoid StateMachine::Process(StateMachineEvent event){
States(event, m_currentState);
int safetyCount = 10;
while (m_stateChange && (-safetyCount >=0)) {
assert ( safetyCount > 0 && "States are flip-flopping.");
m_stateChange = false;
//Let the last state clean-up
States(EVENT_Exit, m_currentState);
//Set the new state
m_currentState = m_nextState;
//Let the new state initialize
States(EVENT_Entr, m_currentState);
}
}
Integrating this Solution How is it actually integrated within a game?
class Robot: public StateMachine{public: Robot(void){}; ~Robot(void){};private: virtual bool States(StateMachineEvent event, int state); // Put private robot specific variables here
};bool Robot::States (StateMachineEvent event, int state){
BeginStateMachine // Put any number of states and event responses. // Refer to the code on the CD for more examples.EndStateMachine
}
Conclusion State Machine Language provides
Simple enforced structure Excellent readability Natural to debug Full power of C/C++ within the state machine Easy to add code for entering or exiting a state State changes are more formal and protected Error checking for flip-flopping state changes No need to spend months developing a custom language
6.6 Enhancing a State Machine Language through Messaging
The Concept of Messages Message
A number of enumerated type
Communicate with messages between game objects
The technique of messages Event-driven behavior
enum MSG_Name { MSG_Attacked, MSG_Damaged, MSG_Healed, MSG_Poisoned };
Messages as Letters
class MSG_Object
{
public:
MSG_Name m_Name; // Message name(enumeration)
float m_Data; // Extra data for the message
objectID m_Sender; // Object tht sent the message
objectID m_Receiver; // Object that will get the message
float m_DeliveryTime; // Time at which to send the message
};
Messages as Events
Incorporate messages into the State Machine Language
A message event
BeginStateMachine
State(STATE_Wander)
OnMsg(MSG_Attacked)
SetState(STATE_RunAway);
State(STATE_RunAway)
OnEnter
//run away
EndStateMachine
Message Timers When a game object sends a message to itself to create an
internal event
BeginStateMachine
State(STATE_Wander)
OnMsg(MSG_Attacked)
SendDelayedMsgToMe(0.5, MSG_TimeOut);
OnMsg(MSG_TimeOut)
SetState(STATE_RunAway);
State(STATE_RunAway)
OnEnter
//run away
EndStateMachine
Scoping Messages Scoping
Something is only valid within a certain context
Scoping messages By defining a scope for messages Won't be misinterpreted in the wrong context The extraneous queued timeout messages are ignored
Scoping Messages
BeginStateMachine
State(STATE_Wander)
OnMsg(MSG_Attacked)
SendDelayedMsgToMe(0.5, MSG_TimeOut, SCOPE_TO_THIS_STATE);
OnMsg(MSG_TimeOut)
SetState(STATE_RunAway);
State(STATE_RunAway)
OnEnter
SendDelayedMsgToMe(7.0, MSG_TimeOut);
OnMsg(MSG_TimeOut)
SetState(STATE_Wander);
EndStateMachine
Redundant Message Policy Redundant Messages
Multiple delayed messages All basically the same messages Accumulate in the message router
Standard rules for redundant messages1. Ignore the new message
2. Replace the old message with the new one
3. Store the new message, letting redundant messages accumulate
The best policy is #1
Global Event ResponsesBeginStateMachine
OnMsg(MSG_Dead) // Global response -Triggered regardless of state SetState(STATE_Dead);
State(STATE_Wander)OnEnter
//Wander State(STATE_RunAway) OnEnter //Run away State(STATE_Dead) OnEnter //Die OnMsg(MSG_Dead) //Ignore msg - this overrids the global responseEndStateMachine
Recording Behavior for Debugging The need for good debugging of state machines is critical To monitor and record every event
That's handled and not handled by each state machine
Embed logging function Calls within the macro keywords themselves
#define BeginStateMachine if(state < 0){char statename[64]= "STATE_Global"; if(0){
#define State(a) return(true);}}else if(a == state) \
{char statename[64] =#a; if(0){
#define OnMsg(a) return(true);}else if(EVENT_Message == \
event && msg && a == msg->GetMsgName()){ \
g_debuflog.LogStateMachineEvent( \
m_Owner->GetID(),msg,statename,#a,true);
Partial listing of the main function// Create game objectGameObject myGameObject (g_database.GetNewObjectID());g_database.Store(myGameObject);
//Give the game object a state machinemyGameObject.SetStateMachine (new Robot(&myGameObject));myGameObject.GetStateMachine()>Initialized();
//Game Loopwhile(1){
g_time.MarkTimeThisTick();myGameObject.GetStateMachine()->Update();g_msgroute.DeliverDelayedMessages();
}
Summary
Make games easier and more robust to code State machines
Easy to read Easy to debug Easy to build and prototype A way to communicate with other game objects Global event responses to reduce redundant code A way to set event timers Allow transparent monitoring and logging of all events
Summary Allow execution of special code when first entered Allow execution of special code when exited
Conclusion For simplicity For maintainability For robustness For ease of debugging