KILONOVA
About the game
In this action-packed third-person combo combat game, you take control of Nova, on a quest to stop the evil Kilonova from enslaving and consuming a planet. Battle through waves of possessed creatures as you fight your way up to the Kilonova. Do you have what it takes to save the planet? Or will you fall victim to the Kilonova's power?
​
Kilonova was my Junior year game project and my third time working on a game team at DigiPen Institute of Technology. Our team was comprised of 16 members: 6 programmers, 6 artists, 3 designers, and 1 sound designer. This project itself was created in Unreal 5 and I was responsible for the AI aspect of the project. This included animation graphs, enemy prefabs, behavior trees, navigation meshes, spawning logic, and wave logic as well as assisting designers in balancing enemy behaviors and encounters. Additionally, our team implemented a Task Force structure in which I was the AI Task Force Lead, responsible for tracking tasks, assigning priority, and communicating progress to production and other departments. The AI Task Force itself had 2 artists, 2 designers, and myself.
​
This project saw me taking on a lot more responsibility than I had previously experienced and forced me to take on some new roles in the process. For one, this was my first project in which I was officially an AI programmer but I was the only one on the team and I was also in a position of leadership on top of that. This was initially intimidating for me, especially being directly responsible for coordinating other people's work, but I welcomed the new challenge and came to enjoy and excel at my new roles.
Enemy AI Logic
One of the first systems I created for this project was our behavior tree architecture. I had previous experience working with behavior trees in Unreal 5 so I was fairly familiar with the basic structure of a behavior tree, but my biggest challenge was figuring out how to translate that logic into C#. Eventually, I created classes for trees, blackboards, and nodes, all of them abstract classes. The reason I opted for abstract classes is so that the base classes themselves could not be accidentally instantiated in the game. This also led to abstract functions being created for many of the base functions (like initializing the tree, creating the blackboard, constructing a node, etc.). This approach allows for an additional layer of protection when compared to using virtual functions since the lack of a default implementation means that each override is unique.
In my implementation, the behavior tree class is fairly simple. It holds the tree itself and controls the flow of the nodes as well as connects them to the blackboard. The actual tree logic itself can be created in either a single construction call for the root node or by adding leaf nodes onto an already existing root node. The simplicity of this class allows for behavior trees to be easily built and the complexity to come from the content of the nodes and how the tree is structured.
The blackboards for this architecture allow for variables to be added either via script or through the inspector window in the Unity editor. The blackboard supports many standard variable types (ints, floats, strings, bools, Unity GameObjects) and other variable types can be implemented as well in new child class instances of the blackboard. Variables can be edited in the inspector tab through the blackboard's custom dictionary structure, allowing for new variables to be added easily.
Behavior tree nodes have three major variables: state, parent, and children. The state determines if this node has returned success/failure or if it still needs to be run. Nodes also have functions to run logic on enter, exit, and interruption. Enter and exit logic are called each time the tree runs that given node, allowing for checks to be made (for example, is the player still within our attack range? Is this the first time we've run this node? etc.). The interrupt logic is run whenever the tree has to interrupt a node and it has not returned success or failure yet (for example, the player has now gotten within attack range of the enemy so the tree must interrupt the current behavior to run the attack sequence earlier in the tree). This allows the node to have a way of pausing/stopping/resetting itself if it's interrupted rather than potentially breaking the next time it is called.
Cooperation with Designers and Task Forces
I wasn't just creating behavior trees and animation graphs, however. I was also working closely with designers to help balance enemy encounters and behaviors as well as creating tools to help them efficiently test and make changes. One such tool was for creating and modifying enemy encounters. When I was creating the spawning system, I asked the designers about any useful aspects they may want to change with regard to the spawning system. I took their requests and made those aspects not only readily available but easy to modify and understand. I utilized a list of transforms to allow them to manually place enemy spawns throughout the arena. The list of enemies to spawn was also directly tied to that spawn list, meaning that they could easily control which enemy spawned at which spawn location. There was also support for changing the duration between each wave of enemies as well as controlling how many enemies would attack the player at any given time.
​
For this project, we had milestones every month. The first week of our milestones was dedicated strictly to Task Force planning and coordination for the upcoming milestone. It was during this time that I would recap all we had accomplished to production and the rest of the team as well as coordinate and prioritize our future tasks. I was never limited to just the members of my Task Force, however, since enemies were intertwined with just about every other aspect of our game. I would often spend this time planning with the Character Task Force lead about how each of our tasks would affect one another and coordinate when certain features would be complete and ready for testing. It was during these meetings that I truly began to understand the value of not only knowing what people in your department were working on, but what people in other departments were working on as well. A good example of this was implementing knockback. Originally, I was planning on implementing it on my own but I realized the Character Task Force also needed to implement a version so we worked together to create a singular knockback function that could work for both players and enemies.