Artificial Intelligence Showcase
This project showcases a variety of enemies controlled by the Unreal Engine's artificial intelligence system. This system utilizes behavior trees, environment query system and more.
At the moment we have three different types of enemies: Mele, Ranged and Flying:
Mele Enemy: They use a sword and can only attack at close range.
Ranged Enemy: They use a rifle and can attack at different ranges, from close to far.
Flying enemy: They have the hability to fly and can attack from any distance using magic spells.
Thanks to the software design we implemented, we can create and add any type of enemy we want.

Languajes
The project combines both blueprints and C++. All the complex logic related to the AI Controller, calculations, data structures and algorithms are implemented in C++. Minor details such as behavior tree tasks, animations, animation notifies, animation graphs, sounds and visual effects are handled using blueprint nodes. Some blueprint logic (or blueprint nodes) calls C++ logic. For instance, this is the case with behavior tree tasks.


Software architecture
It is important to define the classes and systems that will interact with each other. This is a key design aspect in order to maintain the code in the long term, reuse it, extend it and make it bigger with minimal changes. For this reason, we created a C++ base clase for all the enemies, where all the low-level logic is implemented. All the visual aspects of the enemy characters are handled in a blueprint class that inherits from the C++ base class. These visual aspects may include the mesh, health bar, sounds, visual effects and more.
In the diagram, you can see three different blocks. The green ones represents the interfaces, the orange ones the C++ classes and the blue ones the blueprint classes. As shown, the blueprint classes inherits from the C++ classes and the interfaces are also implemented in C++.
The CPP_Enemy class includes all the basic logic for all enemy types. Each enemy type has its own specific C++ class. For instance the Enemy Mele has its own C++ class called CPP_EnemyMele, which contains only the logic that is necesary to this enemy type. The visual logic is stored in its blueprint class BP_EnemyMele.


Enemy structure
Similarly to the enemy structure, each enemy type has its own behavior tree, but all of them use a basic behavior. For instance, each enemy type has a patrol route to follow when they are passive and also has an investigating behavior when a sound occurs near them. These behaviors are the same for all enemy types, so we can create these behaviors in separate trees, which we call sub-trees, and then execute them inside the main enemy behavior tree. At the moment, the only behavior that differs slightly for each enemy type is the attacking phase, which can be programmed inside the main enemy behavior tree.
In the graphic, you can see the basic structure of an enemy behavior tree. This tree can be replicated for each new enemy we want to add and customized for its behavior. Inside the behavior tree, we can see the attacking branch, which is built using sequences, selectors, and tasks. This branch is programmed inside the same behavior tree because each enemy will have its own attack behavior. So, we need to customize this branch for each new enemy. On the other hand, the Patrolling and Investigating branches are the same for every character we set up, so we can isolate their logic in another tree called a sub-tree. These sub-trees will be called inside the main tree of the enemy. Each sub-tree contains selectors, sequences, and behavior tasks.
Behavior tree structure


For the purpose of this demonstration, the weapon system is very simple; we only need one weapon for each enemy type. Each weapon has a C++ base class with its core logic, while the visuals remain in the blueprint class. The structure is similar to the enemy structure, where each blueprint class inherits from a unique C++ class.
In the graphic, you can see the sword class for the melee enemy and the rifle class for the ranged enemy. As you may notice, there is no weapon for the flying enemy, and that is because the flying enemy type does not have a weapon. Instead, they use a spell attack, which is a spawnable actor projectile that is created every time the enemy attacks. Each enemy type inherits from the CPP_Enemy C++ class, which implements the CPPI_Enemy interface. This interface has an attack function that will be overridden in each enemy type class. In this way, we can customize the attack for any enemy type. Thanks to the inheritance mechanism, we can solve this problem very easily.
Weapon structure
In the video, you can see how the enemies can die and react to the damage they receive. They also have a health bar that shows the current health of the enemy. All of this behavior is thanks to the health system we implemented.
Remember that our main goal is to maintain a clear code structure and design patterns so that we can extend our code with more functionalities or replicate it in more entities. To build a reusable health system, we decided to create an isolated system that can be implemented in any actors we want. We can achieve this by using actor components and attaching them to the child elements that define our actor, in this case, the enemy character. Thus, each enemy type will have this health system attached to their child elements.
The health system works independently from the character logic. It is attached to it, connected, and has the functionality to communicate with the character it belongs to.
The health system communicates with the character via event dispatchers. The health system triggers a specific event that the enemy character is subscribed to. When the health reaches 0, the event triggered by the health system kills the enemy. In other cases, when the character receives damage but does not die, the health system determines the behavior the character must perform. For instance, playing a hit animation and reducing its health by a certain amount. This system is also connected to the health bar.
The health system is fully implemented in C++ and attached from the enemy C++ side. It can also be attached from the blueprint side.
Health System




Animations
The animations used in this project are from external resources like Mixamo. They were downloaded and combined to achieve the desired behaviors. Due to the lack of flying animation resources, we were unable to achieve the movements we wanted, which is why the flying enemy appears a little strange. On top of that, to build an animation system, we used the animation blueprint, state machines, animation montages, animation offsets, and animation blendspaces to implement leaning effects and 8-directional locomotion movement when the character focuses on a target.




Artificial Intelligence
The artificial intelligence is made up of two basic components: the AI controller and the behavior tree.
The AI controller is common to all enemy types. It has a C++ part where all the core logic is implemented, and then we created a blueprint class derived from the C++ class, just to make it more manageable in the editor.
In the AI controller, we implemented the AI perception system with sight, damage, and hearing senses. The AI controller handles which action must be triggered when one of these senses is activated. For instance, the controller has the ability to store and recognize which enemies were detected at a certain time and when those enemies should be forgotten when they are no longer seen.
On the other hand, the AI controller also manages the state of the possessed character. For example, the controller determines when the controlled character is in a passive, attacking, or investigating state, and it has the ability to switch between them at any time depending on the actions taking place. To do that, the AI controller can communicate these states to the blackboard component of the behavior tree. Thus, the behavior tree will select the corresponding execution branch.
The AI controller possesses a character and also runs the behavior tree associated with that character.
The behavior tree uses tasks. These tasks are created from the blueprint side, but most of their internal logic is implemented in C++. We create blueprint behavior tree tasks to make them more manageable.

