State Machine Language v3.0 Designed by Steve Rabin Nov 20th, 2008 ============================================================ 1.0 Special Instructions ============================================================ Note: Place the USERTYPE.DAT file in the C:\Program Files\Microsoft Visual Studio 10.0\Common7\IDE directory of Microsoft Visual Studio. This will highlight the state machine keywords. For 64-bit versions of Windows, the directory is C:\Program Files (x86)\Microsoft Visual Studio 10.0\Common7\IDE ============================================================ 2.0 Overview ============================================================ The State Machine Language is a hybrid approach to creating a friendly FSM scripting language for game AI that is fully contained within the game's native source language, namely C++. This results in a consistent enforced structure that is easy to read, easy to program, and easy to debug. The State Machine Language is tightly coupled with a messaging system that provides much of the power of the system. For example, timers and delays are implemented within the state machine using the messaging system. Also, all communication between AI agents can easily be accomplished via the SendMsg and OnMsg constructs that send and capture messages within states. In this implementation, game objects contain a queue of state machines, running the top one on the queue. State machines can be pushed, popped, queued, and requeued. The game object maintains a state machine manager which funnels initialization, update messages, and general messages to the current state machine. Messages are special packages that can be sent within a state machine or to state machines within other game objects. A message can be delayed so that it is delivered at a specific time in the future. It can also hold a small amount of data. Message names are defined in the file msgnames.h. There are many various helper functions for sending messages that can be seen in statemch.h. ============================================================ 3.0 State Machine Language Keywords ============================================================ The state machine language has several keywords that interchangably fit together to compose a state machine. All state machines must start with the "BeginStateMachine" keyword and end with the "EndStateMachine" keyword. Between those two keywords, states can be defined by using the "DeclareState()" keyword and placing the name of the state as the argument. The state names are defined at the top of the derived state machine cpp file as a StateName enum. Note: The very first state to execute will be the first one defined in the StateName enum. Under a DeclareState(), you can catch events such as entering the state for the first time ("OnEnter"), exiting the state ("OnExit"), an update notification every frame ("OnUpdate"), and a message notification ("OnMsg()"). Whatever C++ code appears below these keywords will be executed on those events. The space before the very first DeclareState() declaration is reserved for global message responses. This is where you can put "OnMsg()" to capture message events regardless of which state is active. However, if an "OnMsg()" listening for the same message appears in the current active state, then it will have priority and the global "OnMsg()" will not be triggered (if possible, the message is consumed by the most specific substate or state). Within a DeclareState(), you can have "DeclareSubstate()" keywords to define substates. The default substate is NULL. So if there are three DeclareSubstate() keywords defined within a DeclareState(), when the DeclareState() is initially entered it is in none of the substates. Through the use of "ChangeSubstate" in an "OnEnter", the current NULL substate can be changed to any of the defined substates. ============================================================ 4.0 State Machine Control Flow ============================================================ The state machine is driven by events. By understanding the events, it is easier to conceptualize the control flow. When a state machine is created and pushed onto a game object's state machine queue, it is initialized by sending it an OnEnter event. This can be captured in the first state that is defined in the StateName enum. Every frame, the database is sent an update message that flows to the game object and then gets pumped into the active state machine as an OnUpdate event. State machines can send messages that are routed immediately to itself or other state machines. State machines can also send delayed messages that are sent in the future. In the main game loop, after objects in the database are updated, queued delayed messages are checked to see if it is time for them to be delivered. Any ripe messages are then sent. Within a state machine, it is possible to change states using the "ChangeState" or "ChangeSubstate" function. These are requests to change states. The state change takes place once control exits the state machine. Upon a state change, an OnExit event is sent to the state machine, the state is then changed, and then an OnEnter event is sent. State changes can be delayed by using the "ChangeStateDelayed" or "ChangeSubstateDelayed" functions. Message scoping is an important technique to keep particular delayed messages from inappropriately being delivered. A delayed message (and delayed state changes as well) can be scoped to the substate, state, or state machine. If a delayed message is sent within state A and it is scoped to that state, then the message will not be delivered if the state changes. The function "SendMsgDelayedToState" will scope the message to only be valid if the current state does not change. Similarly, "SendMsgDelayedToSubstate" will scope the message to only be valid if the current substate does not change. Use "SendMsgDelayedToStateMachine" to scope a delayed message to the entire state machine, so that it's delivered regardless of substate or state changes. Note that scoped messages can still be captured by global message responses as long as the state or substate hasn't changed to invalidate the message. ============================================================ 5.0 State Machine Implementation ============================================================ The state machine keywords are implemented as macros. The macro definitions can be found at the top of the file statemch.h. In essence, a state machine is a series of "if" statements coupled with the state machine class that pumps events into it. By keeping the messy details hidden behind macros, the state machine is kept clean, easy to read, and easy to debug. An additional benefit of the macros is that logging info and error checking can be hidden within the macros. There are two sets of macros, with the default group containing the extra debugging assistance. When this debugging info is not desired, the define DEBUG_STATE_MACHINE_MACROS as the top of statemch.h can be commented out. ============================================================ 6.0 Additional Info ============================================================ Several articles/chapters have been written about various earlier versions of the State Machine Language. Please see the following articles for more information: Rabin, Steve, "Designing a General Robust AI Engine," Game Programming Gems, Charles River Media, 2000. Rabin, Steve, "Implementing a State Machine Language," AI Game Programming Wisdom, Charles River Media, 2002. Rabin, Steve, "Enhancing a State Machine Language through Messaging," AI Game Programming Wisdom, Charles River Media, 2002. Fu, Dan and Houlette, Ryan, "The Ultimate Guide to FSMs in Games," Charles River Media, 2003. Rabin, Steve, Introduction to Game Development, Charles River Media, 2005. ============================================================ 7.0 Updates and Bug Fixes ============================================================ The latest version of the State Machine Language can be downloaded from www.introgamedev.com or www.aiwisdom.com. ============================================================ 8.0 Contact ============================================================ Comments, suggestions, and bug fixes should be e-mailed to steve.rabin@gmail.com THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.