My Liner Notes for Spore/Spore Behavior Tree Docs

< My Liner Notes for Spore

Revision as of 06:42, 12 April 2009 by Checker (talk | contribs) (New page: SPBehaviorTreeDocs - The Spore Behavior Tree AI System Documentation Chris Hecker - Maxis / Electronic Arts "The present letter is a very long one, simply because I had no ...)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

 SPBehaviorTreeDocs - The Spore Behavior Tree AI System Documentation
 Chris Hecker - Maxis / Electronic Arts
    "The present letter is a very long one, simply because I had no
     leisure to make it shorter." - Blaise Pascal
 This documentation is written as a sort of "literate program", which
 means the documentation and the demo code are interspersed, and the
 code can actually be compiled to make a running sample of the AI (if
 you've also got the Animpreview prototyping environment to link in,
 but even if you don't, you at least know the code is real since it's
 used as the test rig).  This means the demo code is slightly
 exaggerated to be a good example of the system's features; you
 wouldn't necessarily use as many different features in such close
 proximity during normal use.


 Contents
 --------
 S01: Overview
 S02: Design Principles
 S03: Differences From Halo's Behavior Tree AI
 S04: How Deciders Decide
 S05: Subset Rule
 S06: Preconditions & Stimuli
 S07: Memory & Blocks
 S08: Behaviors
 S09: Initializing and Ticking the Behavior Tree
 S10: Performance
 S11: Debugging
 S12: Macros
 S13: Behavior Declaraction
 S14: Behavior Definitions
 S15: Behavior Flags
 S16: Decider Declarations
 S17: Decider Definitions
 S18: Decider and Behavior Function Definitions
 S19: The Agent Pointer
 S20: Basic Behavior Memory Blocks
 S21: Advanced Behavior Memory Blocks
 S22: Decider Blocks (DBlocks)
 S23: Group Decider Behaviors
 You can search for sections using the "Sn:" section number.  There
 are lots of comments that aren't big enough for sections, so also
 search for "//".
 
 
 S01: Overview
 -------------
 The Behavior Tree (BT) system is a library for declaring and ticking
 Hierarchical Finite State Machines operating on "agents"[1].  The
 system is blind to the type of the agent, so the agent could be a
 character, or it could be some kind of strategic entity, or it could
 just be null.
 At the highest level, a behavior tree is made up of arrays of
 "deciders".  Eventually the deciders bottom out and reference
 specific "behaviors".
 Deciders can contain references to arrays of other deciders (called
 "group deciders", and the referenced deciders are called
 "children"), or they can reference behaviors (called "behavior
 deciders")[2].
 As their name implies, deciders do the decision-making in the tree.
 A decider has a function called Decide which looks at the state of
 the world and calculates whether the decider should activate.  If
 the decider was a group decider, then the decision process repeats
 for the children.  There are a number of policies for determining
 which child in a group will run, including prioritized list,
 random, sequential, Sims-like scored competition, etc.  These are
 covered below in detail.
 
 Behavior deciders activate their referenced behavior when they
 activate.  The behavior is where the actual AI action code lives.
 Behaviors have Activate, Deactivate, and Tick functions for
 accomplishing their goals.  Assuming no higher priority deciders
 interrupt it, the currently active behavior will get ticked every
 frame and the AI will do its stuff.
 A decider that references a behavior is a leaf of the tree.  A given
 behavior can be referenced from multiple behavior deciders, and
 deciders can be referenced by multiple group deciders.  This means
 the BT is actually potentially a DAG and is not limited to a tree
 structure.
 A simple example of a behavior tree might be:
          +--------+
 *ROOT+-->| FLEE   |                               +----------------+
          | GUARD+-|------------------------------>| YELL_FOR_HELP  |
          | FIGHT  |                               | FIGHT          |
          |*EAT+---|--------+     +------------+   | PATROL         |
          | IDLE+--|--+     +---->| FIND_FOOD  |   | REST           |
          +--------+  |           |*EAT_FOOD   |   +----------------+
                      |           +------------+ 
                      |  +-------+  
                      +->| PLAY+-|--+   +--------+
                         | REST  |  +-->| FLIP   |
                         +-------+      | ROLL   |
                                        | DANCE  |
                                        +--------+
                          Figure 1.
 Figure 1 only shows deciders in the tree, in ALL CAPS.  The deciders
 with arrows coming out of them are group deciders, and they point to
 their children arrays (the boxes), which contain other deciders.
 The deciders with no arrows coming from them--the leaves of the
 tree--are behavior deciders.  These behavior deciders reference a
 set of behaviors (not shown in the figure) that may or may not be
 named similarly to the deciders and may or may not be shared between
 the deciders.
 Each call to TickBehaviorTree with the ROOT decider works its way
 down the deciders, finding either a new path through the tree to
 activate, or continuing with the currently active path.
 Walking through a simple example, assume the starred deciders in
 Figure 1 are active (ROOT->EAT->EAT_FOOD).  Also assume the group
 decision policy on the various groups is kGroupPrioritizedList,
 which means each decider's Decide will be called in order until
 either one accepts, or the execution reaches the currently active
 decider, and then it recurses.
 During a tick of this tree, we start at the ROOT and see it is
 active, so we enter the group and call the Decide function on FLEE,
 GUARD, and FIGHT in order, because they're higher priority than EAT.
 If none accept, we recurse into EAT without calling its Decide since
 it's already active.  This is how the system exhibits hysteresis:
 the test to activate a decider is different from the test to
 continue executing.
 In this case, we recurse into EAT, and call FIND_FOOD's Decide.  If
 it also does not want to run, we go into the active EAT_FOOD, and
 call Tick on the referenced behavior.
 On the other hand, if GUARD's Decide accepts, then we deactivate the
 EAT->EAT_FOOD path through the tree (so the behaviors and deciders
 can clean up if they want), and then we activate GUARD and recurse.
 The GUARD decider's children are then decided, one is activated,
 and that path becomes the new active path through the tree.  Next
 tick, only FLEE's Decide will be called in the ROOT group, because
 only FLEE is higher priority than GUARD.
 To extend the walkthrough one step farther, if, within its Tick, the
 EAT_FOOD behavior decides it no longer wants to run, it returns
 false and the group does a "masked redecide", meaning that the other
 children in the group are given a chance to Decide, but the child
 who failed is masked out of this decision step.  If one accepts,
 then the old path is deactivated and the new child is activated.  If
 none accept, the group decider pops back up a level and another
 masked redecide occurs, but now at the ROOT group level, with EAT
 masked out for this tick.  This gives IDLE a chance to activate.
 
 S02: Design Principles
 ----------------------
 
 There are a few design principles that permeate the whole system:
 1.  Clear, monolithic, and static declaration of the tree of states.
     As you'll see below, the declaration syntax of the state machine
     is highly C macro-driven and can be statically defined at file
     scope in a way that tries to make the overall structure of the
     tree visible at a glance, so it is fairly self-documenting.  The
     actual behavior function code itself can be in multiple files or
     anywhere you want it, but the tree structure should be declared
     in a single block of definition statements like below.  This
     architecture has the side benefit of compiling extremely
     quickly.
 2.  Avoid O(n^2) coupling of behaviors by brute force, and make the
     brute force execution efficient.  The entire active part of the
     tree is ticked every frame, which--in the case of the common
     prioritized list group decision policy--means all designated
     higher priority behaviors get a chance to interrupt the current
     active behavior before it is ticked.  This frees a behavior from
     having to know about other behaviors or unrelated external
     stimuli; if a more important behavior is supposed to activate,
     it will activate naturally due to the flow of decision making.
     Various flags and masks allow this ticking to proceed without
     even calling functions a lot of the time.
 
 3.  Easy visualization and debuggability.  Because a tree is a
     static plain C struct data structure with no need for a dynamic
     initialization/construction phase or allocations, it is clearly
     visible in the debugger watch window at any point during
     execution, with full named symbols even in optimized builds.
     Also, multiple runtime visualization tools are provided to
     display the entire tree, the active path through the tree, and
     hookable debug output with 5 debug levels of output detail,
     covering everything from outputing only changes in the active
     state, to full output of every decision result.
 4.  No boilerplate code.  Just about everything in the BT system has
     defaults and custom creation macros, so you should only ever need to
     write code that actually does something for your AI.  If you
     have a behavior that only needs to Tick, you don't have to write
     default Activate/Deactivate functions that simply return, or
     vice-versa if you only want Activate/Deactivate but don't need
     to Tick, etc.
 5.  Diagonal design.  As Larry Wall says, orthogonal design is
     totally overrated.  You don't want to travel in right angles,
     you cut across and go direct.  There is more than one way to do
     just about everything in the BT system.  This is flexible, but
     can be confusing.


 S03: Differences From Halo's Behavior Tree AI
 ---------------------------------------------
 
 This BT system is basically trying to be "version 1.5" of Halo's AI
 system.  The Halo system is documented in the Gamasutra article
 "Handling Complexity in the Halo 2 AI" by Damian Isla, mirrored in
 this directory as halo_ai_isla.htm.  I talked with the Halo guys a
 bunch about their current system, and then brainstormed with them
 about how to improve it since we were starting from scratch.  This
 system is the result.  The Halo guys were incredibly helpful and
 open with sharing detailed information about their system, and its
 pros and cons, and this system owes much to them.
 The Halo paper is slightly misleading in one key area: their tree is
 actually completely static.  Impusles and stimulus behaviors are not
 inserted into the tree at runtime, they are simply checked every
 tick like everything else, it's just that they won't fire until the
 conditions for them to fire are present.  This is conceptually like
 they are inserted dynamically, but in fact the tree structure is
 compile-time static.  Our system is the same.
 The biggest difference between the two systems is that we separated
 out the concept of a "decider" from a "behavior".  The Halo tree is
 all behaviors (group/mutex or regular) and impulses, and impulses
 can jump to behaviors to activate them at different priorities.  We
 unified the impulses and group behaviors into deciders, and the
 behaviors are completely separate.  This lets us reuse behaviors in
 multiple places in the tree like impulses do, without having a
 separate type.
 There are a bunch of minor differences as well (we don't check
 preconditions on already active deciders, Halo does, etc.), but the
 concepts and design principles are basically the same.


 S04: How Deciders Decide
 ------------------------
 Conceptually, a decider has a Decide function that gets called, and
 depending on the group policy, the floating point return value of
 the Decide function indicates whether the decider will activate.
 In practice, there are a number of options available for deciders.
 The specific code expression of these options will be shown below,
 so this section will only give an overview.
 
 1.  There does not need to be a Decide function.  If it is 0, then
     it is semantically the same as if it had returned 1.0.  This can
     be used by itself to make a decider that always accepts, say as
     the last entry in a priority list, like IDLE in Figure 1, or it
     can be combined with the other decider options listed here to
     avoid writing a dummy Decide.
 2.  There are optional preconditions that are checked before the
     Decide is called.  These preconditions can do things like only
     allow the Decide to be called after a given timeout, or they can
     do logical operations on two kinds of flags, DecisionFlags and
     StimuliFlags, covered below.
 3.  A Decide can delegate its decision to its children.  There are
     currently a number of caveats to this mentioned in the header at
     the function declaration, but it is possible.
 4.  The deciders have a flag that controls whether the Decide of the
     group is called when it redecides because a child failed.  This
     allows you to avoid writing the redundant checks inside the
     children if you want to check whether a group is still valid to
     run.
 In general, Decide functions should not set state on the agent
 unless you know what you're doing.  If you know the structure of
 your tree, and know that if you return 1.0 from a Decide that you
 will activate it, then it's fine to set state since you know your
 return will cause a change in the AI state.  But, if you're in a
 Decide that you don't know will guarantee activation, setting state
 on the agent could cause bugs (you set state, then don't get
 activated, so your state isn't cleaned up or you hose existing
 state).  Decide functions can use DBlocks (see below) to handle
 transient data communication between Decides and behaviors.
 
 S05: Subset Rule
 ----------------
 
 The most important thing to remember when writing a Decide function
 for a group decider is that the Decide has to obey the "Subset
 Rule".  The Subset Rule says that a Decide on a group must be a
 subset of the Decides on the children.  This could mean they are
 equal (ie. it does not have to be a proper subset), but the parent
 Decide can not be a superset of the child Decides.  If the parent
 was a superset, then the parent would accept in some circumstances,
 but the children would all reject, and then the parent's group would
 redecide, the parent would potentially accept again (ignoring the
 masking on redecides for the moment), and you'd get an infinite
 loop.  There is a redecide loop count to guard against this, but if
 it fires, the TickBehaviorTree exits with an error (which makes it
 easy to call EA_VERIFY()), and potentially no behavior ticked that
 frame so the AI is frozen.
 The Subset Rule is pretty easy to obey, it just takes a small amount
 of thought when designing your tree.  There is nothing stopping
 children from calling the parent's Decide directly (or using the
 kFlagsCallDecideOnRedecide) if that's the easiest way to enforce it.
 It's not really a constraint, because all it is actually saying is
 that if you're going to enter a sub-state, the states within it had
 better be ready to run.
 The Subset Rule makes it slightly inconvenient if all you're trying
 to do is pull some deciders out of a group into a child group to
 clean up a large list or something.  In that case, you need to use
 the DelegateDecideToChildren function in the subgroup Decide, which
 has some performance and behavior caveats as documented at its
 declaration.  If this turns out to be a big problem, it would be a
 bit of work to fix, but we can make a flag for delegation and handle
 this within the TickBehaviorTree.  Usually, groups are used as
 semantic units, like GUARD_NEST, where the decision to guard the
 nest is very different from the children decisions about how to
 guard the nest, so I don't forsee this being much of an issue.


 S06: Preconditions & Stimuli
 ----------------------------
 As mentioned above, a decider can have preconditions that are
 evaluated before the Decide is called.  There are two basic kinds:
 time checks, and flag checks.
 Time check preconditions allow you to mask off deciders based on a
 countdown, so a time check precondition of 30.0 would mean only
 evaluate this decider every 30 seconds.  The delay is from the last
 call to Decide, not from the Deactivate, which might change in the
 future.
 There are two kinds of flags checks, DecisionFlags and StimuliFlags,
 but they both have the same behavior.  You can specify "any" and
 "all" type checks (AND and OR), with 0 or 1 bits required in the
 check.
 DecisionFlags are simple ad hoc flags passed into TickBehaviorTree
 to control the enabling and disabling of deciders.  DecisionFlags
 might be used for the agent's level, whether it's an herbivore or a
 carnivore, whether it's part of a squad, whether it's dead, whether
 it's nighttime in the level, etc.
 The StimuliFlags support the same precondition logical checks as
 DecisionFlags, but they have more explicit semantic structure.  The
 StimuliFlags are passed in as a pointer to the StimuliAllFlags
 member of a behavior_stimuli structure, which stores "stimuli" for
 an agent.  A stimulus is a token with some optional data associated
 with it and a timeout.  It is used for things like inviting an agent
 to play, or notifying an agent that it took a hit, or setting some
 temporary state on yourself to avoid repeating something, etc.
 There are lots of stimuli examples spread throughout the example
 code below (search for kStimuli_), and the behavior_stimuli struct
 is declared in SPBehaviorTreeHelpers.h.
 Both of these flag checks allow the BT system to not even call a
 Decide if the preconditions are passes, and often you don't even
 need to write a Decide because you can express the conditions for
 activation with just the precondition checks.
 The preconditions support arbitrary combinations of all of these
 checks simultaneously.


 S07: Memory & Blocks
 --------------------
 The BT system manages transient per-behavior data for you, and
 passes behaviors a pointer to a fixed size block of memory for the
 behavior's use.  This memory is reused as behaviors are activated
 and deactivated, and it stores both the current path through the
 tree, and also any custom information a behavior needs for its
 execution.  With casting, it can store arbitrary C++ data with
 constructors and destructors, as long as placement new and explicit
 deletes are used correctly.  There are macros provided to make this
 easy and safe.
 The Decide function also gets passed a block (called a DBlock) that
 it can use to pass information to its behavior if necessary.
 Any persistent data is assumed to be stored on the agent (for
 example, the stimuli are a form of blackboard storage on the agent).
 Behaviors cannot pass information to other behaviors in the tree
 without using the agent or some other extra-tree means.
 More details on these blocks are below near the usage examples.


 S08: Behaviors
 --------------
 Briefly, behaviors are the chunks of code that operate on the agent
 to perform actions.  They are usually relatively chunky, like
 PATROL, and often have simple switch-statement finite state machines
 in them, using the blocks to keep their state.  Any state machine
 could have been coded into the tree, but at some point it becomes
 easier to have a simple 3-state FSM in the behavior rather than keep
 breaking things up, especially if the behavior needs temporary data
 (which would have to be lifted up to the agent if the states were
 broken up).
 It is expected there are layers of code somewhere that deal with
 things like animation blending between states, timesliced routing,
 and whatnot.  The behaviors call these layers, and they handle any
 blending necessary between their own states.  So, for example, the
 animation layer will handle blending between animations played by
 two different behaviors; the behaviors themselves should not have to
 know about behavior transitions (to avoid O(n^2) complexity
 scaling).
 Group deciders can also have behaviors that Activate/Tick/Deactivate
 with the decider.  This can be useful for doing setup and
 shutdown for the group state.  For example, notifying the nest that
 you're guarding it or no longer guarding it.  A false return value
 from Activate or Tick will fail the group and force a redecide, just
 like it does for behavior deciders.


 S09: Initializing and Ticking the Behavior Tree
 -----------------------------------------------
 Most of this documentation deals with creating the behavior tree
 itself, and writing the AI code in it, but there are a few things to
 know about setting it up and ticking it on agents.
 Each behavior tree needs to have InitializeTreeData() called on it
 once before it will be ready to use (not per-agent).  This will
 verify that the tree obeys some of the constraints (like
 kMaxNumShuffleDeciderGroupChildren, etc.), and it will initialize
 any timer preconditions that may be in the tree and set the total
 number of timers in the root decider.  It returns true if the tree
 has been initialized correctly.
 TickBehaviorTree will automatically call the InitializeTreeData
 function if it hasn't been called on the tree yet, but by that time
 it will be too late to allocate the timers if there are any in the
 tree (because you'll already be inside TickBehaviorTree).
 Each agent should have behavior_tree_memory object, a
 behavior_stimuli object, and an array of decider_timers if the tree
 has any timer-based preconditions.  The number of decider_timers is
 set by InitializeTreeData into Root.Preconditions.NumTimers, where
 Root is the name of the root level decider.  This is the only
 decider that needs to be visible to the calling code, as you can see
 in SPBehaviorTreeDocs.h.
 Every tick, the calling code should update the DecisionFlags for the
 agent state, call behavior_stimuli::Update(dt) on the agent's
 stimuli, and use the return value from this call to pass in as the
 StimuliAllFlags pointer to TickBehaviorTree.  After ticking, you can
 optionally call DebugPrintState as documented below.
 
 S10: Performance
 ----------------
 The most important performance factor to keep in mind is that the
 Decide functions should be very fast, since it is potentially called
 a lot.  It should use external timesliced systems for doing hard
 work, or at the very least use DBlocks so that it can communicate
 information to its behavior and the behavior won't have to
 redundantly compute stuff.
 The behavior Tick functions should loop internally if the behavior
 is supposed to repeat, rather than failing and letting the BT choose
 them again.  This avoids needless work.


 S11: Debugging
 --------------
 There are two main types of debugging helpers in the BT system.
 1.  DebugPrintState is a function that takes a tree and the memory
     state and will fill a text buffer with an indented view of
     either the active deciders or the entire tree, depending on a
     flag.  This is useful for tooltip style floating text in-game,
     and you can see what state an agent is in.
     Here is an example output from DebugPrintState on an agent:
     *0:ROOT/0x2d5e207 (kGroup, kGroupPrioritizedList 0x0) time: 34.32
       [(no group behavior)] idx: 1
         *1:GUARD_NEST_GROUP/0x2d5e200 (kGroup, kGroupPrioritizedList 0x1) time: 34.32
           [GUARD_NEST_GROUP/0x2c20c25] (0x00000000 0) idx: 3
             *3:GUARD_NEST_PATROL/0x2d5e203 (kBehavior 0x0) time: 4.51
               [PATROL/0x2c20c23] (0x00000000 0)
           
 2.  The BT system has debug output hooks for displaying what's going
     on during the decision making process inside TickBehaviorTree.
     The variables to hook into are at the top of SPBehaviorTree.h,
     and including DebugOutputLevel and its kin.  DebugOutputLevel 0
     is no output, 1 and 2 cause output only on behavior state
     transitions, and 3 through 5 cause output every tick.
     DebugOutputAgent set to 0 outputs for all agents, or if set
     masks all other agents.  There are DebugOutputCurrent* variables
     for doing more custom masking inside the DebugOutputFunc.
     Here is some example DebugOutputLevel == 1 output (notice the
     timestamps; it is not output every frame, but only on
     transitions):
     BTDBO(1) 0x026DBE38 @ 106.017:     activating behavior GROWL/0x2c20c22 with flags 0x0, in decider GUARD_NEST_GROWL/0x2d5e202
     BTDBO(1) 0x026DBE38 @ 106.728:     activating behavior FIGHT/0x2c20c21 with flags 0x3, in decider GUARD_NEST_FIGHT/0x2d5e201
     BTDBO(1) 0x026DBE38 @ 110.266:     activating behavior PATROL/0x2c20c23 with flags 0x0, in decider GUARD_NEST_PATROL/0x2d5e203
     The BTDBO(n) indicates the output level for the line, the next
     pointer is the agent, then the time passed to TickBehaviorTree
     during this tick.  Next is the number of spaces indicating the
     depth into the tree at which the output occured, and then is the
     custom message.  Often the messages will have a SYMBOL/id pair,
     like GROWL/0x2c20c22 (from the example behavior tree below).
     
     And here is DebugOutputLevel == 5 output, dumped every frame:
     BTDBO(3) 0x026DBE38 @ 149.267: **************** starting tree @ 0x00918CD0 for 0x026DBE38 ****************
     BTDBO(3) 0x026DBE38 @ 149.267: dt: 0.065555, DecisionFlags: 0x00000000, StimuliAllFlags: 0x00000000
     BTDBO(5) 0x026DBE38 @ 149.267: processing decider ROOT/0x2d5e207, flags: 0x0, new: 0, mask: 0x00000000, redecide: 0, block id: 0x02d5e207
     BTDBO(4) 0x026DBE38 @ 149.267: visiting current kGroup decider ROOT/0x2d5e207, kGroupPrioritizedList, 7 children, active child 1, duration 145.434
     BTDBO(5) 0x026DBE38 @ 149.267: child 0 (DIE/0x2d5e211)...failed DecisionFlags check
     BTDBO(5) 0x026DBE38 @ 149.267: ChildrenDecide returned with RunIdx -1, ShuffleIdx -1
     BTDBO(4) 0x026DBE38 @ 149.267: continuing child 1 GUARD_NEST_GROUP/0x2d5e200, duration: 145.433682
     BTDBO(5) 0x026DBE38 @ 149.267: next_decider_push GUARD_NEST_GROUP/0x2d5e200
     BTDBO(5) 0x026DBE38 @ 149.267:   processing decider GUARD_NEST_GROUP/0x2d5e200, flags: 0x1, new: 0, mask: 0x00000000, redecide: 0, block id: 0x02d5e200
     BTDBO(4) 0x026DBE38 @ 149.267:   visiting current kGroup decider GUARD_NEST_GROUP/0x2d5e200, kGroupPrioritizedList, 5 children, active child 4, duration 145.434
     BTDBO(5) 0x026DBE38 @ 149.267:   child 0 (GUARD_NEST_HELP/0x2d5e212)...passed StimuliFlags check...Decide returned 0.000000
     BTDBO(5) 0x026DBE38 @ 149.267:   child 1 (GUARD_NEST_FIGHT/0x2d5e201)...has no preconditions...Decide returned 0.000000
     BTDBO(5) 0x026DBE38 @ 149.267:   child 2 (GUARD_NEST_GROWL/0x2d5e202)...has no preconditions...Decide returned 0.000000
     BTDBO(5) 0x026DBE38 @ 149.267:   child 3 (GUARD_NEST_PATROL/0x2d5e203)...has no preconditions...Decide returned 0.000000
     BTDBO(5) 0x026DBE38 @ 149.267:   ChildrenDecide returned with RunIdx -1, ShuffleIdx -1
     BTDBO(4) 0x026DBE38 @ 149.267:   continuing child 4 GUARD_NEST_IDLE/0x2d5e204, duration: 145.433682
     BTDBO(5) 0x026DBE38 @ 149.267:   next_decider_push GUARD_NEST_IDLE/0x2d5e204
     BTDBO(5) 0x026DBE38 @ 149.267:     processing decider GUARD_NEST_IDLE/0x2d5e204, flags: 0x0, new: 0, mask: 0x00000000, redecide: 0, block id: 0x02d5e204
     BTDBO(4) 0x026DBE38 @ 149.267:     visiting current kBehavior decider GUARD_NEST_IDLE/0x2d5e204, duration 7.904
     BTDBO(3) 0x026DBE38 @ 149.267:     ticking behavior IDLE_WALK/0x2c20c24 @ 149.266829 (dt: 0.065555) with flags 0x2, duration 7.904
     BTDBO(3) 0x026DBE38 @ 149.267:     current behavior IDLE_WALK/0x2c20c24 with flags 0x2, in decider GUARD_NEST_IDLE/0x2d5e204
     BTDBO(3) 0x026DBE38 @ 149.267: **************** finished tree for 0x026DBE38, depth 3 ****************      
 You can also access the various Debug*Name char fields in the
 deciders and behaviors yourself, and there are some string arrays
 defined for introspection of types at the top of SPBehaviorTree.h.
 
 S12: Macros
 -----------
 As mentioned above, and as you'll see below, the BT system uses a
 lot of macros to reduce the need for the user to type boilerplate
 code.  The general rule is that all BT macros begin with BT_, and
 are then usually followed by either DEFINE or DECLARE, depending on
 which the macros do.  Most declarations can appear in header files,
 while most definitions have to appear in source files.  Next is the
 type of object that's defined or declared, and then options.  Often
 the options will come after a __, and the full version with all the
 options available is often postfixed with __FULL.
 Sometimes a compile error on a line with a macro will be hard to
 figure out, because something about the generated code is wrong.
 Often you can spot the problem by inspection, but remember you can
 right-click on a file in the Solution Explorer in MSVC, select
 Properties|C/C++|Preprocessor, and turn on Generate Preprocessed
 File if you have a really hard problem to track down.  Compiling the
 file will produce a filename.i file in the directory containing the
 sln file.  You can usually search for your symbol name, so something
 similar and find the generated code.  Don't forget to turn off the
 preprocessing so the compiler will generate object code again.
 If you want, you can go all the way down to the aggregate
 initializer if you want, but you shouldn't have to.  There is
 usually a __FULL macro that will give you all the access you need.
 You can avoid the auto-generated names, share functions between
 deciders, or whatever you want.


 --------------
 That's it for the high level overview.  The rest of the
 documentation is interspersed through the code below.  Major bits of
 documentation have section numbers like the above sections, but
 minor notes are just in regular C and C++ comments.
 --------------
 [1] No claim is made that HFSMs are the ultimate way to do game AI.
     However, they have certain nice clarity and development process
     advantages over other more complex simulated forms of decision
     making, like planning.  The idea behind this BT system is to
     make writing big HFSMs easy enough that you can increase the
     complexity of the AI code without needing to turn to a more
     advanced form of decision making.  Eventually I think we'll all
     have to switch over to more simuluation-y forms of decision
     making, but pushing that off as long as possible seems wise from
     a process standpoint.
 
 [2] There is a third kind of decider, a "reference decider", that
     can cause a jump from one part of the tree to another, but it is
     not implemented yet because we haven't found a use case for it
     in Spore's AI.
This page was last edited on 12 April 2009, at 07:14.