Game Project 4

Info

Role: Lead Programmer / Enemy AI Programmer

For this 5-week game project, I worked as the Lead Programmer and was mainly responsible for creating the game’s enemy AI systems using both Unreal Engine Blueprints and C++.

My biggest contribution was designing and implementing 7 different enemy types in total. These included 2 animal (One is enemy other one just roam around), 3 standard enemy types, and 2 boss enemies. Each enemy had a different role in the game, helping make the gameplay more fun, varied, and less repetitive.

In addition to enemy AI, I contributed to several other areas of the project, including the main menu, player systems, music manager, and general gameplay functionality.

BaseEnemy — Shared AI Foundation

The BaseEnemy class was written in C++ and worked as the foundation for the enemy AI system. It stored shared enemy stats such as field of view, health, attack damage, and other basic values.

It also included shared functions like SeeingPlayer, ChangeHealth, and other base behavior that multiple enemies used. I also created a custom vision cone for player detection, which was used to check if the enemy could see the player.

All important stats could be adjusted inside each enemy’s child Blueprint, making it easy to balance and customize different enemy types without changing the C++ code.

Group Combat Positioning

To make group combat feel more organized, I created a custom positioning function for melee enemies. Instead of allowing all enemies to attack the player from the same point, the system assigned attack positions around the player.

The two melee enemies closest to the player were assigned one of four possible positions: in front of the player, to the right, to the left, or behind the player. For example, one enemy could take the front position while another enemy moved to the right side.

The system could also check if a position was blocked by the environment. If a spot was blocked, the enemy would avoid using that position and choose a better one. This helped make combat feel clearer, more balanced, and less crowded around the player.

Standard Enemy Types

Each enemy was built using a flexible AI system that allowed the level designer to choose how the enemy should behave when placed in the level. An enemy could either stand still and wait or patrol an area until it detected the player.

Once the enemy spotted the player, it would first alert nearby enemies, causing them to move toward its position. After calling for help, the enemy would enter its attack state, where it chased the player and attacked once it was within range.

To make combat feel more controlled and less chaotic, only two melee enemies could attack the player at the same time. The other melee enemies would stay at a distance and wait for their turn to attack. We also implemented a perfect block system where enemies would become stunned if the player blocked at the right moment. This gave the player a chance to counterattack and added more skill-based timing to the combat.

This system helped make enemy encounters feel more dynamic, since enemies could be placed in different ways depending on the level design. It also allowed groups of enemies to react together instead of every enemy acting completely on its own.

Enemy Types

Goblin — Normal Enemy

The Goblin is the standard enemy type in the game (Enemy seen in clip above). It is equipped with a club and shield and acts as the most common enemy the player encounters. Its main behavior is to run toward the player and attack when it gets close enough. Gets stunned when hit by player.

The Goblin was designed to be a basic but important enemy that helps introduce the player to combat and keeps pressure on them during encounters.

Debug mode to show how they work

Orc — Heavy Enemy

The Orc is the heavy enemy type in the game. Compared to the normal Goblin, the Orc has more health and deals more damage, making it a bigger threat in close combat. Is the only one not to get stunned when hit by player.

The Orc has two normal attacks, similar to the Goblin, but also has a heavy attack that cannot be blocked by the player. This forces the player to react differently instead of relying only on blocking. When fighting the Orc, the player needs to pay more attention to its attack patterns and avoid the heavy attack at the right moment.

The Orc also has an anti-ranged attack. If the player tries to use the bow against him, the Orc can teleport directly to the player and perform a heavy attack. This prevents the player from safely defeating the Orc from a distance and makes the enemy feel more threatening.

The Orc was designed to add more challenge and variety to melee combat by being a stronger enemy that punishes both careless close-range fighting and unsafe ranged attacks.

Archer — Ranged Enemy

The Archer is the ranged enemy type in the game. On his own, he is not the biggest threat, but when combined with enemies like the Goblin and Orc, he becomes much more dangerous. While the melee enemies keep pressure on the player up close, the Archer attacks from a distance and forces the player to stay aware of their surroundings.

