
Terranova is a metroidvania prototype I made for my capstone project at the University of Texas at Dallas during the Fall of 2020. While I made it as a showcase of my level design skills, I also put a lot of time and effort into crafting a character controller that felt fun to play with. First, lets talk about jumping.
I wanted to make a jump with a defined jump height that I could easily control with a variable in the inspector. At first, I wasn’t sure how to do this, but then I remembered the kinematic equations I learned in high school physics and realized they would come in handy here. I knew the height I wanted and the force of gravity I was using, so I could plug those values into v^2 = 2ad and take the square root to find my initial velocity for any desired height. The tricky part was getting this to work with variable jump heights. I ended up changing the gravity acting on the player if they were holding down the jump button while going up. I figured out that if I multiplied gravity by the minimum jump height divided by the maximum jump height, it would pull the player downward at a rate that would end with the player jumping the max jump height if they held it down the entire time. This let me easily mess with minimum jump heights, maximum jump heights, and gravity to craft a jump that felt good to play with. As a finishing touch, I added some coyote time so that the player didn’t have to be 100% precise with their jump timings. Here’s the code used for jumping and handling gravity. You’ll notice that I also implemented wall jumps at some point, but that mechanic had bugs that needed to be solved and wasn’t implemented into the prototype for time reasons.


Horizontal Movement
For horizontal movement, the player can walk back and forth or they can use a dodge roll to get a quick burst of speed. To get this to work, I have a function that takes player input and saves the normalized horizontal speed to determine movement direction. Then that speed’s multiplied by the intended target speed based on the player’s state. Then I lerp between the player’s current speed and that target speed to create acceleration. If the player’s standing on the ground, they accelerate / decelerate faster then if they’re in the air. To actually detect collisions and translate the player in space I used Prime31’s 2D character controller. In part this was because this was ultimately a level design exercise, but I also did this because I already do basically the same thing they do for collision detection and movement when I write 2D character controllers myself, so it felt like unnecessary work in a project with a tight deadline. Now that the implementation stuff is out of the way, lets talk about why the dodge roll is cool as hell.


The dodge roll interacts in interesting ways with the normal movement system and the state system for the player. The player has a number of states, and most states allow you to cancel actions with other actions. The dodge state is a state that you can always go into so long as your dodge is charged. You can only cancel out of the dodge state by jumping, however, including when you’re in midair. The midair jump allows you to clear gaps that would be difficult to clear otherwise, and gives the player another tool for expression and mastery with their movement. It’s also interesting on the ground, however, because you lose speed slower in the air. This means you can dodge on the ground and then jump to preserve that speed into the air. Once you’re in the air, though, you aren’t dodging anymore, so you start slowing down immediately. As a consequence, you’re encouraged to try to time your jump towards the end of your dodge. This allows for more depth and player expression in movement, and adds a nice risk / reward aspect to it.
The dodge is also useful in that it allows you to travel between small gaps as the player rolling makes their hitbox smaller. To get this feature to work, I had to change the player’s hitbox out with another one whenever they dodged. Then, at the end of the dodge I used raycasts to check if the player was below a ceiling they normally couldn’t be, and if they were I would continue the dodge roll until they were in a place where they wouldn’t clip into the wall by standing up.
As the name would suggest, the dodge is also useful in combat. It gives the player invincibility frames for a short while that allow the player to dodge through enemies or hazards in the map. I implemented this by simply using flags and a timer to tell the player script to ignore damage sources during the dodge roll. It’s truly a versatile tool that I used to spice up the rest of the player’s move set, and creating all this functionality was something I spent a lot of time doing during this project.
Combat
While I didn’t spend as much time on combat as I did on the movement, There are still a couple interesting things to go over with it. Originally, I intended to give the player an aerial attack and a ground attack, but the air attack got cut for time reasons. I decided to give the player character a hammer in combat, and to create the feel of a weighty, heavy hammer, the player’s attacks would need to be a little weighty too. To accomplish this, I had it set so that while attacking, the player would be unable to move unless they cancelled their attack by dodging. I also gave the attack a little start-up time before it came out to represent the player taking time to swing the hammer. Then they have a frame or two where they can’t swing the hammer to show them recovering from the swing. The attack timings are handled via a few timers that tick down in update one after another.

Of course, implementing the downsides of having a big hammer wouldn’t be fun if you didn’t implement the upsides too. In order to replicate the feel of a hammer, I decided to give it a large sweet spot for the front hitbox. If you hit with this hit box, the hammer deals extra damage and knockback, allowing you to hit enemies into hazards like spikes, or just keep them away from you and retain your control over the battlefield. If they overlap with the handle hitbox closer to the player though, they take less damage and knockback, and they’re able to get in on the player a little bit. To implement this feature, I have each hitbox send different levels of damage and knockback velocity to the enemy. If both hitboxes hit, the enemy logs both of those hits and chooses the smaller damage of the two to apply to itself.

Besides the basic melee attack, the player also unlocks a ranged fireball attack at the end of the prototype. Since this attack was planned to be used for puzzles as well as combat, I had an interesting decision to make where I wanted the player to be able to stop and aim the fireball for more precise shots, but I also wanted it to be easy to use in combat. My solution was that the player would stop and aim the fireball if they pressed the button while standing, or if they shot one in the air the fireball would just go forward immediately. I implemented the aiming by putting the player into an Aim state which told the movement script not to let them move. Then I used the keyboard inputs to determine which way the player was aiming, and rotated a child object around them to face that direction. Then when the player stops holding the aim button, if they have the mana to cast it, they will instantiate a fireball at the point where they were aiming, and it will move forward based on its rotation, which is set to match the rotation of the point they’re aiming at. To aid in figuring out exactly where the fireball will go, I changed the offset on the camera follow script so that it would move in the same direction that the player was aiming, also.
By comparison, the aerial fireball was a lot simpler. It uses the same code for firing a fireball, just without any of the aiming, resulting in a fireball that always fires forward.