A while ago I started working on a new game project. It’s called Saturday Morning Frag (Let it be SMF for short). Some sort of an arena shooter with bots – that is the idea at the current moment. Anyway, at first, the idea was to employ machine learning™ for bots AI. I was overenthusiastic at that moment. I eagerly started to build layers for my neural network. I haven’t achieved anything significant before I realized that I need to choose: either I am making a game or I am building a neural network. So, I chose the game and switched to UE4 behavior trees.
Today I finished the very first draft version of a bot AI implemented by means of a behavior tree and happy to share what I’ve learned and how you could do something similar.
First of all, I recommend that you’ll read this article.
If you are like me – then you, sort of, know what a behavior tree is. UE4 behavior trees look straightforward for you. What could go wrong, right? Actually, a lot of things could go wrong if you don’t know the exact difference between Selectors and Sequences.
Then you are welcome to read the official guides from Epic: https://docs.unrealengine.com/en-us/Engine/AI/BehaviorTrees/HowUE4BehaviorTreesDiffer
Please read, otherwise you are going to stuck in similar problems a lot: BT service does not get executed and How do I finish executing a Blueprint Task? and others. I did and wish I read the documentation first.
Finished? Alright, let’s dive in.
On the image above you can see a simple behavior tree which tells a bot what to do. It consists of a selector and two sequences. Let’s see how it works.
The offical Epic documentation says that behavior trees don’t run every frame but rather just listen to events and respond to them if needed. I did not have a chance to see that myself and my impression is that they actually run every frame. But it does not matter as long as they work. Anyway, this gives a lot more to understanding on how to create you own tree. Let’s start from the very beginning and break down the algorithm of an AI bot:
- Patrol between target points
- If a player is observed then shoot at them
This is the idea from a high level perspective. An AI bot needs to patrol between points which were assigned to him and if he sees a player, then he needs to shoot at them. He does not shot until the player dies, but rather until the player is out of AI’s sight. Then he proceeds to patrolling.
Let’s give more details:
- If a player is observed:
- Turn to the player.
- If a player is out of sight:
- Pick up a new target point.
- Get to the point.
- Wait for a second.
- Go to step 1.
This is more what a behavior tree looks like. 1 and 2 are conditions here. On the animated image above 1 is a blue conditional block “Is Player”. 2 is a conditional block “No Player”. 1.1 – 1.2 and 2.1 – 2.3 are sequences. The steps (or tasks if we are speaking in terms of Unreal Engine) in them need to be executed sequentially. If one fails, then it does not make sense to execute others: If a bot cannot find a new target point, how would he go to it? If he cannot turn to the player, how would he shoot at them? Purple blocks (leafs) at the bottom are tasks – these are actual actions which a bot needs to make. They are ordered in sequences in my case, but don’t have to – there might be situations where they could be selected by a selector. I have only one selector here – at the top level. Selector means – select the first successful child and then finish. If shooting at the player was successful (i.e. the player was found, AI bot was able to turn to them and finally shoot), then there is no need to start patrolling. Why patrolling if there is a player which needs to be shoot at?
Now, let’s touch a few UE4-specific things.
First, a player – how do we find a player? For that purpose we define a service: Find Player. It asks a bot character if he sees anything:
Service runs every 0.4 to 0.6 seconds. You can set that in Behavior Tree configuration. Every time it calls GetVisiblePlayer from an owner. If that function returns something, its result is stored in a blackboard.
GetVisiblePlayer, by the way, is implemented with use of AI Perception component. I don’t want to go into details here – but it is pretty straightforward.
Now, twice a second the behavior tree, thanks to Find Player service, knows if there is a player seen by the bot. In case if there is, then we proceed to attacking sequence, otherwise we proceed to patrolling.
Another important point – aborting tasks. Imaging, the bot is running to a next target point and suddenly stumbles into a player. By the game design, he should stop and start shooting. But behavior tree implementation will not let him do that. First, it has to finish “Move To” task, then wait for a second. Only after that the sequence would be successfully over and the behavior tree will have a chance to check one more time if a player is on the scene, so the bot would try to shoot the player. Sounds bad. At that point, the player may be already gone. For that purpose “No Player” condition has “aborts self” flag raised. What does that mean? That means if the condition is true (i.e. Player is on the scene), then the behavior tree aborts the current task immediately. That is really important.
Here is how it looks in action:
P.S. A few words about the format of this post. I understand, that the post might not seem really helpful. I am still trying to find the best way to present my findings. Please, let me know, what do you think might make it better.