Scrapload is a first person tower defense developed over 5 months with a team of 8. You play as a lone space engineer, defending your station's core from incoming waves of enemies. Construct turrets and other support structures to build up your defense against the invading robots, while stunning them with your gun to slow them down.
There are a fair few different systems that were developed within this game to make it work. Luckily many of these systems were intertwined, but required me to be mindful of how I was passing and storing the relevant data. Throughout the project, my main responsibilities were focused on the gameplay programming, where I was responsible for:
Building system
Player controller
Upgrade system
Tutorialisation functionality
Radial menu & UI feedback
Environmental features
The biggest goal of the building system was ease of use; I didn't want players to feel hindered every time they decide to build. So I decided to limit the mechanics of it to only placing objects or rotating them. This helps to streamline the process, so building during the limited time in the prep phase or while under attack during an enemy was efficient to do. Early on, we decided to follow a grid based placement system with position snapping to the prespawned tiles within the grid. This decision came mostly to remove the extra decision time it takes when doing free placement, but also as a way to limit where the player can build as a way to minimise unforeseen issues from arising.
Once an object is placed, another is spawned with the same rotation and position applied. This approach was decided on as a way to further streamline the building process, where I noticed through playtesting that players 95% of time wanted to place their second building in the same orientation as their previous placement.
Placement and rotation of turrets
Placement Checks
Since we are using grid tiles for building and our buildinable objects take up more than 1 tile, I was faced with a challenge of how to determine if a placement is valid or not. When an object is placed, the ‘owner tile’ (the tile which the object is snapped to) is set to ‘occupied’ and then checks its surrounding neighbours to see if they are now occupied too. This is done via raycasts to check for any objects that might be on it. If it hits the object, the tile is set to ‘occupied’ and the process is repeated until no more tiles are marked as 'occupied’. If at any time throughout this process a neighbour tile is occupied and also hits the building object, the placement is considered invalid and won't be built. When scrapping an object, the same recursive process is followed, but marking the as ‘unoccupied’ instead.
Raycast to check if a tile is occupied
Structure Radial Menu
To compliment the ease of use when building, we introduced a radial menu for selecting objects to build. Since navigating menus can be slow and clunky, choosing to use a radial menu instead aligns more with our goal of ease of use. The radial menu was constructed through the use of a data table to help streamline the development process, where any adjustments and changes could easily be made in one spot. You can read more about that here if you are interested!
I built the player controller as an input based controller as I found that this would allow for more precise and responsive movement. One of the challenges when creating this controller was immersing the player into the game, as it felt very static to move around the world. To address this, I made use of camera manipulation techniques such as camera shakes for footsteps and jumping, as well as FOV adjustments when sprinting to make the player feel faster and more connected to the character. But to really tie everything together, I introduced some weapon sway to help compliment that connection with the character I was aiming for.
Player movement
The building objects within the game have an interaction menu that pops up when the player gets within range. I needed to figure out the best way to ensure this pop up happens at the right time without hindering the player. The first consideration was having it show up when the player is looking directly at the object, which was solved through using a dot product check. This check also let me tighten that look threshold to allow the player to stand behind a turret and shoot without that popup showing up.
The next consideration was a distance check to ensure that the player is intentionally activating the menu. Without the distance check, the interaction menu was popping up for objects the player wasn't even close to, which caused a lot of confusion as to which object they were interacting with. Adding this distance check in fixed this issue, while also helping to allow the player to be close to targets without activating that menu.
Interact menu
To allow for growth and development throughout the game, the player has the option to upgrade their turrets to improve their range, damage, ammo capacity and reload speeds. To ensure expandability, I created an upgrade component that is attached to all chosen objects, that hold the information needed to be upgraded. This information is stored in struct and includes things such as:
Upgrade type
Current level
Max level
Stat adjustment per level
Cost per upgrade
This upgrade component handles all upgrade logic. Each time a stat is upgraded, it is reflected through incrementing the current upgrade level and is applied when needed, such as damage increase when calculating their attack. Certain values that are relevant to what upgrade level it is at, such as the modifier value or cost of that upgrade, are stored in an array where the current upgrade level is used to index into which value to read.
After seeing the success of the data table used when creating the radial menu, I will change my approach towards an upgrade system next time to use a similar approach. Having all the data scattered across different classes made it difficult to change values as we balanced the game, whereas if I had set them up in the one spot it would have simplified the process significantly. The upgrade component would read the needed data from this table when doing any upgrade logic.
Upgrading turrets
Working with the team was a great experience, and I learnt a lot during this project about myself as a team member and my skills as a programmer. It was a good project to explore different ways of storing and handling game-related data, and see where I can improve for the next project I am working on. I am proud of what we were able to achieve together as a team.