During combat, the Archer moves in a circle around the player while constantly facing them. This helps him keep distance and makes him harder to ignore during a fight.

The Archer also uses a ray trace to check if he has a clear line of sight before shooting. If there is something blocking the shot, such as level geometry or another enemy, he will not fire until he has a clear angle on the player.

Standard Enemies Overview

To keep the enemy system organized, the standard enemy types were built from a shared BaseEnemyCharacter class. This class inherited from the more general BaseEnemy class, which handled core enemy AI functionality (Health, Attack points, etc).

Since the Goblin, Orc, and Archer shared many of the same systems, such as detection, patrol behavior, chasing, attacking, and alerting nearby enemies, they also used the same Behavior Tree. This made it easier to reuse functionality and keep the AI logic consistent, while still giving each enemy its own unique role, stats, and attacks.

Enemy Camp — Enemy Spawning System

The Enemy Camp was a Blueprint system used to spawn the three standard enemy types: Goblin, Orc, and Archer.

Instead of placing every enemy directly in the level from the start, the camp handled spawning them when needed. This helped improve performance by avoiding having all enemies active at the same time.

The camp checked how close the player was before spawning enemies. This check was done using a timer instead of Event Tick, which helped reduce unnecessary performance cost.

The Enemy Camp was also designed to be easy for the level designer to use. When placing a camp in the level, the designer could choose the enemy type, adjust values such as health and attack damage, and even change the enemy’s appearance by selecting which texture it should spawn with, and decide whether the enemy should patrol or stand still.

Tonitrus Caelus – Last boss

For our game project, I was responsible for creating the final boss based on a design from our group leader, David. The boss had a lightning theme, two different faces/phases, and around ten different attacks.

I worked closely with David to make sure the boss matched his vision. I was responsible for all the coding and blueprints needed to make the boss work, including its behavior, attack patterns, and phase changes.Due to the boss having multiple attack systems, I had to ensure that they functioned together correctly without causing conflicts, bugs, or gameplay issues.

This task helped me improve my skills in gameplay programming, enemy AI, boss design, teamwork, and adapting my work based on feedback.

Normal phase

The boss’s normal phase had six different attacks that were chosen randomly during the fight. Each attack had its own logic and behavior, which meant I had to make sure they could all function correctly together without causing conflicts or gameplay issues.

During the boss’s normal face, if the player landed a five-hit combo, the boss would cancel his current animation or attack and fall backwards. This gave the player a clear reward for playing aggressively and successfully chaining attacks together.

Two of the boss’s attacks involved teleporting directly toward the player. These attacks also included a perfect parry mechanic, where the player could time their parry correctly to stun the boss for a short period. This added more depth to the fight and rewarded precise timing.

The boss also had one AOE attack that was intentionally easy for the player to dodge. However, unlike his other attacks, this attack prevented the player from knocking him back, which made it behave differently from the rest of his moveset (The player can’t block it).

The boss also had a Lightning Slam attack, where he slammed into the ground and created a lightning circle that followed the player. After a short time, the circle stopped moving, giving the player just enough time to dash away before it exploded(The player can’t block it).

The boss also had two additional attacks: a projectile attack and a spinning sword attack. During the projectile attack, he jumped into the air and fired three unblockable projectiles that the player had to avoid. During the spin attack, he spun his sword around while following the player, forcing the player to keep moving and avoid getting too close.

Intermission phase

During the intermission phase, the boss became invisible and the player had to focus only on avoiding his attacks. This phase included seven different attacks, which were mostly chosen at random. To improve the flow of the fight, I made sure the attacks could not repeat, while the third and final attacks were always fixed.

The boss has three different versions of a teleport attack, where he teleported to the player and attacked immediately. Each version had slightly different behavior, which helped make the phase feel less predictable and forced the player to react quickly.

Teleport projectile attack and a dash attack. In the projectile attack, the boss teleported in front of the player and spawned projectiles that the player had to block or parry. In the dash attack, the boss quickly dashed toward the player, forcing them to avoid it with precise timing.

