CAGD 470 Group 1, Sprint 2
- Els Fouche
- Feb 18
- 6 min read
Kill Everything As Fast As Possible
Kill Everything As Fast As Possible is a 3D first-person boomer-shooter where you take control of a killer AI hell-bent on destroying every human in sight.
Project Credits:
Daniel Bocanegra-Ramos: Lead Designer
Jack Bradford: Producer
Dylan Brown: 3D Artist
Els S. Fouché: Lead Programmer, Level Designer
Sean Gibson: Programmer
Sophia Villenueve: 2D Artist
Rayen Yousfi: 3D Artist
Gameplay Feedback
We were unable to playtest the movement systems during this sprint and prior to beginning the next build. This is unfortunate because it means that our current round of playtesting needs to assess multiple different variables simultaneously. This is likely to muddy the resultant data - not to the point of unusability, but it may make it more difficult to tease out potential causal relationships between specific mechanics and player statements.
For the current build, I began work on feedback systems for the player. I created a player HUD widget to act as a wrapper for the many sub-systems that compose it. As of this writing, the HUD holds:
the player's health display,
the armor display (as an overlay on the health),
the player's dash ability cooldown, and
the player's current amount of currency.
The dash ability cooldown is currently two complete circles. The design has recently been updated to instead be two abutted half-circles - this will be implemented in the next sprint.
In order to test the player's health system prior to the enemy system being complete I created a debug actor that dispatches the same type of damage event to the player as the enemies use. I made this system modifiable during runtime to allow for different damage rates and to enable or disable it. I decided on an automated rather than manual input system so that I wouldn't need to manually press buttons to initiate damage, enabling me to test the movement systems while being harmed. This was done as future-proofing for polish elements like hits stun and camera shake.
Note that the collection radius on the health pickup is quite small. This is designer-adjustable and has a toggleable visualizer. The pickup to be dropped from the destructible object is selected from that actor for ease-of-use. I've been trying for a while now to find a good reason to use primary data assets - it seems every time I think a PDA is the best tool for a given situation there's other data structures that are more suitable. I've implemented the pickups as data assets which seems like a fine use case...but a data table and single actor would have resulted in lower memory overhead. The search continues.
The damage flash was implemented very quickly. It didn't occur to me to do a directional damage indicator initially - the team only reached the conclusion that it was necessary after the designer played the game with enemies implemented. I'll be modifying the damage flash to reflect the direction of the damage-causer in a future sprint.
The pause menu was deceptive. I've been doing a lot of reading about Unreal & general UI best practices lately and trying to incorporate those principles into my approach. A pause menu can be quite simple but enabling the player to access the settings menu from the pause screen made it a bit more interesting.
During the first sprint I implemented menus extremely quickly in order to have a playable build ready asap. When I was asked to revisit the pause menu this sprint I took the opportunity to discard the bespoke menu solutions I'd been using in favor of the best practices I'd been teaching myself. These fall into a few categories:
Template objects,
Composition of widgets, and
Layer managers.
Template Objects
Every button currently in the project references a singular button template. This means that only a single object needs to be changed instead of having to go through every single widget to update the button's style once art assets have been finalized. I was attempting to do this for text as well to avoid having to update every text object's font later on, but I haven't yet figured out how to do so.
Widget Composition
This leads into the second bullet above - each widget is composed of other widgets. This modular approach has the benefits mentioned above and is definitely the 'secret sauce' I'd been straining towards when doing UI in the past. As I mentioned above regarding the player HUD, I've wrapped all of the functional pieces of the HUD into a single larger wrapper. This wrapper then provides access to the child widgets it holds to expose functionality held in those children (e.g. decrementing the player's healthbar).
Layer Managed Widgets
Finally, the last point refers to an approach I've recently learned about that adds one more layer of abstraction to the way UI is handled. By wrapping many different menus that should all be accessible from one particular game state into a grouping widget it becomes significantly easier to switch between those menus as needed. This does need to be done thoughtfully in order to prevent loading children widgets into memory that aren't needed, of course, but it's worth the additional planning required due to the decreased room for error that a layer-managed system provides.
Stat Tracking
A large portion of this sprint was devoted to enumerating, tracking, and storing player statistics. As part of this I wrote a library for handling time. Specifically, this project features a timer that counts down and is tracked in the player statistics as the amount of time they've survived. The DateTime structure that exists in Unreal currently tracks a large amount of data including days, months, and years. I created a paired-down structure that only deals in minutes, seconds, and milliseconds and added library functions for converting from that structure to integers (both 32 and 64 bit) and back again. This allowed me to store time as a single int64 and made the data easier to operate on as compared to DateTime.
Regarding data, the player's stats are currently tracked in two places as three separate instances of the player stats structure. The struct held in the player's blueprint represents the values for their current run. (As an aside, this data is not held in the player state because this game is not intended for networked play.) Their are two versions of the struct held inside the save game object which represent the player's single-run personal best and their totals across the lifetime of the save. These stats are currently tracked:
Experience points,
Kills,
Headshot kills,
Shots fired,
Shots hit,
Headshots hit,
Blood*,
Time survived, and
Time earned.
Note *: Blood is the player's currency.
Accuracy is not tracked directly but is displayed to the player as derived from the stored shots fired vs. shots hit.
Level Design
I was also given the chance to do some level design this sprint. In order to have a prototype ready for playtesting I was asked to create a testing level with enemies for the player to run around in. This was a very free-form task - I endeavored to create an interesting arena for the player to maneuver around.




Navigation
Adding navigation to the level turned out to be more challenging than initially expected. We're utilizing a persistent level -> sub-level paradigm for this project. Levels are streamed in from the persistent level while the player is within the bounding volume assigned to the level. I implemented this paradigm because it enables actual loading screens rather than pseudo-loading screens which seem distressingly common if Youtube tutorials are anything to go by.
The issue I encountered with the persistent level approach is that the navigation information is, as far as I've been able to tell, discarded when the sub-level is loaded. Currently the navigation system is set to populate dynamically at runtime in order to generate the navmesh when the sub-level streams in but I foresee significant performance problems with this approach. I'm going to continue searching for a better way to implement the navigation system in future sprints.
Summary
This sprint did not result in as much progress as I was hoping for. Overall it went well, but I feel that their were more critical features I could have focused on implementing e.g. additional weaponry types rather than the player statistics system. Additionally, the fact that we didn't do any formal playtesting for the movement systems during this sprint was unexpected and very unfortunate. The project as a whole appears to be on track but I'm hopeful that we'll be able to focus on the core game pillars in future sprints.

Comments