Should Behavior Trees for AI be used despite C++?

I’m beginning to implement AI into my game and I see pretty much every source of reference telling me to use Behavior Trees. I would much, much prefer keeping all of my game logic in C++, but I wonder: are there any huge advantages to using BTs despite that? As far as I’ve seen, a BT just seems to be a friendly GUI organizer layer on top of what would otherwise be a very messy BP graph, similar to the AnimBP state machine graph. The only non-easily-C+±replicated thing I see (as far as I’m familiar with the API) is the Sequence, which would allow me to ex. do something immediately after moving to a destination (as there are/seem to be no callback parameters or functions available for MoveToLocation() in C++).

Is it much preferred to use BTs even in a C++ project, or can I get by pretty easily without it?

It’s fine to say “I want to use C++” for a project and always default to “well, I’ll just do this entirely in C++”, but there are system where data driven aspects make a large amount of sense. AI is one of those systems. Unless you want to spend hours coding individual AIs (and testing them), instead of letting any designer (or even yourself) quickly whip up multiple AI behaviors, test them immediately, all without a recompile.

But what exactly is the super power of the BT aside from quick testing and the Sequence? Does it have any particular functionality that would be hard to replicate in C++? When I look at the docs for it, it looks like the meat of what the AI does is done with normal BP graphs (== exact same functionality available in code), while the BT itself just defines the order of operations (with hard to read blackboard and variable references). It looks like it would be much simpler in code:


// Runs every 0.5s and sets targetToFollow and targetLocation
function agroCheck() {
	if (targetToFollow != null && closeEnough(targetToFollow, 100.0f)) {
		rapidMoveTo(targetToFollow);
	}
	else if (!isAtLocation(homeLocation) && targetLocation != null) {
		moveToAsync(targetLocation);
		wait(200.0f);
		moveToAsync(homeLocation);
	}
}

I could be wrong but this is the feeling I’m getting. It would be hear to see from people experienced in using and not using BTs whether it’s actually a lot more problematic to accomplish specific things C-side. I also don’t get what ‘data-driven’ is supposed to mean when every part of OOP programming is data-driven already.

I’ve done both in other engines, but I’ve only used BTs in UE4 simply due to how well integrated they are.

Data-driven in this context means that the code is driven through data (i.e. assets, blueprints, whatever), particularly gameplay values.

Take your example, what if I want to change the follow distance? How about the wait time? What if I want to change it so it randomly chooses a new target after 3 seconds? But maybe 3 seconds is too long so I change it to 2 seconds?

If I do that entirely in C++, I’ve just recompiled N number of times where N is the number of times it takes for things to “feel” correct. If I do things in BTs, I’ve probably knocked out all of these in the span it would take you to finish compiling once. Iteration time is everything in gameplay code. You will change numbers 1000x till you get things feeling just right, forcing a recompile every time is just a waste of time IMO.

BTs are tested and proven in shipped UE4 games. Anything else you add will not be.

Noone is forcing you to using BTs.
You can roll your own AI solution if you have time. I would do it (I have been coding on and off for some time Utility AI), because for me BTs are way to rigid, unflexible and hard to maintain.
If you need some simple AI then BTs are way to go.

Hehe… Feel free to code your own AI architecture. Then when you’ve done it and finished raging at how **** it is, come back and re-read this.

BT’s are one option for decision making and no magic bullet, but they do serve a purpose in terms of being relatively easy to understand, relatively performance and relatively battle tested. The fact that you can debug them helps a lot (check out the visual logger, breakpoint the BT etc).

Now they do have some downsides, but you can add different flavours of functionality to them pretty easily (Utility for instance). Also, why not just code your stuff using C++ as BT nodes? That way you feel comfortable creating them in a language you know, but you still have the structure and flexibility of the BT architecture? Have a look at the runtime/aimodule/behaviortree? code and make your own BT services, generators and nodes.

Trust me, hand-coding AI is just a bad idea.

1 Like

To back up what @zoombapup said, you can easily extend BTs. I did that in Able and continue to do it in all my projects. Adding new decorators, tasks, etc is pretty trivial.

It obviously depends. If you need behaviour trees then there’s no need to reinvent the wheel. There are other approaches, like utility-based AI. In that case you’d have to get your hands dirty.