Nickolas Rau

Game Developer | Programmer | Designer

Can You Survive 10 Waves?

Project Image

Game Statistics

  • Engine: Unreal Engine 5.3
  • Platform: PC
  • Genre: Third-Person Shooter
  • Team Size: 3
  • Time Spent on Project: 2 weeks (165 hours)
  • Target Audience: Call of Duty Zombie Players

Overview

This is a third-person shooter survival game completed during an apprenticeship for Prosper IT Consulting where I participated in Agile/Scrum practices by completing user stories to deliver a functional MVP game in Unreal Engine version 5.3. This turned out to be an amazing proof of concept that I will likely continue learning and iterating on in the future. It was aimed at a target audience of former Call of Duty Zombie players where we saw play sessions exceeding 5 hours on twitch. I was fully responsible for every aspect of the game's development including concept, planning, design, animation, and programming. Nearly every feature/mechanic included had to be referenced from Unreal's documentation, forums, or tutorial guides as this was my first game in Unreal Engine. The project is nowhere near perfect and many times I just needed to get something working to move forward. Completing this game, however, taught me better development skills which I will continue building upon.

Table of Contents

Game Design

Outcome Objectives

  • Identify the fun and replayability that come from close calls and weaving between enemies. Create as many of those situations as possible.
  • Deny the player initial success or put the player in stress multiple times on first playthrough. Player should experience the quick ability to die or redscreen. Adding intrinsic meaning to surviving.
  • Allow for spawn loctions to change every wave with an element of randomness. Keeping players on their toes and changing where their attention should be focused depending on where the enemies are coming from.
  • Account for gameplay mastery. Include progressive scaling for waves so when the player masters the movement, pathing, and enemy attack patterns, they can continue past wave 10 until death in attempt to survive high waves.
  • Accomodate different playstyles or player strategies by having options for buyable objects and upgrades. Player can choose between doing more damage, moving faster, or having more health.




Image 1
Image 2
Image 3
Image 4
Image 5

Breakdown

 I began with white boarding the set of outcomes I wanted the player to experience. I knew the target audience was already familiar with the concept of hoarding up enemies and killing them so I wanted to innovate on difficulty parameters while also creating as many close encounter situations as possible.

Core Gameplay

 Most important was to understand how these situations are created. To the right, I attempt to illustrate that maneuvering around enemies is significantly more impactful when:

  • Enemies come from multiple directions
  • Enemy paths overlap with player direction
  • There is enough spacing between some enemies for player to move through
  • Playable area is contrained enough to force the player to traverse through the spaces between enemies or die
Project Image


Project Image

Spawn Design

 At first I designed a scenario where there would be spawns that move into positions each wave to “set-up”. Similar to vehicles moving around the parameter and “parking.” This might be beneficial in the future but would have required too much effort for a concept I first needed to prove.

 I simplified the system by having static spawns around the map that would randomly “activate” for the wave. Iterating through play testing I added directional sounds, particle effects, and glowing material to the spawns to help telegraph to the player where the enemies would be coming from.

Level Design

 I designed a map for the player that took into account open areas, narrow paths, and item locations.

  • Identified main traffic areas for the player and placed spawns that intercepted them. 1-2 interceptions for narrower (1-way) paths and more interceptions for the larger area
  • Placed item interactables in locations depending on interaction frequency. For example, ammo and health are going to be bought more frequently than revive or weapon upgrades. Therefore they should be where the player will spend most of their time, while also have the risk of being between enemy spawns

Placeholder Full-Width Media

Gameplay & Currency Balancing

 I began with outlining wave-specific goals I wanted the player to experience. Specifically:

  • Wave 3 - First Purchase
  • Wave 4 - Second Purchase
  • Wave 5 - Third Purchase
  • All Purchases Complete Before 10

 With these in mind, I tailored the gameplay experience. Through hours of playtesting and iteration I determined exactly how many enemies to spawn, focusing on their health, speed, and spawn rate for each wave. Often, initially doubling the number so the player could instantly see the increase without any discrepancies.

Project Image
Placeholder Full-Width Media

 After clearly defining the amount of enemies along with their health, it became much simplier to calculate the minimum amount of points (killing enemies without headshots) the player will have after each wave. This gave a baseline to price buyable objects to meet the wave-specific goals. Notice above how a price of 2500 seemed fitting for the first item since the minimum amount of points gathered would be 2660. By making all buyables initially 2500, the player then has meaningful choices to make. Also, if the player prioritizes heashots and doesn't buy as much health, they will be rewarded by getting upgrades faster and thus making survival easier.

