Tech

Buttery Smooth Tech Tips: Movement

Unnamed

Posted by Gavin Smart on 9 June 2017

Hey, Game Makers! Seth Coster from Butterscotch Shenanigans here.

When I first started writing this article, I had some lofty goals. I was going to write about code-driven skeletal animation, lighting systems, inventory systems, or maybe some other absurdly complex subject. But you know what? Sometimes, you gotta stick to the fundamentals. Because it doesn’t matter if you know how to animate a character if that character keeps getting stuck on walls. And it doesn’t matter if your lighting system makes an enemy look good, if that enemy piles on top of other enemies and has boring AI.

The core elements of your game have to be solid, or all the pretty visuals won’t do you a bit of good! So today, we’re going to go through some of the fundamentals of character movement.

Tip #0: Make Your Game 60 fps

You may be doing everything right, but still be thinking, “Something about my character’s movement just doesn’t feel that SMOOTH.” That might be because your game is at 30 fps (which is the default), when the preferred frame rate for games is 60! In GameMaker Studio 2, this setting can be found in the game’s “Options” under “Main”. So get on in there and fix it!

If you’re aiming to make a retro-style game to kick that NOSTALGIA into overdrive and therefore prefer to have 30 fps... Well, you’re wrong! Just set it to 60 fps anyways. Nobody misses the days of lower frame rates.

Don’t worry -- if you ever want to increase the frame rate later (because you definitely don’t want to decrease it), you can, because of what we’re about to do next!

Tip #1: Tie Your Movement to Time, Not Frames

So your game’s at 60 fps, and there’s a lot going on: enemies, explosions, dragons, NPCs, fighter jets, lasers, and of course, romance. It’s a masterpiece. You build an APK, and you hand it off to your friend for tesing. Your friend boots it up on their favorite 14-year-old, hand-crafted machine running a custom Linux kernel running Wine running an Android Emulator, and their first point of feedback is: “This is really slow.”

Just because something runs well on your setup, doesn’t mean it will run well for everyone. Players often have sub-optimal setups. They might experience frame rate dips, or maybe just a constant 15fps.

If your game is at 60 fps, and your characters move at 10 pixels per frame, then Susan’s beast machine gaming rig will show your characters moving around at 600 pixels per second, while Jerry’s Linux Android Frankenstein’s Monster will show your character only moving at 150 pixels per second. Jerry’s going to have a bad time because of this, but he really loves his setup, and he’s definitely not going to get a new gaming machine.

The impetus now falls on you, the developer, to make sure your characters move at the same speed on vastly different frame rates! But how?

By harnessing THE POWER OF TIME ITSELF. Specifically, delta_time, which is a built-in GameMaker variable that is equal to the number of microsends that have passed since the last frame.

How do we use this?

Let’s say we want our character to move at 600 pixels per second. In the create event, we will write:

move_speed = 600;

And in the step event, when the player is supposed to move (let’s say... to the right):

var seconds_passed = delta_time/1000000;
var move_speed_this_frame = move_speed*seconds_passed;
x += move_speed_this_frame;

The first thing we do is turn delta_time into something useful. I don’t know about you, but I don’t normally measure my day in millionths of a second. So first, we divide delta_time by 1 million, giving us the number of seconds that have passed. At 60 fps, this will ideally be 1/60th of a second, or 0.0167 seconds.

Now that we know how many seconds have passed this frame, we simply multiply that by our baseline movespeed, which we set as 600. This will cause our character to move at 600 pixels per second, regardless of frame rate. You’re welcome, Jerry!

Side Notes

Be careful with this! Someone running at 1 fps will show your character moving 600 pixels per step, rather than 10, so your character could potentially go through walls, dodge bullets, or do other awesome yet game breaking things. Try to set up your code to account for that.

Also, recalculating “seconds_passed” in every object every frame is a violation of the golden rule of programming: Don’t Repeat Yourself (the DRY principle). Instead, I would recommend converting it into a global variable, and have a single object that calculates it every frame. Piece of cake!

Tip #2: Clean up that input code!

GameMaker provides you with a nice “keyboard event” system you can use to track player inputs. While convenient, that system breaks your code apart into lots of little chunks that are all doing the same thing. WHOOPS! That’s another DRY violation! As a programmer, you should avoid those kinds of scenarios! Plus, handling each key press as a totally isolated code block entices people to write code like this:

Keyboard Event “W”:
y -= move_speed_this_frame;
 
Keyboard Event “A”:
x -= move_speed_this_frame;

This is cool, because it lets you move up, left, and diagonally. Except for one weird problem: you will move diagonally faster than you move up or left. Because if you’re moving left 10, that’s 10 pixels. If you’re moving up 10, that’s 10 pixels. But if you move up 10 and left 10 in the same frame, you’ve travelled 14.14 pixels instead. Hey, don’t give me that look -- that’s just the Pythagorean Theorem doing its thing.

SO... If it’s better to handle movement inputs all together in one code block, and if it’s better to move at a predictable speed no matter what direction you’re going, let’s FUSE these two concepts together and clean up our code once and for all!

First, let’s initialize our movement inputs in the create event of our character.

movement_inputs[0] = ord("D");
movement_inputs[1] = ord("W");
movement_inputs[2] = ord("A");
movement_inputs[3] = ord("S");

This is a 1-dimensional array, where each input represents a 90-degree increment. So, “D” is 0 degrees (right), “W” is 90 degrees (up), “A” is 180 degrees (left), and “S” is 270 degrees (down).

Now, in the step event, we do what programmers do best. ITERATE!

var move_xinput = 0;
var move_yinput = 0;
 
