My behaviour tree library now exists as a git subtree in my game’s repo!
This means any updates I make to the library from other endeavors can easily be incorporated to this project. Likewise, any updates to the BT library from this game can be applied to the BT library’s own repo.
One such update is the Parallel Composite. I realize they’re really needed for real-time games, so I added them in. For example, when you need your A.I.’s moving, to act independently from shooting, allowing a run-and-gun behaviour (shooting while evading, shooting while charging in, etc.).
It’s not multi-threaded though (with this being in Unity), and I haven’t added completion policy or failure policy to it yet.
The way I made my Parallel, I think is different from the one explained in AIGameDev.com. It’s far more rudimentary since I’m still not finished with it.
- The Parallel executes all its children once in sequence, but does no early-out aborts.
- It waits for all of them to finish executing, then deliberate what its return value should be, based on the return value of its children.
- Even if a child returns “Running” (a request to suspend tree traversal), we still wait for all children to complete.
There would be two types of deliberation policies:
- At least one failure from any child will make the parallel return failure. The parallel will return success only if all children returned success.
- At least one success from any child will make the parallel return success. The parallel will return failure only if all children returned failure.
Essentially, one is strict, and the other isn’t.
What if two children of a Parallel return “Running”? I haven’t taken this into account but I imagine it would be like this:
- Traversal should continue at the very first one that signaled “Running”.
- Even if one of them finished successfully, if the other one is still in “Running”, the whole tree should still return “Running”.
- This means the traversal should keep track of all “Running” actions.
- Essentially what we want to prevent here is resetting execution of a “Running” action, when what it should really do is attempt to continue where it left off instead.
Thankfully I already made code where the traversal stack is saved when we suspend traversal. However, the idea of keeping track of two or more “Running” actions complicate things. Could be I need to store more than one traversal stack.
Real parallel nodes I believe, will keep on running its children. If one child finishes too early while the others haven’t finished yet, it gets run again.
Since my BT library runs on Unity which is hampered by restrictions on multi-threading, I’d have to do some sort of job scheduling like operating systems do.
This was one thing I thought of before: an interleaved parallel.
- This will disrupt the normal flow of traversal. The interleave will look at its first child, let it execute one of its children, then suspend that traversal. The interleave will then move on to its next child, also only allowing it to execute one of its children only. And so on.
- It’s a constant cycle of suspension and continuation of the children’s own traversals.
- When should the interleave stop? When all children have completed their traversal at least once (remember, they repeat traversal if possible). Then the return value of the interleave will be chosen from the deliberation policies mentioned earlier.
For my scenario though, I’ve yet to need this. My behaviour tree actions and conditions are very lightweight, in that they don’t actually do the actions themselves. They just send requests to the other sub-systems in the game to do the work for them. The behaviour tree only handles the deciding part, not the doing part. As such, traversal is relatively fast, that I (so far) don’t need a job scheduler for a parallel. I just traverse them linearly.
Assertion checks, on the other hand, makes sense to be checked only after resuming traversal of the whole tree (back from a “Running” state).
Why? Because most, if not all, actions always return a “Running” signal. As mentioned, actions only fire off requests to the other sub-systems in the game to do the actual work. Once that request has been sent, the tree has no choice but to wait for that to finish. For example, an attack action sends a request to play the attack animation (among other things). And only once the animation is finished do we resume traversal of the tree.
So I made a special composite just for that. It’ll automatically traverse its “assertion” child when the resuming of traversal continues from its “main” child. I called it Auditor.
The BT library by the way, is now tentatively named “INTLord”. I’m not sure if that should be INTLord, or INT Lord, or Int Lord.