8-Bit Warrior Games + Design + Tutorials + Random

27Oct/1127

[Game Maker] Bettering 4-Direction Player Movement

null

This tutorial assumes you have some experience with Game Maker's scripting language, GML.
However, even if you are unfamiliar with it, you should still be able to follow along and find this useful if you take the time to understand it.

Controlling a character in any game should be as intuitive as possible for the player.
Thus, control should function in a predictable manner and avoid any little quirks that could arise and leave your player falling down an endless pit.

One small quirk I'd like to address here revolves around 4 direction character control, and the issue of key direction input/release dominance.
To help illustrate this issue, play around with the character in this example:
BAD CONTROL EXAMPLE

Take note of what happens when you have one direction held and then press down another while holding on to the first direction. Then, try holding down the second direction first and press down the original direction afterwards. Do you notice anything odd?

If you play around with the rest of the directions, trying the different combinations of starting with one direction and then pressing and holding another, you will find there are keys with dominance over other keys. It doesn't care which key was pressed last, but only which key returns true. But if more than one key direction returns true, it simply takes the first key returning true and uses it.

We will now look at one solution for this issue.

Note: This solution includes both ARROW/WASD key movement.

1) Start a game maker project and create an object called obj_input and set it as persistent

2) Inside obj_input, add Event -> Create create

3) Inside the Create event, add -> Code code for initializing a few global variables

globalvar
g_keyDirection,             // holds value for active direction key pressed
g_keyDirectionIsPressed,    // is true when direction key first pressed down
g_keyDirectionIsReleased;   // is true when direction key is released

g_keyDirection = -1; // initialize as -1 to ensure no initial false input
g_keyDirectionIsPressed = false;
g_keyDirectionIsReleased = false;

4) Inside obj_input, add Event -> Begin Step step

5) In this event, add -> Code code to check for the last direction pressed and set g_keyDirection/g_keyDirectionPressed accordingly:

    // First, clear global Pressed/Released states
g_keyDirectionIsPressed = false;
g_keyDirectionIsReleased = false;

    // Second, Check for ARROW or WASD presses
if (keyboard_check_pressed(vk_right) 
or  keyboard_check_pressed(ord('D')))
{
    g_keyDirection = 0;
    g_keyDirectionIsPressed = true;
}
else 
if (keyboard_check_pressed(vk_up)
or  keyboard_check_pressed(ord('W')))
{
    g_keyDirection = 90;
    g_keyDirectionIsPressed = true;
}
else 
if (keyboard_check_pressed(vk_left)
or  keyboard_check_pressed(ord('A')))
{
    g_keyDirection = 180;
    g_keyDirectionIsPressed = true;
}
else 
if (keyboard_check_pressed(vk_down)
or  keyboard_check_pressed(ord('S')))
{
    g_keyDirection = 270;
    g_keyDirectionIsPressed = true;
}

Okay, this is great, but what about key release variances? If a player has two keys pressed and releases one of them, it should always return the direction to the remaining key held.

6) Add the following code below the code we just wrote in the Begin Step event

   // Third, Check for ARROW or WASD releases
if (keyboard_check_released(vk_right) // Arrow keys
or  keyboard_check_released(vk_up)
or  keyboard_check_released(vk_left)
or  keyboard_check_released(vk_down)
or  keyboard_check_released(ord('D')) // WASD keys
or  keyboard_check_released(ord('W'))
or  keyboard_check_released(ord('A'))
or  keyboard_check_released(ord('S')))
{
    g_keyDirectionIsReleased = true;
    g_keyDirection = -1; // Make sure to clear this first
    
    if (keyboard_check(vk_right)) g_keyDirection = 0;   else
    if (keyboard_check(vk_up))    g_keyDirection = 90;  else
    if (keyboard_check(vk_left))  g_keyDirection = 180; else
    if (keyboard_check(vk_down))  g_keyDirection = 270; else
    if (keyboard_check(ord('D'))) g_keyDirection = 0;   else
    if (keyboard_check(ord('W'))) g_keyDirection = 90;  else
    if (keyboard_check(ord('A'))) g_keyDirection = 180; else
    if (keyboard_check(ord('S'))) g_keyDirection = 270;
}

There, once you have added obj_input to your game's starting room (remember to make it persistent!), you should now be able to have any object in your game access the g_keyDirection variable and set its movement accordingly. You also have access to g_keyDirectionIsPressed and g_keyDirectionIsReleased for when you need to use those states.
For example,

7) Create an object called obj_player

8) Inside obj_player, add Event-> Step step containing -> Code code for moving the player accordingly

    // Set speed and direction when key pressed
