1. Hey! Guest! The 35th GMC Jam will take place between November 28th, 12:00 UTC - December 2nd, 12:00 UTC. Why not join in! Click here to find out more!
    Dismiss Notice

GML Creating Smooth Character Movement

Discussion in 'Tutorials' started by Rukiri, Jun 10, 2019.

  1. Rukiri

    Rukiri Member

    Joined:
    Sep 25, 2016
    Posts:
    141
    GM Version: GMS1.4, GMS2 *May work with older versions
    Target Platform: ALL
    Download: https://github.com/Rukiri/ZenRPG * This will be constantly updated, but the main code below should stay the same
    Links: https://en.wikipedia.org/wiki/Trigonometric_functions

    Summary:
    In this tutorial you'll learn how to create smooth movement, this tutorial is specific to player movement but can be translated into NPC movement, or Cell-based movement "Math for that type of movement is not provided"

    Code:

    Supporting Scripts:

    RadiansToDegree:
    Code:
    var y1 = argument0;
    var y2 = argument1;
    var x1 = argument2;
    var x2 = argument3;
    var degree;
    
    degree = arctan2(argument0 - argument1, argument2 - argument3) * (180 / pi);
    
    return degree;
    DegreeToRadian:
    Code:
    var radians;
    var degrees = argument0;
     
    radians = 3.14 * degrees / 180;
    
    return radians;
    Player Object:
    Create Event:
    Code:
    move_speed = 200;
    enable_diagonal_movement = true;
    Step Event:
    Code:
    // Input Setup
    
    var key_right, key_left, key_down, key_up;
    
    key_right = keyboard_check(vk_right);
    key_left = keyboard_check(vk_left);
    key_down = keyboard_check(vk_down);
    key_up = keyboard_check(vk_up);
    
    // Object Position and Direction
    var move_direction, dir, xpos, ypos, hspd, vspd;
    var delta = delta_time / 1000000;
    hspd = move_speed;
    vspd = move_speed;
    xpos = key_right - key_left ;
    ypos = key_down - key_up;
    
    dir = RadianToDegree(key_up, key_down, key_right, key_left);
    move_direction = DegreeToRadian(dir);
    
    // Check if moving
    var moving
    if key_left || key_right || key_down || key_up {
        moving = true;
    } else {
        moving = false;
    }
    
    // Move the object
    var move_xpos, move_ypos;
    move_xpos = abs(xpos);
    move_ypos = abs(ypos);
    
    if moving == true {
        if enable_diagonal_movement == true {
            if move_xpos {
                x += hspd * cos(move_direction) * delta;
            }
            if move_ypos {
                y += vspd * -sin(move_direction) * delta;
            }
        } else {
            // Disable Diagonal Movement
            if move_xpos + move_ypos == 1 {
                x += hspd * cos(move_direction) * delta;
                y += vspd * -sin(move_direction) * delta;
            }
        }
    }
    
    // Keep Object within the map boundries
    x=clamp(x,0,room_width-sprite_width);
    y=clamp(y,0,room_height-sprite_height);


    Tutorial:
    Don't worry about the math conversions, if you wish to learn more about them google is your friend! Now onto the actual tutorial!

    So let me explain a few things as to why I'm doing this way over another way and why I prefer using math over built in functions. So the formula I'm using to move the player is using sin and cos, these are trigonometric functions which are essential lengthdir_x or y, and in my experience, they don't work as well you were to write the code yourself. The same can be said for converting Radians by math vs the built-in function radtodeg.

    So, with that out of the way let's explain the code!
    In our create event we see 2 variables:

    Code:
    move_speed = 200;
    enable_diagonal_movement = true;


    The first variable is our movement speed variable, I found 200 to be a decent speed. The reason it needs to be 200 is because speed is controlled by delta time, so if I were to remove 200 it would move 200 frames in whatever direction per second and that's not good.. 2 is good, but everyone has different hardware and we need to incorporate delta time because of this so it doesn't feel like a drag if, for example, you have 100 enemy objects on the screen. For you, that's probably nothing, but another person is struggling to run the game. This is why we need to use delta time whenever we need to properly time something.

    The second variable is if you don't want diagonal movement in your game, setting it to false will only allow the object to move in 4 directions which is common by default in RPG Maker.

    The step event is pretty basic, we start by creating a few variables for our input which is nice so we don't have to call
    Code:
    keyboard_check(key)
    every time we need to call some input. The next section will set up our horizontal speed and vertical speed as well as our direction. The reason why it's important to split up horizontal and vertical it's so you can control them independently.

    The next step we need to get our directions in radians, we could use point_direction but from my understanding point_direction get the angle in degrees which is not something we want.. it's important to always use radians > degrees it's a rule in game development I won't break. You are welcome to use point direction, which is called by
    Code:
    var move_direction = point_direction(0, 0, xpos, ypos);
    but for this tutorial, I will be sticking with radians.

    Now we can move on to moving our object or player.

    We first need to check if we're moving, this can be done a few ways and one way to do it is by doing the following.
    Code:
    var moving;
    if xpos && ypos {
        moving = true;
    } else {
        moving = false;
    }
    
    The way I check to see if we're moving is to check if any input variable is pressed.
    Code:
    var moving
    if key_left || key_right || key_down || key_up {
       moving = true;
    } else {
       moving = false;
    }
    
    It works the same as the above method but I find it's easier to read, so use which way works for you :)

    The next step is to check if the input is 1 or -1, and 0 if nothing is pressed we do that by inputting the following code.
    Code:
    var move_xpos, move_ypos;
    move_xpos = abs(xpos);
    move_ypos = abs(ypos);
    
    Now that most of our code is written we just check if we want to move diagonally and to check if the object is moving. So, it should look like the following.
    Code:
    var move_xpos, move_ypos;
    move_xpos = abs(xpos);
    move_ypos = abs(ypos);
    
    if moving == true {
        if enable_diagonal_movement == true {
            if move_xpos {
                x += hspd * cos(move_direction) * delta;
            }
            if move_ypos {
                y += vspd * -sin(move_direction) * delta;
            }
        } else {
            // Disable Diagonal Movement
            if move_xpos + move_ypos == 1 {
                x += hspd * cos(move_direction) * delta;
                y += vspd * -sin(move_direction) * delta;
            }
        }
    }
    So that's about it, we checked if we can move and check if we can move diagonally now let me explain what's going on inside the movement portion of this code.

    Code:
    x += hspd * cos(move_direction) * delta;
    
    Because it was important that we kept our project using radians over degrees we use cos, if you don't want to use radians you can use dcos the results should be the same. So we're taking our horizontal speed than we multiply that by cos(move_direction) and then multiply by delta time. And it's the same for our vertical axis but we use the sin function, we add a minus sign in front of sin since game maker interpolates things...

    And that's it, we use the same code for our 4-direction method but we just check if our inputs are 1, so this will limit our movement to 4 directions.

    * More info on trigonometric functions
    https://en.wikipedia.org/wiki/Trigonometric_functions

    If you have any questions don't hesitate to ask :)
     
    Last edited by a moderator: Jun 10, 2019
  2. Juju

    Juju Member

    Joined:
    Jun 20, 2016
    Posts:
    412
    dsin() and dcos() are in-built functions and use degrees as units. Also your RadiansToDegrees() script is a bit confusing as a name, and point_direction() does the same job. If you do want to swap between radians and degrees then GM also has functions for that too: radtodeg() / degtorad()
     
  3. Rukiri

    Rukiri Member

    Joined:
    Sep 25, 2016
    Posts:
    141
    There is nothing wrong with using built in functions as long as *YOU KNOW THE MATH BEHIND THEM! I'm the type of person who would rather know/learn the math behind the function than just use a function because it works.

    I generally write my own functions if I know the math, I was aware of built in functions that basically do the same job :p
     
    NeZvers likes this.
  4. ChessMasterRiley

    ChessMasterRiley Member

    Joined:
    Jun 22, 2016
    Posts:
    40
    I like the idea of learning the concept before implementing your solution, but I would guess the included functions are significantly faster due to being precompiled with the engine. I could be wrong, so it may be worth testing since this is a script that is called every single step.
     

Share This Page

  1. This site uses cookies to help personalise content, tailor your experience and to keep you logged in if you register.
    By continuing to use this site, you are consenting to our use of cookies.
    Dismiss Notice