for ( var i = 0; i < array_length_1d(movement_inputs); i++){
    var this_key = movement_inputs[i];
    if keyboard_check(this_key) {
        var this_angle = i*90;
        move_xinput += lengthdir_x(1, this_angle);
        move_yinput += lengthdir_y(1, this_angle);
    }
}
 
var moving = ( point_distance(0,0,move_xinput,move_yinput) > 0 );
if moving  {
    var move_dir = point_direction(0,0,move_xinput,move_yinput);
    x += lengthdir_x(move_speed_this_frame, move_dir);
    y += lengthdir_y(move_speed_this_frame, move_dir);
}

There! We’ve collapsed the movement inputs into a nice iterative loop. This allows us to have movement keys easily cancel each other out. Pressing “W” will change move_yinput by -1, while pressing “S” will change it by +1. So if you hold both, move_yinput simply equals 0!

Next, we are determining whether we are moving, which will only be true if we are:

  1. Holding at least one key, and
  2. Not holding a key that cancels that key out

We do this by measuring the total distance from (0,0) to (move_xinput, move_yinput). If move_xinput or move_yinput are not equal to 0, we’re off to the races!

Last, if we’ve determined that we are indeed moving, we once again rely on movexinput and moveyinput to tell us which direction we need to go, and we will use the lengthdirx and lengthdiry functions to go a particular speed in that direction.

Now we have all our inputs bundled together, and we are always moving at the same speed, no matter what direction we’re going. FANTASTIC!

Tip #3: Make it Slippery

You know how in the previous section, to move the character, we just did this:

x += lengthdir_x(move_speed_this_frame, move_dir);
y += lengthdir_y(move_speed_this_frame, move_dir);

Well that was a LIE. Turns out that’s a terrible idea, because it allows your character to ignore literally everything, including walls, and just go wherever it wants with impunity like some kind of toddler throwing a tantrum. Well, we’re not doing that anymore. It’s time to put our foot down and become responsible parents by forcing our character to obey some dang movement rules.

We’ll do this by making a movement script, which we will just call move(). This script will take two inputs: speed and direction. First, we need our script to account for solids. To do this, we’ll simply check whether we can go somewhere, and if so, we’ll go there!

move()
/// @arg speed
/// @arg direction

var spd = argument0;
var dir = argument1;

var xtarg = x+lengthdir_x(spd,dir);
var ytarg = y+lengthdir_y(spd,dir);

if place_free(xtarg,ytarg) {
    x = xtarg;
    y = ytarg;
}

Then, in our character object, instead of:

x += lengthdir_x(move_speed_this_frame, move_dir);
y += lengthdir_y(move_speed_this_frame, move_dir);

We will write:

move(move_speed_this_frame,  move_dir);

This will allow us to stop when we hit solid objects, but this leads to the stuck problem. You’ll see something like the following:

Stuck

My life is pain.

With this movement system, it’s very easy to have our character get stuck in, on, around, or against walls.

There’s nothing less satisfying as a player than having your character stop responding to inputs, even if those inputs say, “I want you to defy the laws of physics and go through a wall.” So instead of having the character just stop moving when faced with an obstacle, we’re going to allow the character to “slide” along the wall like a greased pig at a county fair.

We’ll do this using a FANCY technique called ANGLE SWEEPS! I don’t know if that’s actually what it’s called, but that’s what it does, so deal with it!

We’re going to update our move() script to say the following:

move()
/// @arg speed
/// @arg direction
 
var spd = argument0;
var dir = argument1;
 
var xtarg = x+lengthdir_x(spd,dir);
var ytarg = y+lengthdir_y(spd,dir);
 
if place_free(xtarg,ytarg) {
    x = xtarg;
    y = ytarg;
}
else {
    var sweep_interval = 10;
    
    for ( var angle = sweep_interval; angle <= 80; angle += sweep_interval) {
        for ( var multiplier = -1; multiplier <= 1; multiplier += 2) {      
            var angle_to_check = dir+angle*multiplier;
            xtarg = x+lengthdir_x(spd, angle_to_check);
            ytarg = y+lengthdir_y(spd, angle_to_check);     
            if place_free(xtarg,ytarg) {
                x = xtarg;
                y = ytarg;  
                exit;       
            }   
        }
    }
}

With this, we’ve added a “fallback” system. If the place we are attempting to go is unavailable, we start checking in 10-degree angle increments to the left and to the right, until we get up to 80 degrees to either side. The first time we hit an available position, we go there instead of the original spot! This will cause your movement to look like this:

Slide

I feel liberated.

You may notice that if I’m pushing directly against the wall, I still am not moving. This is because we stopped the angle sweep at 80 degrees. If we checked a full 90 degrees, then that would allow us to move sideways from our original intended movement, which may or may not be good, depending on your intent! But if you did allow a full 90 degree sweep, that would look something like this:

Slip

SO SLICK AND SLIPPERY!

That’s All!

Now that your characters can move around freely, your life will be different.

You’ll have a gleam in your eye and a spring in your step. When you come across an obstacle, you won’t get stuck -- you’ll just slide right past it as if it’s not even there. You won’t care whether you’re going diagonally, left, or forwards. All that matters now is that you’re moving in a general 180 degree cone toward your destination, because you know you’ll get there eventually.

When everyone else around you is having a rough day, and they’re moving all sluggish, you’ll still be blazing forward at the same speed. Someone will say to you, “How are you able to always move at the same pace no matter what?” And you will say, “delta_time.”

Later that evening, when someone at the bar asks you, “What do you do for a living?” You will say, “I move characters around on a screen.” And then you will take a deep pause and look thoughtfully into the distance and say, “Or maybe... the characters move me.”

Back to Top