Also included two fixed attacks that always happened at specific points. The third attack was always an unblockable ground slam, where the boss slammed into the ground and forced the player to dodge. The final attack was always a large unblockable projectile, which the player also had to avoid before the boss returned from the intermission phase.

Half Health phase

At half health, the boss entered a special phase where he spawned three crystals around the arena. During this phase, the boss became invisible, and the player had to use the bow to shoot down all three crystals before the fight could continue.

Final phase

In the final phase, the boss flew up into the air and spawned projectiles that the player has to avoid. Once the attack ended, he returned to the ground, giving the player the opportunity to finish the fight and defeat him.

Vhalgor Shadowbane — Secret Boss

Vhalgor Shadowbane is a secret boss designed to be a more advanced and challenging encounter for the player. Unlike the standard enemies, Vhalgor has several different attacks that force the player to react in different ways during the fight.

He has four main attack types:

  • AOE Attack (Area-of-effect attack that cannot be blocked)
  • Spin Attack (moves toward the player while spinning. Can block but not parry)
  • Normal Attack (Can parry, similar to the standard enemies)
  • Teleport Attack

When Vhalgor reaches half health, he can also spawn enemies into the arena. This changes the flow of the fight and adds extra pressure, forcing the player to deal with both the boss and additional enemies at the same time.

Cerberus — Boss Enemy

Cerberus is the first boss in the game. His AI was kept fairly simple, with attacks based on how close the player is to him.

When the player is far away, Cerberus can use ranged attacks such as spawning fireballs and fire circles to pressure the player from a distance.

When the player is close, Cerberus switches to close-range attacks. He can perform an AOE attack, spawn fire circles, and attack the player with his paw.

Animal AI — Wolf and Deer

I also created two animal AI types for the game: Wolf and Deer. These were added to make the world feel more alive and dynamic.

Wolf

The Wolf works similarly to the basic Goblin enemy, but with simpler combat behavior. It can detect and chase the player, but it does not call nearby enemies for backup.

The Wolf can also interact with the Deer. If a Wolf sees a Deer, it can chase it and kill it, creating small dynamic moments in the world without player involvement.

Deer

The Deer is a non-combat animal mainly designed to make the environment feel more alive. If the Deer sees a Wolf, it will avoid it and try to move away. It will also walk away from the player if the player gets too close.

The Deer was not designed to be a combat-focused AI, but instead to add life and movement to the world.

The animal’s also have a spawner like normal enemies, it’s built on the same basic idea as the camp spawner, but it is simpler in structure and behavior. Its purpose is to handle animal spawning without the extra logic used by camps.

Conclusion / Reflection

I am proud of how much I was able to create during this 5-week project. I worked on a lot of different systems, especially enemy AI, and many of them are shown throughout this portfolio.Creating several enemy types, bosses, animals, and supporting gameplay systems in such a short time was a big challenge, but it taught me a lot.

I was happy with the BaseEnemy class written in C++, since it worked well as a shared foundation for enemy stats, detection, health, and core functions. However, I was less happy with how I structured the BaseEnemyCharacter class for the standard enemies. Because that class was not planned well enough from the start, the standard enemy Behavior Tree ended up having too many branches, which made the AI logic harder to read and maintain.

I also had to copy and paste some code for the Wolf, because it was separate from the BaseEnemyCharacter class. This worked for the project, but it was not the best solution. If I were to redo it, I would structure the shared enemy functionality better so that enemies and animals could reuse more of the same systems.

Around week 3, I considered reworking the enemies to create a better structure, but decided against it because of the limited project time. Looking back, I think reworking it might actually have saved time in the end. I also think I could have improved the project by focusing on fewer enemies, for example cutting the first boss or the Wolf, and spending more time polishing the remaining enemies. This taught me an important lesson about quality over quantity.

In the end, this project helped me understand much more about how to structure AI systems, base classes, and reusable code. I have already started using what I learned in newer projects, where my enemy AI is much cleaner, better organized, and easier to expand.