Juicing Your Movements
Written in October 2017 by Nathan Ranney, the founder of game development studio Gutter Arcade.
Adding some juice to your character movements and animations will help you squeeze some more life and feeling out of your games, and improve the quality of your games overall. This is by far the most subjective part of game development, and it depends heavily on the kind of game you want to make. That said, these concepts can be applied to almost any game to a varying degree.
Acceleration and Deceleration
One of the easiest things you can do is add acceleration and deceleration to your character's movement. This can give your characters a real sense of weight, and make them feel much more grounded in reality. Let's start by adding a couple of variables to your oPlayer object's create event. Under the //movement comment go ahead and add the following variables.
aSpeed = 0.2;
dSpeed = 0.5;
mSpeed = 2;
The first variable, aSpeed, will be our acceleration value, dSpeed will be our deceleration, and mSpeed will be our maximum speed. We are going to use the standard approach to make the movement nice and smooth. Open the normal_state script and make the following changes to your movement code.
//movement
if(left){
xSpeed = approach(xSpeed,-mSpeed,aSpeed);
}else if(right){
xSpeed = approach(xSpeed,mSpeed,aSpeed);
}else{
xSpeed = approach(xSpeed,0,dSpeed);
}
//don't run off the screen!
move_wrap(1,1,sprite_width);
Now your character should have some acceleration and deceleration. When pushing left or right, your xSpeed will approach your mSpeed by aSpeed. When you stop pushing left or right, your xSpeed will approach 0 by your dSpeed. Go ahead and fiddle with the aSpeed and dSpeed values until you get something that feels good to you. Generally, I try to keep my dSpeed higher than the aSpeed. I think it just feels better that way.
If you are wondering about the move_wrap line, that is just there to keep our character on the screen if you run him off. This function checks your object's x and y coordinates, adjusting them as appropriate to ensure your character doesn't actually leave the screen.
Just for fun, let's make a change to the crouch_state script. Open that up and change the xSpeed = 0 line to this.
xSpeed = approach(xSpeed,0,0.1);
Now when you run and then crouch, you will slide.
Jumping
Jumping is something that a lot of games have, and surprisingly, a lot of games get wrong. It's understandable though. There are a lot of moving parts to having a good jump. Gravity, air control, jump power, variable jump height, hang time, coyote time, etc. all have a huge impact on how your jump feels. Open the create event in the oPlayer object and add the following line to the end of your //movement section.
jPower = -4;
You may have noticed that gSpeed has already been defined for you, along with a couple of other variables that are important to collision. The only one you really need to be concerned with at the moment, other than gSpeed, is onGround. I'll explain why in a bit.
Go back into your normal_state script and add the following lines below your left/right movement code.
//jump
if(onGround){
if(up){
ySpeed = jPower;
}
}
Run the game and check out your new jump! How does it feel? Too slow? Too fast? Does your character jump too high? Or maybe not high enough? No problem. These are all easy things to tweak by adjusting our gSpeed and jPower. Before adjusting either of those values, though, open the code in the Begin Step event of the oPlayer object. This is where our gSpeed, gravity speed, is being applied to the character. As long as our character is in the air, as indicated by onGround=false, we cause gravity to affect his vertical velocity by adding gSpeed to ySpeed. This is our very scientific application of gravity.
You may have noticed that jumping is always the same height, no matter how long you hold the jump button. This may not be exactly what you want for your own game; you may prefer to have a variable jump height. This means that you jump higher the longer you hold the jump button. Tapping the jump button results in a short hop, while holding the button results in a full jump. Below the code we just added in the normal_state script, add the following lines.
//variable jump height
if(!onGround){
if(ySpeed < 0 && !up){
ySpeed *= 0.5;
}
}
Run the game and try out the jumping. When you let go of the jump button, your ySpeed is cut in half and your jump is cut short. First, we check to see if onGround is false (you are in the air) and your ySpeed is less than zero (you are traveling up). Next, we check to see if you are NOT holding the jump button (you just tapped it and let go). If all of that checks out, then your ySpeed is divided in half. Try adjusting the value that ySpeed is multiplied by to see how this changes the feeling of the jump.
Given our current setup, it's also really easy to adjust the amount of horizontal control the player has in the air. You can take away in-air control entirely or allow for precise in-air control. The easiest way to do this (without changing out basic movement code) is to change our aSpeed variable according to whether we are in the air or on the ground. In the Create Event of the oPlayer object, let's define a couple of new variables.
airAccel = 0.1;
groundAccel = 0.2;
airDecel = 0;
groundDecel = 0.5;
In the normal_state script under your left and right movement code, and above the jump code, add the following lines.
//accel and decel control
if(onGround){
aSpeed = groundAccel;
dSpeed = groundDecel;
}else if(!onGround){
aSpeed = airAccel;
dSpeed = airDecel;
}
If you copied the values exactly, you should have reduced control in the air, and no deceleration while in the air. Try adjusting these values a bit until you get something that feels good to you. There is no right or wrong answer here.
The last thing we need to do with jump is to rig up the animations. You'll notice that the sprPlayer_Jump sprite is three frames. However, we don't actually want to animate this sprite. We want to display each frame depending on the current state of the players jump: rise, peak, and fall. Open up your animation_control script and add the following to your states.normal case.
switch currentState{
case states.normal:
if(left){
facing = -1;
}else if(right){
facing = 1;
}
if(onGround){
if(left || right){
sprite = sprPlayer_Run;
}else{
sprite = sprPlayer_Idle;
}
}else{
sprite = sprPlayer_Jump;
if(ySpeed < -1){
frame = 0;
}else if(ySpeed > 1){
frame = 2;
}else{
frame = 1;
}
}
break;
}
Now your jump animation is set up and changing frames based on the character's ySpeed.
Squash and Stretch
Now that we have all of our movement in and tuned, let's juice it up a bit. We can make it feel even better. It's no secret that animation plays a huge role in how our games feel. When our animations don't match up to the feeling they are trying to convey in-game, things can feel really disconnected. Considering our animations are pretty simple, we can add some more "oomph" to them programmatically.
First, let's define a new variable in the Create Event of oPlayer.
landed = true;
This is a simple boolean that we can flip when we land on the ground, or leave the ground. In the normal_state script, at the bottom, add the following lines.
//landed
if(onGround){
if(!landed){
squash_stretch(1.3,0.7);
landed = true;
}
}
if(!onGround){
landed = false;
}
Run the game and start jumping around. When you land back on the ground, your character will squash down a bit, and stretch horizontally a bit. This makes our jumping a lot more interesting that before. Let's apply the same idea to the jump itself. In the normal_state script, where you added your jump code, make the following changes.
//jump
if(onGround){
if(up){
ySpeed = jPower;
squash_stretch(0.7,1.3);
}
}
Whenever you jump, you'll stretch upward a bit and squash in horizontally. Instantly our jump feels more powerful, even though we didn't change any of the actual jump properties. Squashing and stretching exaggerate our movements and add visual interest wherever they are applied. When used in the right context, this technique also provides additional visual feedback to the player when lots of other things are going on. Let's apply squash and stretch to crouching. In the normal_state script make the following changes.
//change to crouch state
if(down){
currentState = states.crouch;
squash_stretch(1.3,0.7);
}
If these values are too squishy, or maybe not squishy enough, feel free to adjust them to your liking. This is totally up to you.