Basic 4-Directional Movement with States

Hey there! In this tutorial we’re going to make a basic 4-directional movement system with states. The states we’re going to have in this tutorial will be “idle”, “move” and “attack”, but after completing this tutorial, you’ll be able to add more states!

End result:


This should work with both GMS2 and GMS1.4.


First you’ll need to create separate sprites for all the states and directions. Since there are 4 directions and 3 states (idle, move and attack), there need to be 4 directional sprites for each state.


So, the sprites should be named in this way: First “spr_player_”, then the state name (“idle”/”move”/”attack”) and then the direction. The directions here are:

0: right
1: up
2: left
3: down


So according to this, the right facing sprite for move state is spr_player_move0 and the downwards facing sprite for idle state is spr_player_idle3.

It is the way it is because the algorithm that we’ll be using for the directions is simply the direction divided by 90. Since the direction is from 0 to 360, where 0 is right, 90 is up, 180 is left and 270 is down, divided by 90 they would return:

right: 0/90 = 0;
up: 90/90 = 1;
left: 180/90 = 2;
down: 270/90 = 3;

Player Object

Now let’s move to the fun part: coding! Make the player object. Since there are so many sprites for the player, you can assign any one of them to the object, since the correct sprite will be selected through code anyway.


First we’ll make the movement, which is quite simple. First I’ll create these variables in the Create event:

hsp = 0;
vsp = 0;

move_speed = 1;

hsp will be the horizontal input and vsp will be the vertical input. move_speed is the speed at which the player will move.

Next, Step event:

hsp = (keyboard_check(ord("D")) || keyboard_check(vk_right)) - (keyboard_check(ord("A")) || keyboard_check(vk_left));
vsp = (keyboard_check(ord("S")) || keyboard_check(vk_down)) - (keyboard_check(ord("W")) || keyboard_check(vk_up));

hsp *= move_speed;
vsp *= move_speed;

Ah, that looks complicated, doesn’t it? But actually it’s quite simple, so let me explain.


Look at this code:

hsp = keyboard_check(vk_right) - keyboard_check(vk_left);

keyboard_check() returns 1 when the key specified is pressed.

This will set hsp to 1 when right is pressed:

hsp = 1 - 0;

and to -1 when left is pressed:

hsp = 0 - 1;

But, since we also need to implement WASD input into our game, I’ll add more keyboard_check() functions for those keys:

hsp = (keyboard_check(ord("D")) || keyboard_check(vk_right)) - (keyboard_check(ord("A")) || keyboard_check(vk_left));

So, I joined the D condition with the vk_right condition using the || sign, which means OR. So if either of those keys is pressed (D or right), it would return 1. Same for the A and vk_left part.

Next, I just copied the line, changed the variable being applied to vsp, and replaced D and right with S and down, and A and left with W and up.

vsp = (keyboard_check(ord("S")) || keyboard_check(vk_down)) - (keyboard_check(ord("W")) || keyboard_check(vk_up));

So this will set vsp to 1 if down/S is pressed, and to -1 if up/W is pressed.

Then I multiplied the hsp and vsp values with the move_speed:

hsp *= move_speed;
vsp *= move_speed;

Some of you might be thinking that next we’ll add these hsp and vsp values to x and y, but not now. We’ll add that at the end of the Step event, after adding more code.


In the Create event, I’ll create the following variables:

dir = 0;
state = "idle";

attack_time = 0;
attack_key = vk_space;

dir will store the direction divided by 90, as I explained in the Sprites section above. So it will be either 0, 1, 2 or 3.

state will store the state we’re in, which we’ll initialize with the state “idle”.

attack_time will be the time that has gone by, since the player pressed the attack_key, which will be used for ending the “attack” state after some time.

Now in the Step event, I’ll add:

var moving = hsp!=0 || vsp!=0;

case "idle": 
    //change to move 
    if (moving) state = "move"; 
    //change to attack 
    if (keyboard_check_pressed(attack_key)) state = "attack"; 
case "move": 
    //change to idle 
    if (!moving) state = "idle"; 
case "attack": 
    if (attack_time>10){ 
        attack_time = 0; 
        state = "idle"; 

The moving part will create a local variable named moving that stores whether or not the player is moving, because hsp!=0 || vsp!=0 will return true if either hsp or vsp are NOT equal to 0, which signifies that there is some input in either variable.

Next comes a switch statement. If you don’t know how they work, read about it in my GML guide. Scroll down and you can find the Conditions – switch Statements part.

Switch Case Explanation

So when the state is “idle”, the following code will run:

//change to move 
if (moving) state = "move"; 
//change to attack 
if (keyboard_check_pressed(attack_key)) state = "attack";

If the player is moving, as told by the local variable moving we created above, it will change the state to “move”.

If the attack_key is pressed, which we set to vk_space (spacebar) in the Create event, the state will change to “attack”.

Now to the “move” state code:

//change to idle 
if (!moving) state = "idle";

This will just change the state back to “idle” if the player is no longer moving.

Now the “attack” state code:

if (attack_time>=10){ 
    attack_time = 0; 
    state = "idle"; 

It will add 1 to attack_time. When it reaches 10, it will set it back to 0 and change the state to “idle”. So, the player only stays in the “attack” state for 10 steps. You can change this number in the condition.

So we’re done with the switch case, now let’s add some more code in the Step event:

if (moving){ 
    var _dir = point_direction(0, 0, hsp, vsp); 
    dir = floor(_dir/90);

If the player is moving, it will create a local variable named _dir and set it to the direction the player is moving in. point_direction() returns the direction from one point to another. So here it will return the direction from the point 0, 0 to the point hsp, vsp, which are the movement variables of the player.

Then it’ll set the dir variable to the direction in _dir divided by 90, then floored. floor() will round a number down, so say if the number is 1.5, it will become 1. We’ll use that since we only need the 0, 1, 2 and 3 values and not anything in between.

We’re using that moving condition since we need dir to stay towards the direction the player last moved towards, after it stops. Remove the condition and dir will just return to 0 (facing right) whenever the player stops.

Some more code after that:

sprite_index = asset_get_index("spr_player_" + state + string(dir));

x += hsp;
y += vsp;

The sprites part will set the sprite according to the state and the direction (dir). asset_get_index() returns an asset (sprite, object, etc.) that is present in your project that has the name that you type into the function.

In this function, I’m using “spr_player_” + state + string(dir) so if the state is “move” and dir is 1 (up), it will select the spr_player_move1 sprite.


The string() function is used to convert a number into a string.

The movement part will, finally, add the hsp and vsp values to the x and y values to make the player move.

Now, maybe you’ll be thinking that I missed collisions, but I’ll not talk about them in this tutorial since this is mainly about states.


That’s all! Now place the player object in the room and test it out!


You can move with both the WASD and arrow keys and attack with spacebar.

Now, in the same way, you can add more states by adding their cases in the switch statement and sorting out proper transitions between them.

You can download the project here: GMS2 Project


Hope this tutorial helped you! If you have any further questions or doubts, join my Discord server (no sign-up required) and you can get all the help you want there, for free. You can also find me there (username: matharoo).

Follow the website now (go to the bottom of this page) to get the latest posts delivered right into your e-mail inboxes as soon as they’re out.

See you, and happy dev’ing!


Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )


Connecting to %s