I’ve been using behavior trees in my game for about a year. They’ve worked pretty well for me, but there have always been little quirks with the AI which pops up, and these are frustrating. With 4.12, a few things changed to add to the frustration. I’ll try to detail them here:
-The AIController MoveTo() command or MoveToActorOrLocation() will not work as you’d expect. If your behavior tree needs to interrupt a movement task, one of these nodes will allow you to interrupt the logic, but the other will not (the simple version). This means that if your character is moving towards a location and a higher priority task arrives, he’ll continue moving until he arrives, then he’ll evaluate the behavior tree.
-If you put in a bunch of conditional decorators into your behavior tree, sometimes you’ll get false positives and vice versa. The behavior tree could have a selector node, one of the left nodes gets pass on all conditions, but a node to the right gets run instead.
-Aborting lower priority nodes, self nodes, or both doesn’t always work.
Conclusion: Behavior trees aren’t 100% reliable. 98% of the time they are. I’m humble enough to consider the possibility that it’s user error on my part, however.
When you create your blueprint task nodes, you have to recreate a lot of boiler plate blueprint nodes. Usually the sequence is something like this:
- Create an “Begin Execute” node, then get the AI controller, check that its valid, cast it to your own AI controller, save a reference, get the controlled pawn, check that its valid, cast it to your controlled character class, save a reference to it, etc.
- Check the blackboard values, then change them and apply behavior specific actions
Conclusion: There’s a lot of overhead costs with behavior tree tasks.
So… I’ve finally given up on behavior trees. Instead, I rebuilt my AI in C++. I pretty much recreated my behavior tree logic using a state machine within the AI controller class. Surprisingly, this only took me a full day to do and the code implementation is cleaner than the behavior tree. Instead of using a blackboard, I just created a custom struct which contained all of the key data my existing blackboard used. No more querying the blackboard for keys and key values This makes serializing this data a lot faster and easier as well. Since I use a single master function to run my AI via a state machine, I can combine all of my tasks into one master function, so if any of my tasks need to know anything about the controlled pawn, or other common meta data, it’s already been collected and sanitized at the top of the master function. This reduces the code footprint significantly I can read and write sequential code and debug it and get it to work 100% consistently, and slam it with various test cases, so there’s a huge reliability and confidence win here. Since the AI logic is all being done in an AI controller, I can create multiple behavior patterns by creating multiple master functions to run behaviors, and if I use a function pointer, switching to a completely different behavior is as simple as pointing the function pointer to a different behavior pattern. One tricky thing to note about this though: Your AI method will be running in a tick function, so if there’s something you want to be processed only once, you’ll have to design your state machine to handle that (such as creating an extra flag which gets toggled after initialization is done).
Anyways, I thought I’d share this alternative approach to AI incase anyone else wants to consider alternatives.