True Perfect Platformer Controls (input buffer, air control, diagonal collision, etc)

Discussion in 'Tutorials' started by tagwolf, Jul 22, 2019.

  1. tagwolf

    tagwolf Member

    Joined:
    Aug 6, 2016
    Posts:
    59
    GM Version: Studio 2
    Target Platform: Windows
    Download: see code below
    Links: NA

    Summary:
    I've noticed pretty much no platformer tutorials account for tweaking control feel, input buffering, air control, etc. So I'm tossing it here hoping it will be of help to someone.

    Features:
    * Move input cancellation
    * Pixel perfect collision avoidance and diagonal evaluation
    * Ground braking and acceleration
    * Air braking and acceleration
    * Ledge buffering
    * Jump input buffering
    * Adjustable everything (speed, gravity, controls, etc etc.)
    * Basic debug console info to help tweaking
    * No gamemaker physics (all basic math, unless hspeed/vspeed count as gm physics. I might switch to x/y operators someday but it seems like a pain in the ass as you'd need to track current speed ongoing anyways so why not use the built-ins and kill 2 birds with one stone)
    * Alt controls

    Todo: gamepad inputs (this should work fine for digital assignments now. Might need to tweak analog left/right to account for deadzone. But should basically just need to be like 'if absolute (abs) value > 0 + deadzone then move total is 1 or -1')
    Some refactoring can probably be done.

    Potential Bug: See nothing is perfect ;). While the ledge buffer exception prevents most types of "wall jumping" is does not prevent all unless you add logic to differentiate a floor vs a wall. Basically you need to make the step event aware that the player is indeed over valid ground before allowing them to jump (while accounting for ledge and input buffers.. For many peoples games this probably wont matter. But if you have more intricate level design I'm just making you aware to account for it. I am still working on a "one size fits all" solution to this. I know I could just use different block types for floor vs walls. But I like a challenge. Definitely open to input on this.

    Create 2 objects. obj_Player and obj_Block.

    obj_Block is used for floor and walls, etc.

    obj_Player
    Create event
    Code:
    /// @description Init Vars
    
    // Player Vars
    move_rate = 6;
    jump_rate = 4;
    jump_buffer_count = 0;
    jump_buffer = 10;
    jump_ledge_buffer = 10;
    accel_rate_ground = 0.3;
    accel_rate_air = 0.2;
    brake_rate_ground = 0.4;
    brake_rate_air = 0.2;
    gravity_vspeed = 8;
    gravity_rate = 0.2;
    
    // Controls
    control_left = ord("A");
    control_right = ord("D");
    control_jump = vk_space;
    // Alternate Controls
    control_left_alt = vk_left;
    control_right_alt = vk_right;
    control_jump_alt = vk_up;
    
    Step event
    Code:
    /// @description Control Player
    
    // Get input
    move_input_total = 0;
    if keyboard_check(control_left) || keyboard_check(control_left_alt) { move_input_total -= 1; }
    if keyboard_check(control_right) || keyboard_check(control_right_alt) { move_input_total += 1; }
    // Jump input buffer
    if keyboard_check_pressed(control_jump) || keyboard_check_pressed(control_jump_alt)
    {
       jump_buffer_count = 0;
    }
    // Check / increment jump buffer
    if jump_buffer_count < jump_buffer
    {
       jump_buffer_count++;
    }
    
    // No move input, brake
    // On ground
    if place_meeting(x, y + 1, obj_Block)
    {
       if move_input_total == 0 || (hspeed * move_input_total < 0)
       {
           hspeed -= hspeed * brake_rate_ground;
       }
    }
    // In air
    if !place_meeting(x, y + 1, obj_Block)
    {
       if move_input_total == 0 || (hspeed * move_input_total < 0)
       {
           hspeed -= hspeed * brake_rate_air;
       }
    }
    
    // Move player and clamp value to max
    // On ground
    if place_meeting(x, y + 1, obj_Block)
    {
       hspeed += move_input_total * accel_rate_ground;
    }
    // In air
    if !place_meeting(x, y + 1, obj_Block)
    {
       hspeed += move_input_total * accel_rate_air;
    }
    // Limit speed to move_rate
    hspeed = clamp(hspeed, -move_rate, move_rate);
    
    // Gravity
    if (vspeed < gravity_vspeed) || !place_meeting(x, y + 1, obj_Block)
    {
       vspeed += gravity_rate;
    }
    
    // Jump if on / close to ground
    // Account for ledge buffer but prevent wall jumping
    if (place_meeting(x + jump_ledge_buffer, y + 1, obj_Block) && jump_buffer_count < jump_buffer && !place_meeting(x + 1, y, obj_Block)) ||
       (place_meeting(x - jump_ledge_buffer, y + 1, obj_Block) && jump_buffer_count < jump_buffer && !place_meeting(x - 1, y, obj_Block))
    {
       vspeed = 1 * -jump_rate;
    }
    
    // Collisions and stuck/overlap prevention
    if (place_meeting(x + hspeed, y, obj_Block)) {
       while (!place_meeting(x + sign(hspeed), y, obj_Block)) {
           x += sign(hspeed);
       }
       hspeed = 0;
    }
    if (place_meeting(x, y + vspeed, obj_Block)) {
       while (!place_meeting(x, y + sign(vspeed), obj_Block)) {
           y += sign(vspeed);
       }
       vspeed = 0;
    }
    // Diagonal
    if (place_meeting(x + hspeed, y + vspeed, obj_Block)) {
       while (!place_meeting(x + sign(hspeed), y + sign(vspeed), obj_Block)) {
           x += sign(hspeed);
           y += sign(vspeed);
       }
       hspeed = 0;
       vspeed = 0;
    }
    
    // Speed debug
    show_debug_message("HInput: " + string(move_input_total));
    show_debug_message("Jump Buffer Count: " + string(jump_buffer_count));
    show_debug_message("HSpeed: " + string(hspeed));
    show_debug_message("VSpeed: " + string(hspeed));
    
     
    Last edited: Aug 5, 2019
  2. Mert

    Mert Member

    Joined:
    Jul 20, 2016
    Posts:
    370
    Simple & beautiful. When you said * No gamemaker physics, I wished you'd do it with physics.
     
    tagwolf likes this.
  3. Amon

    Amon Member

    Joined:
    Sep 13, 2016
    Posts:
    250
    This is cool and simple to understand. Thank you.
     
    tagwolf likes this.
  4. tagwolf

    tagwolf Member

    Joined:
    Aug 6, 2016
    Posts:
    59
    I'm making one by request for you <3.

    Wondering if I should do DnD or GML for this. I'll probably do GML.
     
  5. Mert

    Mert Member

    Joined:
    Jul 20, 2016
    Posts:
    370
    Go for GML. I'll share it on my blog :D
     
  6. tagwolf

    tagwolf Member

    Joined:
    Aug 6, 2016
    Posts:
    59
    https://forum.yoyogames.com/index.php?threads/basic-physics-platformer-controls.65895/

    Don't think it's published yet. But if you have input when it lets you see it let me know. I don't have a ton of experience using build-in physics in this engine so I'm struggling getting the right settings for density, gravity, pixels to meters, and kinematic is not working for me the same way it does in Unity. BUT, all that said, I posted a good foundation I think to get players up and running. It still feels more "floaty" than I'd like in my non-physics controls but I think that's mostly just tweaking a bit more.
     
  7. Amon

    Amon Member

    Joined:
    Sep 13, 2016
    Posts:
    250
    Is it possible to add to the code the ability to move/jump etc using buttons for mobile dev? If I had three images, one each for left and right and a jump button, how easy would it be to make this mobile ready?

    edit>> What about adding oneway jumps or jumping down from a platform to one beneath?
     
  8. tagwolf

    tagwolf Member

    Joined:
    Aug 6, 2016
    Posts:
    59
    Hmm, well to replace certain keyboard checks you'd need to utilize the gestures input types or create an on screen controller, depending on what you wanted to do. When you launch GMS2 I believe there's a gestures tutorial available from the home screen. I'd do that first. After that, plugging in or adapting the code above should be pretty trivial and require altering just a few lines.

    As for oneway jumps and jumping down, this tutorial doesn't cover that. But I'd just make some additional step code that checked for keyboard down (or S) or the mobile swipe down gesture and the jump button pressed at the same time to turn off collisions for that jump until the next collision. Without knowing more about your app I can't provide exact advice yet, but I'd do those tutorials and then if you have a question ask in the programming forum. I'll keep an eye out!
     

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