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

tagwolf

Member
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:

tagwolf

Member
Go for GML. I'll share it on my blog :D
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.
 

Amon

Member
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?
 

tagwolf

Member
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?
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!
 

Sincup

Member
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.
hey man ı just started using GM2 and ı found your controller so amazingly beginner friendly! I would love to see more of your tutorials. And just wanted to ask if you found a solution to this problem.
 
Top