Spite: Curse of Archibald
Timeframe: 14 weeks (20h/week)
Team: 5 programmers, 3 designers, 5 artists
Engine: Our improved version of TGE, the school's in-house game engine made in C++ and DirectX11
Using an entity-component system library EnTT.
Inspiration: Diablo 3
Contributions
Enemy AI
Consists of a finite-state machine with the following behaviors: Idle, Attack, Seek, and Separation. The movement solution is based on steering behaviors. Using the same pathfinding algorithm as the player, A*.
Movement
The enemies' "flocking behavior" was made by checking the amount of overlap each enemy had with one another and interpolating the result with the position of the player.
Combat
The combat portion of the enemies required a lot of tweaking and iteration regarding different ranges, angles of attack, smooth rotations in-between attacks, and general timing with the attack animation and damage output.
Rat AI
Blended behavior of Wander, Obstacle Avoidance, and Player Avoidance. The rat's were a stretch goal made during the last couple of weeks of the project. This was also my favorite part of the project for me. I learned a lot during the implementation.
Behavior
They are in a static idle state until the player comes within their detection range. At this point, the rats scatter in randomized directions. Once they've traveled too far away from their starting position, they return.
Challenges
For all of our pathfinding, we are using a navmesh. You can either be on the navmesh or not. However, because the object we want to avoid lacks geometry, (everything outside the navmesh) it is difficult to extract the necessary information for obstacle avoidance. To determine whether a path is valid or not, the rats must sample their next position. But what happens when the selected position is not valid? I provide a more detailed explanation of my solution at the bottom of this page.
Player Combat Mechanics
The combat I've implemented for the player consists of a regular melee attack and a second single target that deals more damage.
Like the enemies, player combat was all about tweaking distance, rotation, range, timing, etc.
I created a Timer class to time damage output, ability durations, and ability cooldowns.
The abilities are locked at first and gradually unlock after each level is completed.
Axe Hammer & Speedbuff
Axe hammer slows all enemies hit in a circle around the player. All slowing effects were applied through the same function, which deals damage to the enemies.
The speed buff accelerates the movement and attack speed of the player for a set duration.
Upon reflection, I realize the effects (slow and speed) would have been better implemented as components, offering improved modularity and reusability.
Throwing Axe & Whirlwind
I am mentioning these two abilities because they both consist of components that manage their lifespan and logic.
The throwing axe component is created and emplaced onto the player, along with the required data, such as the player's forward direction and the applied force. Following that, the Axe has its own update loop, managing its rotation, velocity, and self-destruction.
The whirlwind deals tick damage each second and applies a knockback to all enemies hit. Knockback is a component that "warps" or pushes the enemies away from the player each frame. The knockback component samples the next position to ensure that the enemy is not pushed out of bounds or into an invalid path.
Technical Details & Closing Thoughts
This project was my first time using EnTT, an entity-component system. I really enjoyed the workflow that came with it. Components made with structs, functions in namespaces, each function iterating over their respective component. The accessibility of everything was effortless, and the iteration of abilities went smoothly. Overall, I could have utilized components more.
______________________________________________________________________________________________________
Regarding the Rat AI, I developed a solution that involved using the normalized unit vector "awayFromPlayer". By extracting both normals, I was able to determine a "right" and "left" direction relative to this vector. However, I discovered that if the rat was in close proximity to the player, it would attempt to move away as intended, but this posed a problem when the rat became trapped against a wall with the player nearby. If the separation vector was too large, it could cause continuous out-of-bounds sampling and make it impossible for the rat to find a valid path. The answer I had to this was dynamic weighted blending. Change the magnitude of the separation vector dependent on the amount of overlap the rat has with the player. To avoid division with zero, I set a default value.
©2023 by Viktor Rissanen Resumé LinkedIn rissanenviktor@gmail.com