Rivet

Project summary
Developing Rivet was part of a course that I studied in spring 2024 where I and six other students collaborated to create the final product.
This was my first introduction to Unreal Engine 5 and I was responsible for the enemy behavior as well as general programming for the game.
Tools
Engine: Unreal Engine 5
Language: Blueprints/C++
Team: 7 people
Overview
Rivet is an intense first person shooter where you battle your way to the top of a futuristic arena, facing off against waves of robotic enemies with different abilities. As you fight through each round the enemies grow stronger not only in power but also in numbers.
To survive and prevail you will have the ability to upgrade your character with the currency you earn each fight. These upgrades can enhance your firepower, defensive abilites and utility.
To show off your skills in how far you came in the arena you are rewarded with a wide variety of weapon skins that are unlocked at certain rounds.





Enemies

As we formed the game idea we came up with three different types of enemies.
-
A large tanky enemy that would chase after the player and make them avoid close confrontation.
​
-
A range enemy that shoots projectiles at the player and stays out of sight.
-
A small enemy that would explode on contact with the player but has low health.
Gorilla
The gorilla was the enemy designed to make the player avoid close confrontation. This was made possible by the large health pool so the player couldn't just brute force their way through the gorillas in the arena since it would need a lot of shots from the player to destroy them.
To make the gorillas more varied they could get different personalities when they are spawned in the arena. The first one is called charger and it would chase the player and commit a ground slam to deal damage to the player when they are close enough. The second one is called spawner and it would run around the arena and spawn the small explosive enemies (more on that later when talking about that enemy). The last one is called thrower and it would pick up and throw a small explosive enemy towards the player.



Charger
The first iteration of the charger-gorilla included that it would follow the player and then strafe around them in order to not make them get too close and sort of suffocate the player.
The problem with this was that if there were multiple gorillas close to the player they would collide with each other and it would just come across as kind of chaotic.
To solve this problem I created the behaviour for them to chase the player and then slam into the ground after a charge up when they're close enough and skip the strafe part completely. By making the gorilla charge up their slam it would create an opportunity for the player to avoid them and the gorilla would never be directly on top of the player.
It then created a sort of problem that it was way too easy for the player to avoid the slam if the charge up was too slow. To solve this I created the behaviour for the gorilla to face towards the player when it does its charge up. This made it so the gorilla became more dangerous and not just a walking wall that required no strategy to defeat.
.png)
Spawner

The spawner-gorilla would run around the arena and spawn the small explosive enemies based on a timer. This creates a certain urgency for the player to quickly destroy these types of gorillas or they will be overrun with the smaller enemies.
By using the Environment Query System (EQS) in Unreal Engine I could create a sort of pattern for the spawner-gorillas to keep their distance away from the player when they run around the arena.
Thrower
The thrower-gorilla would run around the arena and pick up the small explosive enemies and throw them towards the player. This was to make the enemies interact more and force the player to keep a lookout for the incoming airborne enemies.
This was made by implementing a sort of partner system that the gorilla would use. They would search through all the available explosive enemies and partner up with the closest one, but if the explosive enemy is too close to the player they will deny the partner proposition. When the partnership is set the gorilla will call on its partner to move towards them, and when they meet up that gorilla will throw the explosive enemy in trajectory towards the player.
.png)

Star

This was the enemy that was designed to be a small and to overrun the player by its numbers. When they come close to the player they will begin to charge up and then explode, dealing damage in an area around it. Since they only required one hit to destroy they are significantly faster at chasing the player than the gorillas.
The first iteration was that the stars walk right towards the player, but in later versions the different stars in the arena would collaborate and flank the player thus creating a sort of ambush behaviour.


Roly Poly
This was the enemy designed to keep its distance and shoot projectiles at the player. When confronted by the player it would roll up into a ball and flee to a certain point away from the player. This was made with and EQS to check a certain distance away from the player and move towards it.
This created a sort of incentive for the player to chase a roly poly since their attacks could deal a lot of damage if the player didn't dodge.


.png)


The first iteration made was that the roly poly would shoot at the players position. But I quickly realized it was way too easy for the player to just move a little and they would never get hit.
The solution I came up with was to give the projectiles a sort of homing mechanic so they would track the players movement and lock its trajectory towards the player. The variables that decided the homing strength and time it would be in this state could be changed to match the difficulty of the game.
This made it more difficult to dodge and created a dynamic in the game where you had to either focus the range enemies down or beware their projectiles while you fought the other enemies.
Object pooling
Since the game mainly revolved around destroying the different enemies and then spawning in new it made it clear that it would be way to expensive to destroy and create the enemy actors in the scene.
​
My solution to this problem was to create a general object pooler that would take the "destroyed" enemies and placing them in another part of the map. When I then needed to spawn in new enemies I could just simply take the already existing ones in the object pooler and reuse them, thus improving the performance of the game.
What I did was create a subsystem in Unreal Engine that would be inherited by each enemy. Two functions were created that would be called either when the enemies are spawned in by the object pooler or returned to the pool.
When returned to the pool the enemies would stop their AI-logic, stop all animations and reset their health to be ready for next time they spawn in.
When spawned from the pool their AI-logic and animations would start again.
.png)
.png)
What I would have done different
Something else I would have done differently is to implement more things in Blueprint that the different designers could use, for example, when an enemy attacks or dies. Initially, I implemented it in C++ when they hadn’t yet finished their animations/sound effects, but once they were ready, everything needed to be linked together somewhere. This meant I had to transfer a lot of things from C++ to Blueprints, which slowed down their work a bit because they wanted to test their stuff.
Another thing I would have done differently is to create my object pooler right from the start. It was created a bit late in the project, so I had to convert a lot of the enemy spawning system, and it would have saved me some time.
​
Right now, the gorilla decides what type of enemy it is when it enters the game, and it constantly checks in the Behavior Tree which path to take, whether it’s a spawner, charger or thrower. It would have been much better to just create a separate Blueprint class for each type of gorilla so it wouldn't have to constantly check what type it is. It might be possible to convert it now, but at this point, it feels so embedded in the system that it would have been much easier to do it that way from the beginning!
​
I also spent quite a bit of time on a "squad system," which I thought would make enemy behaviour more interesting. The idea was that each enemy would belong to a squad, and they would work together to attack the player. If an enemy was alone in a squad, it would join another squad. But this didn’t turn out the way I imagined, and it felt odd when I started testing it. I spent a lot of time on it, and it would have been better if I had just gone with the final system from the start, where enemies manage themselves. The squad system isn’t a bad idea, but I think I would have needed more time to make it a good implementation in the game
