This project was a game I developed with a friend for the quarterly Metroidvania Month Jam in the spring of 2024. The game was created entirely over the course of a month by the two of us, plus some additional help for vfx, music, and some textures. In CMD: Wave, players pilot a sub deep into the ocean to explore undersea caves where a previous vessel vanished. The game features a mix of omnidirectional 2D and first-person 3D gameplay, with the player swapping between 2D control of the sub on their control terminal and 3D control of their human character navigating their sub to repair damage from the inside or recharge abilities.
[video swapping btw 2d and 3d]
During the jam, I handled programming the 2D aspect of the game, while my friend coded the 3D sections and handled the text-based command system. While in the sub, players will encounter a variety of Metroidvania-style upgrades to their sub that they can pick up and add as rooms to their vessel. Each upgrade provides a new ability that can be interfaced with through the ship’s text-based command line, as well as adding on a new room in the 3D play-space for players to interact with.
[adding/walking to room]
One of the first defining mechanics of the game that we settled on was having a limited radar view area of the world around you when in 2D sub controls. We wanted to really capture the tense and anxious feeling of being alone, deep in the ocean, with limited sense of your surroundings. I developed this radar view system as a way to add tension when moving around the world, as well as providing room for player agency when simply looking around and navigating the environment.
[clip of radar system]
The radar field-of-view area is created dynamically by drawing a series of triangular polygons around the player to make up the cone. The radius, angle range, and polygon resolution of the radar can all be changed by adjusting data values in a player data scriptable object, which allowed us to tweak the system easily until it looked and felt just right. This mesh is then given our radar shader material and will rotate around the player as the turn, following the mouse position on the screen.
[scanning enemies]
The radar also importantly will create red “blips” on the screen whenever a solid object is hit, whether that be the environment, enemy, or upgrade. Adding this functionality was incredibly simple after the mesh generation was implemented, as all I had to do is fire a raycast from the player to each outer point of the polygons making up the radar cone, and if an object within the collision mask was hit, draw a blip at this point. I implemented object pooling for the radar blips so they could easily be activated and deactivated without having to worry about the performance impact of rapidly instantiating and destroying what could be dozens of objects in a few seconds.
Designing and implementing all of the different upgrades for the sub was one of my favorite parts of working on this game. When initially brainstorming and creating the different abilities the player would collect throughout the game, we really wanted at least some to feel unique from the classic Metroidvania ability tropes (we still did eventually cave on adding a dash - more movement = more player agency!).
[scan dart firing]
The first upgrade the player collects is a radar scan dart, which fires a projectile forward in a straight path with a small, circular radar scan that follows the dart as it moves. These darts can be stuck to walls and even enemies and will continue to scan until they are reloaded. We wanted the first power in the game to reinforce the feelings of uncertainty and limitation that the game should leave players with. Rather than immediately providing a weapon, or some other way to defeat and destroy enemies and obstacles, we wanted to emphasize the importance of gathering data about the environment and carefully probing new areas before diving in.
The scan darts use the same radar creation system that I had already created for the player’s own radar cone, just with tweaked radius and angle settings to form a smaller but fuller circle. This made implementation incredibly easy yet still feeling new and different from the player’s default scan. Darts can be intentionally left in the environment to activate circuits, probe for danger, or track an enemy outside of the player’s view area. However, the player can only launch three of them at a time before they must walk to the scan dart room on their ship to reload them, deactivating previously launched darts. This process causes players to have to give up control of the ship briefly, further building tension while they are away from the controls and encouraing thoroughly scanning the environmen to find safe spaces to reload.
[torpedo firing]
The next ability players encounter is a torpedo launcher, which reuses much of the code from the scan darts. These projectiles deal damage to enemies and can destroy certain obstacles in the world, providing players with a way to fight back against danger after they have had to really sit in the feeling of powerlessness without it. Both the torpedoes and the scan darts made use of the same object pooling script that the radar blips do to optimize performance and limit any object creation or destruction during gameplay. The torpedo projectile itself utilizes the same functionality of the scan darts, just with the radar scanning removed and an added damage value added when contacting enemies. This allowed me to make this ability very quicly after adding the scan darts and rewarded writing modular code for the different abilities.
[footage of drone]
There are several other abilities the player encounters throughout the game, including a dash and a pressure blast to knock enemies away, but perhaps my favorite of them all is the drone, the game’s final upgrade. This ability allows players to create a miniature drone to take control of and explore the map with an added level of security, as the drone can be reloaded if destroyed with no damage being done to the ship. This power was designed to feel especially impactful due to players having to complete the majority of the game to unlock it, having spent most of their traversal time in the rather-vulnerable sub. However, the drone does not contain the radar scanner that the main vessel does, meaning that exploration is much more dangerous and pinpointing exact enemy locations without the sub is difficult. Controlling the drone is handled through layered input-possession system, which my friend had already developed to swap between 2D and 3D control, and allows our PlayerControls script to easily parse where input should be fed based on which layer of possession is currently active. This allows control to be able to pass seamlessly from the sub to the drone and back again.
I also helped create some of the enemy behavior and logic for this project along with my friend, who also helped with some enemy types and some of the overall enemy state machine structure. This involved creating state machine logic for different enemies to allow for organized, clearly defined behaviors and states for enemies and easy modularity when defining enemy functionality. I also implemented A* pathfinding provided in a Unity package to interface with the enemy states and movement to allow them to accurately and effectively track the player through all different sections of our environment.
The first type of enemy I made was our base enemy, which we internally dubbed the “shy enemy”. This enemy will only move towards the player when it is not within the player’s scan radar, making it relatively easy to play around and providing a lower stakes threat to help players learn the radar system and how it interacts with enemies. This enemy also highlighted a part of the game that was very important to us: having players observe the environment and draw conclusions about the world without specifically being told how certain things may work. We really wanted to emulate the experience of diving deep into uncharted depths of the ocean, so creating an enemy with very predictable and controllable behavior without explaining this to the player helps to set the tone that players will need to be observant and figure the world out for themselves.
[footage of shy enemy]
I also created a charging enemy that appears later in the game and homes to the player until it gets close enough, in which case it will stop for a moment and play an audio queue before charging in a quick burst at the player. The audio queue and strikingly different movement pattern further build on the self-discovery we wanted to highlight in the game’s design. Players may at first mistake this enemy for the shy enemy encountered earlier on, but the stark contrast in multiple aspects of its design quickly teaches players that it is a different type of creature with its own behavior. The charging enemy also made the state machine enemy set-up incredibly useful, as switching to a charge state when it got close enough to the player made fine-tuning the feeling of this behavior incredibly easy and modular to make the enemy feel just right.
[footage of charger]