if (g_keyDirectionIsPressed)
{
    speed = 4;
    if (g_keyDirection == 0)   direction = 0;
    if (g_keyDirection == 90)  direction = 90;
    if (g_keyDirection == 180) direction = 180;
    if (g_keyDirection == 270) direction = 270;

    // Or simply... 
    // direction = g_keyDirection;
}

    // Set speed and direction when key released
if (g_keyDirectionIsReleased)
{
    if (g_keyDirection == -1)  speed = 0; // Stop if no direction pressed
    else if (g_keyDirection == 0)   direction = 0;
    else if (g_keyDirection == 90)  direction = 90;
    else if (g_keyDirection == 180) direction = 180;
    else if (g_keyDirection == 270) direction = 270;
}

 

Now, the second direction key pressed will always be dominate when two keys are pressed down, and when a direction key is released, the remaining key pressed will take over as one would hope.
The character movement should now be like this:
BETTER CONTROL EXAMPLE

Again, notice the difference when compared with the BAD CONTROL EXAMPLE

You can download the completed example gmk for Game Maker here:

[Windows Version]

DOWNLOAD

[Mac Version]

DOWNLOAD

If you have any questions or comments, feel free to contact me!

null

Comments (27) Trackbacks (0)
  1. The awkward direction change is super annoying, so I’m very happy to find a tutorial like this. However, I get this error when I try to test my game:
    ___________________________________________
    FATAL ERROR in
    action number 1
    of Create Event
    for object obj_input:

    COMPILATION ERROR in code action.
    Error in code at line 1:
    globalVar g_keyDirection;

    at position 11: Assignment operator expected.

    And unfortunately can’t open your test file(I guess I have older version or its cause its the Mac version) so I can’t check what I’m doing wrong. Pretty new at this so I probably did something incorrectly. I assume you simply use “Execute a piece of code” for each batch of code? Hmm, I wonder if it doesn’t work due to being a different version.

    • Hey Katie, you bring up a good point. I never tested it out in the Mac version of Game Maker! Sometimes there are odd little quirks between the variations of Game Maker.
      I’ll do that some time today, and see if I can duplicate the error and find a solution for it.
      I’ll update the post to share the info once I do :)
      Thank you for letting me know!

    • It turns out that ‘globalVar’ should have been ‘globalvar’. *blush* My bad. That simple change should fix it :)
      Btw, you should now be able to download the gmk for the Mac version of Game Maker!

      • Awesome! That worked. Thank you so much. Movement is very smooth now. The only little glitch on my end is for when the object changes sprite when it changes direction it maintains the 2nd pressed key’s sprite even when you lift up and are only holding the 1st key(so the player looks like they are traveling backwards. But that’s probably something I can fiddle about with in the key press event. Thanks again! This tutorial rocks and you were super helpful. :)

        • I should have another tutorial up in a couple of days showing how to effectively assign sprites and animations for the right directions. Hopefully you will find it beneficial if you are still having glitches with this :)

  2. Sorry for commenting on a really old post, but I’ve recently tried using this code and I get the error
    ___________________________________________
    FATAL ERROR in
    action number 1
    of Step Event
    for object obj_player:

    COMPILATION ERROR in code action.
    Error in code at line 5:
    if (g_keyDirection == vk_right) direction = 0;

    at position 5: Assignment operator expected.

    for some reason.
    I’ve tried both copying and pasting the code into the game and writing it in line by line myself, and both times I get the same error. However when I’ve downloaded your example (for the mac) it works perfectly, and I’ve compared the code and it doesn’t seem to be any different? I was just wondering if you could tell me what I’m doing wrong if you still check out this blog. Is there some box I have to check that I might have overlooked?

  3. Hey this is great, thank you for a great tutorial. I had one question though; I am making a scrolling shooter and want the player to move up slower, and down faster, to add a more realistic element to the game. What exactly would I edit to do this? Thank you!

    • My apologies for replying so late. If you haven’t already found a solution to this, then I suggest editing this part:

      if (g_keyDirectionIsPressed)
      {
      speed = 4;
      if (g_keyDirection == 0) direction = 0;
      if (g_keyDirection == 90) direction = 90;
      if (g_keyDirection == 180) direction = 180;
      if (g_keyDirection == 270) direction = 270;
      }

      to look something like this…

      if (g_keyDirectionIsPressed)
      {
      if (g_keyDirection == 0)
      {
      direction = 0;
      speed = 4;
      }

      if (g_keyDirection == 90)
      {
      direction = 90;
      speed = 2;
      }

      if (g_keyDirection == 180)
      {
      direction = 180;
      speed = 4;
      }

      if (g_keyDirection == 270)
      {
      direction = 270;
      speed = 6;
      }
      }

      You would also need to change this code below to reflect what was changed above:
      if (g_keyDirectionIsReleased)
      {
      // Stop the object if no other direction key is held
      if (g_keyDirection == -1) speed = 0;

      // Otherwise set direction by remaining direction key
      if (g_keyDirection == 0) direction = 0;
      if (g_keyDirection == 90) direction = 90;
      if (g_keyDirection == 180) direction = 180;
      if (g_keyDirection == 270) direction = 270;
      }

  4. P.S. Sorry, forgot to mention that I also wanted to change the player sprite after a number of steps moving in the left and right direction. (This is to make the plane tilt) What would be the correct code? I have been experimenting, but so far nothing.

    • This question goes beyond the scope of this tutorial, but I’ll give a clue as to how I might achieve this.

      Create a new variable to hold the current value of tilt: planeTilt = 0;

      Check each step to see if the plane is moving in a direction in which it will tilt. If the plane is moving in a positive direction, add something like 0.05 to planeTilt. If it is moving in a negative direction deduct 0.05 instead. Then check to see if planeTilt is equal to 1 or -1 and assign the appropriate sprite.

      From there, you would need to reset the planeTilt variable when the plane stops moving and such. I’ll leave that as an exercise for you to complete :)

  5. Alright thanks. I really appreciate the help.

  6. Could you help me modify the sprite animations when moving. I tried using the animation guide from the grid movement guide, but they don’t use similar variables, and I don’t know what to adjust. I am getting the same problem as Katie, when 2 keys are pressed, it screws with the sprite animations.

    • The grid animation tutorial uses simple keyboard checks directly. I believe you will need to change those checks over to the g_keyDirection values set in obj_input.

      In the player object’s (obj_player) Step Event, try modifying the 4 keyboard checks to these instead:
      if (keyboard_check(vk_right)) -> if (g_keyDirection == 0)
      if (keyboard_check(vk_up) -> if (g_keyDirection == 90)
      if (keyboard_check(vk_left) -> if (g_keyDirection == 180)
      if (keyboard_check(vk_down) -> if (g_keyDirection == 270)

      I don’t think the g_keyDirectionIsPressed/g_keyDirectionIsReleased variables will be required if you are using grid movement based off my example.

      But I’m not exactly sure what your needs are so. So in the case that you need it, in the Step Event, you could place something like this: (**If you are not using grid movement**)

      if (g_keyDirectionIsPressed == true or g_keyDirectionIsReleased == true)
      {
      if (g_keyDirection == 0) { Set Right Animation }
      if (g_keyDirection == 90) { Set Up Animation }
      if (g_keyDirection == 180) { Set Left Animation }
      if (g_keyDirection == 270) { Set Down Animation }
      }

      That would check the direction every time a key is pressed/released, and change the animation accordingly.

      If you have any further trouble with this, do let me know.

  7. Hey, I’m having a little trouble controlling the sprites with your code and was wondering if you could help or if you still post here.

  8. I am trying for the life of me to get my obj_player to stop at walls.
    I’ve either got A) all movement gets stopped at walls and the player gets stuck, or B) the player can move through walls and then gets stuck.
    Where can I put the code for wall detection that’s not in the collision event.

  9. This guide was very interesting. Is the first time I use Game Maker and this helped me a lot to understand how coding works.
    Anyway, i modified it to make 8 direction movement with sprite animation, but i removed the alternate keys.
    Would you like to look at my code and see if it’s good or if it can be improved? It’s the first time I code something and i would love to have some feedback!

    OBJ_IMPUT:

    // First, clear global Pressed/Released states
    g_keyDirectionIsPressed = false;
    g_keyDirectionIsReleased = false;

    // Second, Check for ARROW or WASD presses

    if
    (keyboard_check(ord(‘W’)) && keyboard_check_pressed(ord(‘D’)))
    or (keyboard_check(ord(‘D’)) && keyboard_check_pressed(ord(‘W’)))

    {
    g_keyDirection = 45;
    g_keyDirectionIsPressed = true;
    }
    else

    if
    (keyboard_check(ord(‘W’)) && keyboard_check_pressed(ord(‘A’)))
    or (keyboard_check(ord(‘A’)) && keyboard_check_pressed(ord(‘W’)))

    {
    g_keyDirection = 135;
    g_keyDirectionIsPressed = true;
    }
    else

    if
    (keyboard_check(ord(‘S’)) && keyboard_check_pressed(ord(‘D’)))
    or (keyboard_check(ord(‘D’)) && keyboard_check_pressed(ord(‘S’)))

    {
    g_keyDirection = 315;
    g_keyDirectionIsPressed = true;
    }
    else

    if
    (keyboard_check(ord(‘S’)) && keyboard_check_pressed(ord(‘A’)))
    or (keyboard_check(ord(‘A’)) && keyboard_check_pressed(ord(‘S’)))

    {
    g_keyDirection = 225;
    g_keyDirectionIsPressed = true;
    }
    else

    if (keyboard_check_pressed(ord(‘D’)))
    {
    g_keyDirection = 0;
    g_keyDirectionIsPressed = true;
    }
    else
    if (keyboard_check_pressed(ord(‘W’)))
    {
    g_keyDirection = 90;
    g_keyDirectionIsPressed = true;
    }
    else
    if (keyboard_check_pressed(ord(‘A’)))
    {
    g_keyDirection = 180;
    g_keyDirectionIsPressed = true;
    }
    else
    if (keyboard_check_pressed(ord(‘S’)))
    {
    g_keyDirection = 270;
    g_keyDirectionIsPressed = true;
    }

    // Third, Check for ARROW or WASD releases
    if (keyboard_check_released(ord(‘W’)) && keyboard_check_released(ord(‘D’)) // WASD
    or keyboard_check_released(ord(‘W’)) && keyboard_check_released(ord(‘A’))
    or keyboard_check_released(ord(‘S’)) && keyboard_check_released(ord(‘D’))
    or keyboard_check_released(ord(‘W’)) && keyboard_check_released(ord(‘A’))
    or keyboard_check_released(ord(‘D’))
    or keyboard_check_released(ord(‘W’))
    or keyboard_check_released(ord(‘A’))
    or keyboard_check_released(ord(‘S’)))
    {
    g_keyDirectionIsReleased = true;
    g_keyDirection = -1; // Make sure to clear this first

    if ((keyboard_check(ord(‘W’)))&&(keyboard_check(ord(‘D’)))) g_keyDirection = 45; else
    if ((keyboard_check(ord(‘W’)))&&(keyboard_check(ord(‘A’)))) g_keyDirection = 135; else
    if ((keyboard_check(ord(‘S’)))&&(keyboard_check(ord(‘D’)))) g_keyDirection = 315; else
    if ((keyboard_check(ord(‘S’)))&&(keyboard_check(ord(‘A’)))) g_keyDirection = 225; else
    if (keyboard_check(ord(‘D’))) g_keyDirection = 0; else
    if (keyboard_check(ord(‘W’))) g_keyDirection = 90; else
    if (keyboard_check(ord(‘A’))) g_keyDirection = 180; else
    if (keyboard_check(ord(‘S’))) g_keyDirection = 270;

    }

    OBJ_PLAYER:

    // Set speed and direction when key pressed
    if (g_keyDirectionIsPressed)
    {
    speed = 20;
    sprite_index = plr_walking image_speed = 0.5
    if (g_keyDirection == 0) direction = 0;
    if (g_keyDirection == 90) direction = 90;
    if (g_keyDirection == 180) direction = 180;
    if (g_keyDirection == 270) direction = 270;
    if (g_keyDirection == 45) direction = 45;
    if (g_keyDirection == 315) direction = 315;
    if (g_keyDirection == 225) direction = 225;
    if (g_keyDirection == 135) direction = 135;

    }

    // Set speed and direction when key released
    if (g_keyDirectionIsReleased)
    {
    if (g_keyDirection == -1) speed = 0; // Stop if no direction pressed
    else if (g_keyDirection == 0) direction = 0;
    else if (g_keyDirection == 90) direction = 90;
    else if (g_keyDirection == 180) direction = 180;
    else if (g_keyDirection == 270) direction = 270;
    else if (g_keyDirection == 45) direction = 45;
    else if (g_keyDirection == 315) direction = 315;
    else if (g_keyDirection == 225) direction = 225;
    else if (g_keyDirection == 135) direction = 135;
    }
    // sprite direction
    if (g_keyDirectionIsPressed == true or g_keyDirectionIsReleased == true)
    {
    if (g_keyDirection == 0) image_angle = direction;
    if (g_keyDirection == 90) image_angle = direction;
    if (g_keyDirection == 180) image_angle = direction;
    if (g_keyDirection == 270) image_angle = direction;
    if (g_keyDirection == 45) image_angle = direction;
    if (g_keyDirection == 315) image_angle = direction;
    if (g_keyDirection == 225) image_angle = direction;
    if (g_keyDirection == 135) image_angle = direction;

    }
    //animation stop

    if (speed == 0)
    {
    sprite_index = plr_idle
    }

    Thank you!

    • I’m glad you found this tutorial to be helpful.

      Quickly looking over your code…

      if (keyboard_check(ord(‘W’)) && keyboard_check_pressed(ord(‘D’)))
      or (keyboard_check(ord(‘D’)) && keyboard_check_pressed(ord(‘W’)))
      {
      g_keyDirection = 45;
      g_keyDirectionIsPressed = true;
      }

      could be simplified to…

      if (keyboard_check(ord(‘W’)) && keyboard_check_pressed(ord(‘D’)))
      {
      g_keyDirection = 45;
      g_keyDirectionIsPressed = true;
      }

      … as the second line is simply repeating the first line.

      Also…

      // Third, Check for ARROW or WASD releases
      if (keyboard_check_released(ord(‘W’)) && keyboard_check_released(ord(‘D’)) // WASD
      or keyboard_check_released(ord(‘W’)) && keyboard_check_released(ord(‘A’))
      or keyboard_check_released(ord(‘S’)) && keyboard_check_released(ord(‘D’))
      or keyboard_check_released(ord(‘W’)) && keyboard_check_released(ord(‘A’))
      or keyboard_check_released(ord(‘D’))
      or keyboard_check_released(ord(‘W’))
      or keyboard_check_released(ord(‘A’))
      or keyboard_check_released(ord(‘S’)))
      {
      ...
      }

      … could be simplified to…

      // Third, Check for ARROW or WASD releases
      if keyboard_check_released(ord(‘D’))
      or keyboard_check_released(ord(‘W’))
      or keyboard_check_released(ord(‘A’))
      or keyboard_check_released(ord(‘S’)))
      {
      ...
      }

      Also, the player code could probably be simplified to something like…

      OBJ_PLAYER:
      // Set speed and direction when key pressed
      if (g_keyDirectionIsPressed)
      {
      speed = 20;
      sprite_index = plr_walking;
      image_speed = 0.5;
      direction = g_keyDirection;
      }

      // Set speed and direction when key released
      if (g_keyDirectionIsReleased)
      {
      if (g_keyDirection == -1) speed = 0; // Stop if no direction pressed
      else direction = g_keyDirection;
      }
      // sprite direction
      if (g_keyDirectionIsPressed == true or g_keyDirectionIsReleased == true)
      {
      image_angle = direction;
      }

      Let me know if these changes make sense. I haven’t tested the code to see if there are any problems with it :)

      • Hello! I’ve tried the changes you said and this is what i’ve figured out:

        if (keyboard_check(ord(‘W’)) && keyboard_check_pressed(ord(‘D’)))
        or (keyboard_check(ord(‘D’)) && keyboard_check_pressed(ord(‘W’)))
        {
        g_keyDirection = 45;
        g_keyDirectionIsPressed = true;
        }

        Is necessary, if i remove the second line the 45° walk don’t work anymore, let me explain: if i press W (and the character moves up) and then i also press D the character starts to walk 45° degrees and that’s fine, but if i press D and then W the character goes up instead of 45° because the second check is missing. I could change the first command in keyboard_check_pressed instead of keyboard_check but then the 45° walk will work only if i press the two buttons simultaneously and if i press another button during the movement nothing will happen. Maybe there is a better way to write this.

        The other simplifications on obj_input and ob_input worked really fine, there was just missing the:

        //animation stop
        if (speed == 0)
        {
        sprite_index = plr_idle
        }

        To stop the animation when the character stopped moving.

        Thank you very much, do you have some tutorials or resources for a better understanding of collisions in Game Maker?

        • You’re right about the keyboard checks! I failed to notice that you were using keyboard_check AND keyboard_check_pressed. That makes sense ^.^;

          The only collision resource I have is from my grid based movement tutorial series. But, that is quite specific and is probably not what you are looking for. I haven’t done much with writing tutorials lately, so you will have to look elsewhere for now. I’m hoping to get back into writing more tutorials soon.

          But, you could check out some tutorials by Shaun Spalding on Youtube. He may have some resources you are looking for.

          Hopefully that is of some help :)

          • Thank you, i’ll going to check that out!

          • Oh and feel free to update your tutorial with my modifications to implement 8way and sprite direction/animation if you think it worths. I’m following you on twitter, i like your blog.

  10. Well I guess we can do 8 directional movement with this..nvm,

    I will like to ask you is there any way to change the sprites when moving in a direction?


Leave a comment

No trackbacks yet.