Tweaks & Balancing

 Through iterative playtesting I refined most of the gameplay experience:

  • Introduced obstacles to the main area to increase difficulty and limit exploitable space near the ammo buyable.
  • Added a movement speed buyable to expand player choices alongside weapon and health upgrades.
  • Revised map boundaries to remove invisible barriers
  • Adjusted starting ammo to ensure the player wouldn't enter a state where they buy health and can't afford ammo with an empty weapon.
  • Capped maximum active spawns to 6 so the player would always have options to navigate, balancing difficulty
  • Balanced price of revive to ensure player could always buy the max amount before 10. It initially cost more skulls than could be obtained.
  • Tuned enemy health on wave 7 to only increase by 25 to align with shorter time-to-kill that helped players acclimate to the max speed increase.
Project Image Project Image

Programming

Shooting

 I worked with the weapon skeletal mesh to create animations for a firing sequence as well as attaching it to the character mesh by adding sockets. From there I used blueprints to write functionality for firing when the shoot button was pressed. Once pressed, I played the animations and called a custom line trace function I wrote. The line trace would add variability depending on if the player was aiming or hip firing. Then, it would check the surface material hit to spawn a particle in for the matching material type.

Media 1

Shoot Logic

Media 2

Line Trace

Media 3

Break Hit Results

Image 1
Image 2
Image 3
Image 4
Image 5

Problem Solving - Line Trace Alignment

 Since I initially had the start position of the line trace at the muzzle of the gun, there would be many situations where traces towards closer objects would hit without the crosshairs being over it. This wasn't telegraphed well to the player and I personally found it frustrating when missing enemies because they were too close to me. To fix this I calculated the center point of the screen and projected it to world space for the start position. With that, the trace would always hit what the crosshair was hovered over, eliminating any discrepancies.

Quality of Life

 Through playtesting I found gameplay to feel clucky with having to manually hit the reload button when the clip was empty. So I added a call to my reload function if the ammo was empty and the player still held down the button.

Project Image

Reloading

 Had to do a lot of debugging to ensure the player reloads the certain amount of ammo correctly. I found there were certain edge cases initially where they player would lose more ammo than intended. This was solved by always calculating the amount of ammo used in the clip before reloading. This allowed successful reloads without unintentional or missing ammo. All variables regarding ammo can be changed and this still functions properly.


  • Added interface functions (Event Refill Ammo) that other actors could call to refill ammo
  • Smoothed gameplay flow by checking to see if the shoot button was being held at the end of the reload to automatically call shoot. Before this, players would need to manually press shoot after each reload.
Project Image Project Image
Image 2 Placeholder Full-Width Media

Lesson Learned

 The way I called the animation function worked but if I had to do it again I would try having all the logic on the reload animaton itself. I would use a notify on the frame the magazine is put into the gun to call a function that refills the ammo. That way if I wanted to change the reload speed, I would only need to change the animation playrate. Currently, I have to change both the playrate and the delay node when I want to alter the reload speed.

Interactable Objects

 I initially casted to the third person character on event overlap. However, I learned how costly that can become so I implemented custom interfaces to display text to the player's HUD on overlap events, regardless of the player's screen direction. This aligned more with the flow of the game and ensured the player retained their momentum. This is different than most interactable systems where a line trace is used to detect if the player is looking at the object.

Image 1
Image 2
Image 3
Image 4
Image 5
Image 6
Image 7
Image 8
Project Image

Overlap Event

Each object had their own variables and custom text, then when an actor that implemented the correct interface overlapped with it, it would call the display function.

Buy Event

Buy was called if the player pressed F and the canBuy function returned true.

  • The health buyable to the right would initially cost 2500 and then check a bool to change the price to be 300 for every subsequent purchase.
  • The weapon tierable upgrade, below, kept track of an integer value representing the current weapon level. As it increased, the price, text, and damage would change. I did this thinking multiplayer could allow for strategy where players upgrade different items for the team that persist throughout the game session.

Project Image
Project Image Placeholder Full-Width Media

Enemy

  • Imported and blended enemy animations
  • Coded enemy logic to trigger attack on set proximity to player
  • Added custom physics materials to apply damage according to location hit
  • Wrote the logic for the enemy to take damage according to location hit and give double the points if killed with a headshot
  • Added arrow component to enemy to spawn a sphere trace during attack animation, looping through all hit actors and applying damage
  • Trigger ragdoll effect and spawn collectable currency on death

Media 1

Enemy Attack Logic

