Salmutation is a physics-based 3D platformer where you control a salmon that can change size, and must jump through a fish farm facility using different water sources to avoid dying. This was a 2nd year project where I was the sole programmer along with an artist teammate, and we both did design.
Salmutation was made for a level design module where we were given the brief of making a level based around a bunker theme. We thought other people would play the idea fairly straight, so we wanted to do something silly and different with an unconventional control scheme or player character. After some brainstorming we ended up on the idea of playing as a fish trying to escape a fish farm, having to navigate around the level between different bodies of water to avoid dying. To add more interesting gameplay and more depth, we thought of the fish being able to change its shape and size between three forms, with each form having different attributes that are suited to different aspects and challenges of the level. I did all the programming on this project along with collaborating on the game and level design with my friend, who did all the art, modelling, and UI work.
For the fish to navigate around the level, it needed to have two modes of locomotion, one for water and one for land. The fish's ability to change size, and therefore change the properties of what it was capable of, meant I also needed to keep data on what the fish's different attributes were. For this I created three different scripts; PlayerData, PlayerLand, and PlayerWater respectively. PlayerData holds all the important information about the fish, like health, rigidbody reference, etc. along with an array of data on the different values for the different sizes, as mentioned. These values change the fish's jump height, maximum speed, acceleration, movement modifiers for land and air, and how fast it loses health when out of water. The smallest size, for example, can't jump very high or move as fast, but it loses health at a much slower rate than its larger counterparts. PlayerData also reads inputs and performs actions for things like resizing, keeps public variables that can be read for the player's direction and velocity, handles trigger events for whether it's in water, and sets the variables for the movement scripts. Resizing simply smoothly changes the fish's local scale, but it could cause problems if the fish was in a location where there wasn't enough space for its target size, causing physics issues and getting the player stuck. To avoid this I added some checks to validate resizing, first doing a capsule collider check using the target size, slightly raised up to compensate for how the fish size raises its centre off the floor. In addition there's a raycast from the front and back of the fish, and if any of these checks find collision, it stops the fish from resizing. When the player enters or exits water, the land and water movement scripts are enabled or disabled accordingly.
PlayerLand controls the player's movement on land. The fish uses rigidbody physics for its movement rather than a CharacterController, as the gameplay is physics-based but with an arcade-like feel to the controls for how much you can influence your movement. When on land, the fish rolls around in the direction you're holding. To achieve this we check where the right of the fish is pointing, and rotate the fish so its right side is facing towards the direction we're trying to move, and then add a force in that rightward direction. The force is the acceleration multiplied by the modifier for whether we're on land or in the air, the air modifier reducing the amount of movement control compared to on land. Player velocity is also capped at the max speed, which changes over a short time to match the modifiers, so that when the speed is capped it's not as abrupt. The player can also jump, the height and max upwards speed also being affected by fish size.
PlayerWater controls the fish's movement when in a body of water large enough to swim in. When enabled, this script turns gravity off for the fish, and makes the fish face in the direction of player movement. The fish is able to jump from the water, and when swimming its velocity is moved towards the max swimming speed using the swim acceleration, both of which are set by PlayerData based on fish size. The fish will change direction to match where they should be going very quickly, and doing sharp turns will reduce velocity significantly before speeding back up. The game distinguishes between large and small bodies of water, where only in large enough bodies will the fish be controllable in this way. In small bodies of water where there isn't space for the fish to move, they can only jump. When entering a body of water, the PlayerData script will check the collider for where the top of the water's surface is, and PlayerWater will always attempt to float the fish to rest at the surface of the water in a natural way while swimming. This makes it much easier to assess and make jumps from water sources, and also avoided overcomplicating the swimming, as there was no need to be able to dive under the water.
Salmutation was a fun project that also pushed my gameplay programming and design knowledge the most during university. I learnt a great deal about Unity's physics system during the course of it, and more about the lifecycle of actually finishing a project. Looking back at it I can see how much I've improved since, and the obvious flaws in my programming back then, but I also see the seeds and the origin of evolution for my understanding of programming and my approaches to it in the time since.