Confused about how to split up AI between the Controller, Pawn, and Behavior tree

Hello! I had originally posted this on the C++ forum, but it seems no one really replied, so I’m trying again here. I’m referencing C++ here, but explaining with blueprints is just as helpful. Any help is appreciated!

I’ve read most of the documentation about controllers and pawns, but I’m still not sure how I should handle this. In my game there are multiple types of enemies, let’s use a swordsman and an archer for example. The swordsman attacks by getting close to the player and playing an animation, whereas the archer gets within range and not only plays an animation, but draws his bow, and spawns a projectile. Is a good way of handling this to create a base enemy AI pawn-class, “ABaseEnemy”, which contains the virtual function “Attack()”, and from there derive “ASwordsmanEnemy” and “AArcherEnemy” both which override that function? And then from the AI Controller cast to ABaseEnemy and if it succeeds, execute Attack()? If that’s the case then what logic (aside from sensing components and the like) should the controller handle?

Should I be having separate AI Controllers for each class of enemy? Or should they have different behavior trees and tasks? Carrying over with the same example, the way the archer attacks is very different from the way the swordsman attacks. You need the archer to get within range, aim, pull back on the bow, and fire. The way he reacts is also different from the way the swordsman reacts when an enemy gets close. Should I have the three aforementioned pawn classes for the enemies, and a single AI Controller to handle basic things like sensing, and then implement the attack functions as blackboard tasks rather than as a new player controller or built-in to the pawn class?

For instance, the swordsman pawn would simply have an “Attack()” function, whereas the archer pawn would have a “Draw()”, “Aim()”, and “Fire()”, function. Instead of creating a new AI controller to call these functions, is it appropriate to call them from the tree? In other words, to turn these into blackboard tasks? So I could have separate trees for each class, the one for the swordsman would just get close to the player and perform the “SwordsmanAttack” task which would just call the “Attack()” function if the cast to the Swordsman succeeds. The Archer tree would also get close to the player, but then call the “ArcherDraw”, “ArcherAim” and “ArcherFire” tasks with some time delayed in-between, which I can use the “Wait” task for. This way I would end up with multiple trees and multiple pawns, but not a lot of AI Controllers.

Again, there’s the first way of doing it, handling most of the AI logic in the pawn classes. The tree would be very high-level and mostly consist of “get in range => attack”, and then completely implementing the “Attack()” function in both classes. So the archer, when “Attack()” is called, would call “Draw()”, “Aim()”, and “Fire()” with built-in timers in-between. I’m not sure if I like this method too much, because even though I’m getting rid of multiple trees and tasks, I feel like I shouldn’t be handling AI logic within the pawn itself. I remember reading a post from an Epic staff member in which he compared the pawn and AI controllers to a marionette and its puppeteer, I think that AI logic being handled like this in the pawn might go against that design principle.

The last way of doing it is to have multiple AI controllers, all which have the “AI_Attack()” function. The “ASwordsmanController” class would handle getting close and attacking, whereas the “AArcherController” would handle “Draw()”, “Aim()”, and “Fire()”. I kinda like this method since it flows will with the marionette/puppeteer principle, but at the same time something about multiple controllers turns me off. I guess it’s the added weight of expecting the designers to pair up every specific pawn class to its specific controller, or nothing happening? I’m not sure what it is, but I can get over it if someone tells me this is the right choice ::.

Thanks again and sorry for the wall of text!

There are many ways of approaching this - and there is no a single best approach.

Depending on the complexity and the number of enemies, it is sometimes simpler to use BP only - at least for prototyping.
That being said, the behavior trees can handle more complex behavior better. In addition, if you have a very large number of enemies, the evented nature of the blackboard is very helpful.

On a recent project similar to your description, I went with an enemy base class in bp
that had basic implementation of fucntions like AnyDamage, Die, Attack. It also had a target and FindTarget ability.
I would then subclass for specific types of enemies like archer or swordsman.

The main logic occurred in a overriden bp function called RangeCheck which fired every tick.
This calculated the range to the target, potentially rotating towrds it.
Advancing, retreating , attacking, etc typically were based on range.

Here is blog post I wrote on Behavior Trees for an earlier game that might be useful

Good luck

Thanks for the reply! Your post was a good read as well. But now that there’s no “real” option, I’m even more confused! If I decide to go with the behavior tree route, would I create separate trees for each AI (i.e swordsman and archer)? Or make the tasks themselves more complex?

depends - you might want to get something up and running faster without behavior tree.

another approach is to use the same tree for all types… and do specialization in the tasks.

I would spike something out with the intention of throwing it away and re-doing as you gain more familiarity

Thanks! I was thinking about doing the same thing (trying out all of them), but I noticed that I could get really far doing it any of the ways, so I’m trying to see if I can save some time by asking here. If I specialize in the tasks, would the tasks themselves be specialized? In other words, would the task act differently depending on the enemy type, or would the functions the task calls be the ones which are specialized?