Roles: Programmer / Game Designer
This game was made in the Unity engine as part of my degree in game design from ACC. This game was built over the course of two semesters (8 months), and was made with a team of people. On this team, I acted as the lead programmer/creative lead and wrote the game’s code in C#.
F=MA is a game about using the recoil of your gun to propel yourself through a zero gravity environment. The main bulk of the game will see the player exploring the desolate space station of Enigmas Totalis 1 – a space station that was built by K.N.O.X. Security Corp and has recently been taken over by a “malfunctioning” A.I., referred to as the Enigma Protocol.
As an agent of the K.N.O.X Corporation, the player has been tasked with going into Enigmas Totalis 1 and doing reconnaissance and clean up. But, as the player navigates through the twisting corridors of Enigmas Totalis 1, fighting the A.I. and its minions, they uncover what happened on the station and the reason for the Enigma Protocol’s “malfunction”.
The script for the player handles everything from locomotion and collisions to health and UI pretty much anything the person playing the game will be doing. Most of the coding for it was relatively straight forward.
The player’s locomotion uses Unity’s rigid body system to impart force to the player along the player’s forward vector. The power and direction they are pushed is controlled by the type of gun the player has equipped and in what direction they are aiming the gun. The player can choose to either aim the gun in front of them to be pushed backwards, or behind them to move forward, as shown below.
Collisions in the game are handled by a custom collision system that I wrote because the collision system in Unity is restrictive. It works on a fixed update cycle of 60fps, with the player moving at a top speed of 30 units per second or 0.5 units per frame. This means that if any collider is smaller than 0.5 units, the player will not detect a collision. My system gets around this limitation by checking for a collision every time the game updates, which is on a much faster (although sometimes inconsistent) cycle.
Using Unity’s “raycast” and “overlapping capsule” functions, the code checks to see if the player’s capsule (seen in yellow) has overlapped anything in the current update cycle. If it has, a raycast is drawn in the direction the player is traveling (seen in red). If that raycast hits anything, the player registers a collision and bounces off the surface. By using raycast, the player doesn’t collide with any objects that might be traveling in parallel.
The Spider droid is an enemy that can climb along pretty much any surface that is less than 45 degrees. The biggest challenge to make this enemy come to life, was Pathfinding.
The spider droid’s pathfinding work with two basic functions. The first shoots out a raycast to act as the spider droid’s vision. When the raycast collides with a wall, it takes the normal of that wall face and calculates the angle between the surface the spider is crawling on and the wall the raycast has hit, in order to determine whether or not the wall is climbable. The second function takes the current position of the target object and projects it onto a plane. Building on these two basic functions, I went through three different iterations of the pathfinding algorithm before landing on an algorithm that worked.
My first approach to pathfinding worked a lot like the Boid movement. The spider droid would generate a number of different directions based on where the target was, where the walls were, and where other spider droids might be. It would assign different weights to each of these directions then take the average direction to move in.
This approach worked best in flat spaces with easy-to-calculate directions, but it couldn’t handle cliffs or corners very easily, often getting stuck in loops, trying to walk out of an area, only to walk back in again.
My second approach to the pathfinding problem was to take the algorithm from my first approach and turn it into a semi-recursive function. The algorithm went something like this: First generate a set of possible directions the spider droid can move, then check a direction to see if it is obstructed (e.g. has any walls in the way that we can’t climb on or will cross over any pits). If that direction isn’t obstructed, then generate a new set of possible directions and repeat. After 3 repeats, the function returns only the directions from the first iteration of this loop.
This approach fixed the bugs from the first approach, but it was very computationally expensive. Each iteration through the loop would pick ten new directions, to the power of 3 iterations of the loop. This means (given the worst possible conditions) the algorithm would generate ten directions, and then ten directions for each of those directions, and then ten more directions for each of the previous directions, for a total possible 1,000,000 combinations to be tested, Every. Single. Frame.
The third and final approach I took to solving the pathfinding problem was a modification of the code from my second approach. Instead of generating a new path every single frame, I would generate the path once, and the spider droid would follow it until it reached the end of the path, whereupon it would generate a new path.
This method made the computation less expensive and fixed the pathfinding bugs from first approach. The only downside is that this solution is not as dynamic as the other two approaches. The spider droid can’t make snap judgements or compensate if the movement path changes. For the game, which is made up of mostly static environments, this isn’t a big deal. But, if I were to have made this enemy for a different game, then we would have had to try a fourth iteration. Maybe a system that generates a grid of valid spaces in a given level and then uses the A* Pathfinding Algorithm to find the quickest path to the player.
Game Manager / Event System
The Game Manager handles pretty much everything else in the game not already handled by a script. It is the game manager’s job to relay information between scripts and handle the scripted event sequences in the game (how they fire, what happens when two events fire at the same time, etc.)
All of the different types of events in the game use the Unity Timeline system in order to provide the team’s designers a visual way to plan out different events. Event functionality goes like this:
- Once started, run through the timeline until you hit a pause marker;
- While the event is paused, wait for some input from the user or game world (i.e. player presses a button, enemy dies, player hover over an object, etc);
- Once this input is received, resume the timeline until either you hit a pause marker, or you reach the end of the event.
- wWhen the event is stopped, send a signal to the game manager.
Wave Spawn Event
The Wave spawning event is the most involved event of the game, because it has three different components.
- The first component is a wave header. It tells the event system that a new wave is about to spawn. The wave header has two properties: “Number Of Enemies This Wave” and “Next Spawn Wave Threshold” (which is the max number of enemies remaining before another wave can be triggered.)
- The second component is the spawner controller. It sends a call to the event system to spawn an enemy. The spawner controller has two properties: “Enemy Type” and “Spawnpoint Index” (which is used to choose the possible spawn location.)
- The Third component is the end wave marker. It sends a message to event system to end the current wave, once the player has killed the last enemy.
- Using this system gives the team’s designers an easy and visual way to structure level spawning, work out the time between spawns, and quickly edit and move around the order of spawn events.