Media 2

Take Damage Logic

Media 3

Enemy Die Logic

Image 1
Image 2
Image 3
Image 1 Image 2

Wave Logic

  I wrote a function that would loop through all spawns available in the map and randomly select the amount to activate (6 on wave 6). These selected spawns were then added to an array, and after the "Wave Incoming" text disappeared, would loop through each one playing a sound with partcile effect.

Difficulty Setting

To achieve a fast, noticeable, scaling of difficulty that allowed for rapid changing to variables during iterative play testing, I built a function that would switch the wave number and set all difficulty parameters. This allowed for Individual tweaks to difficulty while also setting a linear progression after wave 7.

Image 3
Image 3

Spawn Enemies

I wrote spawn logic that would be called at the start of each new wave, and check to see if more enemies needed to be spawned after one died. To do this I kept track of the total amount of enemies in the wave as well as the total amount to spawn in at one time. Then to increase the difficulty I promoted the delay between each spawn into a variable that is modified in the wave logic.

Problems Encountered

  I ran into significant issues trying to implement a delay using a for loop. When a wave started, all spawns would activate at the same time. Through debugging and researching online, I found out that Unreal Engine’s node macro iterates through all elements of an array and then outputs. This was a problem if I wanted to add a delay with sound and particle effects after each element so that the player would have proper time to prepare. To achieve this functionality, I implemented a branch with a counter set to the size of the array holding the spawns to activate.

Image 2 Image 1

Improvements

  • Utilize object pooling for collectables and enemies
  • Use event dispatchers to communicate to the game mode blueprint when an enemy dies instead of casting
  • Write a custom macro node for delays within loops
  • Integrate spawning logic and difficulty equations into C++ as things get more advanced
  • I tried to get as many nodes in a screenshot as possible, but in the future it would be best to collapse the logic down for clarity

UI / HUD

Overview

  • Anchored all HUD elements to the player viewport
  • Created bindings with event graph functions to update HUD based on player state
  • Designed custom UI animations for main menu buttons with scripting to update player settings
  • Near death HUD animation to play on low health
  • Built menu level with enemy background scripting
  • Imported and scaled custom font

Image 1
Image 2
Image 3
Image 3
Image 3
Image 3

Main Menu Scene

Building the main menu scene was particularly fun for me as I got to be creative by coding enemies to go by the window outside the player. Coded to have random variations so it doesn't feel as scripted, gives a nice feeling of immersion on the home screen.

Image 3
Image 3

Retrospective

What went well

  • I was able to quickly adapt to Unreal Engine concepts, successfully self-teaching every feature implemented in the project.
  • Refactoring features like the interactable system using blueprint interfaces went extremely smooth and solidified my understanding through repetition.
  • The initial design was precise enough to meet the intended player experience through gameplay iteration.
  • Delivered a minimum viable product with replayable proof of concept in 2 weeks.
  • Onboarding process into Prosper IT Consulting development team went flawlessly with no slow downs and meeting all Agile/Scrum expectations.
  • Received well among target audience resulting in play sessions exceeding 5 hours on twitch.

What went wrong

  • This project was done right as Unreal Engine migrated everything to fab and it couldn't have been more of a mess. I'm hopeful the kinks will get straightened out in the future but I experienced many bugs, loss of content that was originally in my marketplace, numerous crashes, and lag. Couple hours of development time lost.
  • Committing changes went well at the beginning but started getting stashed changes on the master branch that I had to discard every commit.
  • Wasted too much time trying to find the "best way" of implenting some features early on.

What I learned

  • Save more often, Unreal Engine crashes. Especially when using Fab.
  • Look things up faster when I don't know something, be less stubborn in trying to figure it out myself.
  • Test more frequently. Every small change should be tested before moving onto another.
  • To have better naming conventions. Things start to get out of hand pretty qucikly when importing multiple assets from online repositories.
  • Get something working first before worrying about its efficiency. Development time was lost caring too much about "the best way" of doing something when I should have just got something working first.
  • Comment more. I found myself wanting more comments when going back to something I worked on a few days earlier. It would have been helpful to get me back into that heahspace again.
  • If importing more than one asset at a time from Fab, it is better to import it to a blank project and migrate the asset to your working environment.
  • Close out of Fab and restart Unreal Engine after importing.
  • Better communication methods like blueprint interfaces. I was able to go back through all interactable objects and decouple casting references. Moving forward I will be able to further improve upon this by using a combination of event dispathers.
  • Don't have Unreal Engine open while you are commiting/pushing code to the repository.