I'm currently working on a project that will include an enemy who stalks the player. One of the things I want to practice in this prototype is implementing a Behavior Tree to control the enemy stalking patterns. I've done some research, and I think I understand how the tree works conceptually, as well as different ways to implement it. For this endeavor, since the enemy behaviors will be relatively limited and I don't want to complicate it any more than necessary, I decided to go with an implementation that simply ticks the whole tree every frame. I'm working in Unity/C# by the way.
However, I've ran into a few questions related to the design of the tree:
- From what I gather, trees are accompanied by a 'blackboard', which keeps track of variables important to the decision-making process (player position, sight/sound sensor states, player health etc.). I've placed these inside a dictionary accessible through string keys, and when a new node is created, it gets passed the data it needs to operate. My problem here is, how do I keep these variables updated in the blackboard itself? I'm tempted to give the blackboard component a reference to the GameObjects it needs to track, and just update them every frame in the component's Update() loop, but it sounds a bit inefficient. Some of these can be updated with events (player health whenever the player is damaged) but for others, like player position, it would make no difference - I'd just be firing an event in the slightest movement.
- Another issue I have is how I'm currently passing arguments to the nodes. The way I've built the system, the enemy GameObject has two components: a BehaviorTree component and a Blackboard component. The BehaviorTree is responsible for building the actual node structure -- nodes don't derive from MonoBehaviour -- in its Awake() method -- it basically uses the 'new' keyword to create instances of task nodes and 'hooks' them to composite nodes (selectors, sequences etc.). The BehaviorTree component also has a reference to the Blackboard component.
I think an example will help illustrate my point in a better way. Let's say we have an IsInSight conditional node that checks whether an entity's ‘sight sensor’ (i.e. ray, cone) has been triggered. The way I intend to track this is with a boolean variable inside the Blackboard which, in this case, is supposedly monitoring the enemy's sight cone and when a trigger enters it, the value switches to true. Since the tree is ticked every frame, when execution reaches the IsInSight node, it should check whether that bool value is true and return ‘SUCCESS’, so that the sequence can evaluate the next node. This means I should somehow pass a reference to that Blackboard variable in the node's constructor, so at the time of creation, it knows what it needs to track. However, boolean values are passed by value, so the node would be ‘stuck’ with the initial value it was passed. One solution to this I can see is just passing the GameObject references to every node, and let it grab the values it needs, but this completely eliminates the purpose of a blackboard that could be used in different contexts.
To sum up, my problems are mostly related to good practices, and not so much an inability to create the system in code. I understand that things should be kept simple when possibly, however a behavior tree is by its very nature modular, and I would like it to be implemented in a SOLID way so that it's robust enough to accomodate changes. I'm unable to find any examples that address these issues, as most articles/talks on the subject are either too abstract/conceptual, or documentation for existing solutions (i.e. UE's Behavior Tree implementation).