As promised, I’m trying to make some of the documentation I’m working on available while it’s still in progress… hopefully this is in reasonably good shape, but let me know if I need to make anything more clear (or just more interesting ;p).
Here’s the first small bit of documentation about Blackboards:
What is a blackboard?
A blackboard is a simple place where data can be written and read for decision making purposes. A blackboard can be used by a single AI pawn, shared by a squad, or used for any other purpose where it’s convenient to have a central place to look up relevant data. Blackboards are commonly used with behavior trees, but you could use them separately from behavior trees for convenience or you could make a behavior tree with no need of a blackboard.
Why use blackboards?
1. To Make Efficient Event-Driven Behaviors
Changes to the blackboard can throw events to change the flow in the behavior tree without having to update every frame to check the value. I’ll have more details about this in a separate section of Behavior Tree documentation (and I’ll link from here once that’s ready).
2. To Cache Calculations
Some data has to be calculated, which can be a pain to do repeatedly or simply expensive for performance. Therefore, it’s frequently desirable to cache the calculated value for use throughout the behavior tree so it doesn’t have to be repeatedly recalculated.
It’s more performance efficient to calculate the values less frequently, and it’s easier and less error-prone as well. If you recalculate the data in more than one place, you may accidentally calculate it differently (especially if you tweak the calculation after first creating it). Then your entire behavior may suffer from inconsistent values.
3. As a scratch-pad for behaviors
Some data you may need to use in the behavior tree doesn’t have any other obvious home. For example, when starting a sequence, you may want to note that you’ve started it so that other parts of the behavior tree don’t interrupt it. But that sequence may not readily equate to a specific class or knowledge already stored somewhere. In that case, you may want to use a Context Override* to set and clear the value.
For example, set “IsPerformingChargeAttack” true at a subtree root node (in a Context Override). Under that tree, turn to face the goal, roar, charge the goal, attack! The Boolean value for IsPerformingChargeAttack can be used to prevent higher priority behaviors from interrupting.
(* Sorry about the reference to Context Override without explanation… you can tell this documentation is a work in progress! Context Overrides are a special type of behavior Tree node in our implementation of behavior trees. I’m working on the documents that explain this type and many others. In brief, a Context Override sets values when a particular behavior starts and restores them to previous values when that behavior ends for any reason.)
(I’ll try to make some screenshots to show an example here soon.)
4. To Centralize Data
Blackboards are a nice way to centralize data and remove the need for extra knowledge about where that data comes from. Without blackboards, you often have to go through a number of steps to query information that’s stored in various different classes.
For example, get the AI Controller, and ask it for the Pawn or Character. From the Character, ask for the CrouchedEyeHeight. Or, from the Character, get the MovementComponent, cast it to your game’s special derived class MyGameCharacterMovementComponent, and then ask whether the character is sliding. Get the AI Controller and ask what its current Enemy is.
Taking all of these steps whenever you want to read data is a pain. Instead, you can use a service or some other class to post relevant data to the blackboard, and all read access can then be greatly simplified.
Centralizing data in this way is especially helpful for keeping code properly encapsulated! (If you’re not sure what code encapsulation is and you’re writing C++, you may want to read more about Object Oriented Design.)
NOTE: It’s not always best to post every bit of data you can fetch from various classes to the Blackboard. Deciding what belongs in the blackboard and what should be fetched from its primary data location is something that you need to decide on a case-by-case basis. See the other reasons to use blackboards to help make that decision.
There are a few reasons not to copy everything to the blackboard:
- Don’t clutter the blackboard with lots of super-specific-case data. If only one node needs to know something, it can potentially fetch the value itself rather than adding one more value to look through while studying every bit of the tree!
- If you fail to copy data to the blackboard properly, it may cause you some debugging nightmares! If you’re looking at a value in its source only, or in the blackboard only, you may not realize instantly that the two values differ and so are causing a problem.
- If enough values are very frequently updated and so need to be copied to the blackboard constantly, that could be bad for performance (though it’s not likely in most cases; if you’re not sure, I wouldn’t worry much about this issue, as it can also be MORE efficient to copy them).