Over Staffed
Project Type:
Arena Shooter
Role:
Gameplay Developer
Language:
C#
Engine:
Unity
Date:
June 2023
Over Staffed is a First-Person Shooter in which a young wizard eliminates their corporate rivals by any means necessary. As a gameplay programmer I developed the enemies, enemy spawners, the final boss, and level management system.
The enemies of Over Staffed have varying health, damage, attacks, and movement speed. Each enemy is customizable from the editor by the provided serialized fields.
The primary enemy types are the mage, evil-eye, knight, and bomb.
I balanced each enemy and its subtype's stats to provide a variety of challenges for the player.
For example, the evil-eyes shoot homing missiles in contrast to the knight’s short ranged shotgun styled blast.
Enemies
AI
Each enemy utilizes the same basic AI at its core.
The enemy continuously moves towards the player until it is in range of the player.
If the enemy can see the player it will begin to shoot at the player.
As long as the player is within the enemies stopping distance, they will turn toward the player.
The reason for separating the stopping distance and the shooting distance is for enemies like the knights that shoot before they reach the player but ultimately like to stay in the player’s “face”, blocking the player’s shots from reaching the more fragile enemies.
By drawing a ray from the enemy’s head to the player, a check for obstacles is made.
If no obstacles are found, a check of the enemy’s direction and field of view is made.
If the direction of the player is within the field of view relative to the enemies direction then the enemy can see the player. The distance between the player and enemy is recorder for further use by the enemy AI.
A switch case is used for the attack categories.
If the enemy’s attack involves projectiles then the general use Shoot() is called. Otherwise, Explode() is called. Explode() generates a sphere collider and applies damage to all actors in range then destroys the calling actor.
By utilizing Unity’s built-in linear interpolation tools, enemies are kept facing the player when within their desired stopping range.
FacePlayer() takes the direction of the player from the enemy, converts it to a desired rotation, then pivots the enemy towards the player with respects to time and the enemies turn rate.
The Level Manager is designed to move the player between scenes based on the player’s current progress. Generally levels are randomly loaded from a set of prebuilt scenes in which a scene is never repeated twice in a row. However, the first five levels act as a tutorial to teach the player their movement options. Every fifth level loads the player into a hub level upon completion. There the player can spend earned points on upgrades and change their load out if needed. Lastly, at level twenty-one, a boss level is loaded and acts as the game’s end.
Level Manager
LoadNextLevel() is written so various objects can call for a proper level transfer with a single function. The system works by first checking if the player is coming from the character select screen or hub level. If the player is loading a level from either scenario the system will try to load one of the random levels with a few exceptions.
Because the boss level only appears after a hub level, the logic to get to the boss level is considered prior to a normal level’s loading.
The game also supports high score tracking, so it is possible to skip the tutorial if the highest level completed is greater than five.
Playthroughs started after beating the tutorial will take the player from the character select screen to the hub level before general gameplay begins.
Every third level completed will change the background music and every fifth level will bring the player to the hub level if they are not already there.
Every level completion will track the player’s high score and allows them to skip the tutorial or move directly to the “infinite” mode if the boss level has been completed.
Again, the first five levels act as tutorials. General gameplay is randomly loaded levels that do not repeat twice in a row.
Enemy Spawners are made so level designers can place spawn locations in a level, set which enemies they want to randomly spawn, and make it so spawners begin spawning on level load or player entry into an area.
Enemy Spawning
Each spawner calculates the number of enemies to spawn based on the current level and a designer set value.
This allows designers to create levels with increasing enemy density as the player progresses.
At level start most enemies are spawned. Designers are given multiple options for spawning enemies.
Enemies can be spawned over a single area around the spawner, randomly at multiple remote locations, a single remote location, or locally at the spawner itself.
A similar function is provided for ambush spawning of enemies. The main difference is that ambush enemies are spawned over time at rate designated by the designer.
Serialized fields let designers decide the length and width of the spawn area.
By considering the location of the spawner and the desired area around the spawner, enemies can be randomly spawned throughout a room without the need for specific placement.
Specific spawn points can also be placed and linked to the spawner.
Due to enemies “stacking inside of each other” on spawn, a small amount of variance is added to the ‘X’ and ‘Z’ of the spawn location causing Unity’s collision system to evenly spread the enemies around the spawn location - if it is still occupied - instead.
The enemy types spawned can be randomized or specific.
For specific enemy spawning, a single enemy type must be set to the spawner.
For randomized spawning, setting multiple enemy types with all zero weight or the same weight is required.
For weighted random spawning, individual weights must be given to each unit.
Weighted spawning involves generating a random number between zero and the total sum of all spawn weights. The system then iterates through each spawn weight, adding them. When the current sum exceeds the randomly generated number, the corresponding enemy type is spawned.