Pixel-Perfect Smooth Movement

J

Jordan Robinson

Guest
Pixel-Perfect Smooth Movement
Jordan Robinson

GM Version: 1.4.1757
Target Platform: ALL
Download: N/A
Links: N/A

Summary:
This tutorial will explain how you can easily make your player move smoothly (i.e. accelerate / decelerate) without any chance of juddering with small rounding errors that can often occur when trying to achieve such a result. The player's x and y position will always be an integer, so this method is great for people making pixel-art games where accuracy in collision checking and movement is important. Also, the theory behind this may be implemented in any game be it top-down, platformer, or any other.

Tutorial:
Like most of my other tutorials, I will be basing this method on the basic platformer code by Shaun Spalding, except with a few changes added in. Here is what we will be starting with:
Code:
//Create Event

grav    = 0.2; //Gravity
speed_x = 0;
speed_y = 0;
Code:
//Step Event

var k_left  = keyboard_check(ord('A'));
var k_right = keyboard_check(ord('D'));
var k_jump  = keyboard_check_pressed(vk_space);

var spd_wanted = 0; //The wanted horizontal speed for this step

if(k_left)
{
    spd_wanted -= 3;
}
if(k_right)
{
    spd_wanted += 3;
}

speed_x = spd_wanted; //Set the horizontal speed based on the wanted speed

speed_y += grav; //Apply gravity

if(k_jump && place_meeting(x, y + 1, obj_solid))
{
    speed_y = -6;
}

//Horizontal collision
if(place_meeting(x + speed_x, y, obj_solid))
{
    while(!place_meeting(x + sign(speed_x), y, obj_solid))
    {
        x += sign(speed_x);
    }
    speed_x = 0;
}
x += speed_x;

//Vertical collision
if(place_meeting(x, y + speed_y, obj_solid))
{
    while(!place_meeting(x, y + sign(speed_y), obj_solid))
    {
        y += sign(speed_y);
    }
    speed_y = 0;
}
y += speed_y;
This code should be put respectively in the create and step events of a player object, and the code assumes that the instances to collide with are called obj_solid.

Now, this system is going to work by having two variables each for horizontal and vertical speed.The first variable will hold the theoretical speed that we want the player to move at. It will be a floating point value, and will simply be used for calculating his acceleration. The second will be the rounded theoretical value (an integer), and will be used for collision checking and movement. We can start with the vertical speed, as this will be the quickest to implement.

I won't be changing the create event, as there is no need to have our rounded variables stored in any more than a local variable. Therefore, we will only be working in the step event from this point forward.

For simplicity sake in my example code, I have removed everything that is irrelevant to the vertical speed.
Code:
speed_y += grav; //Apply gravity

if(k_jump && place_meeting(x, y + 1, obj_solid))
{
    speed_y = -6;
}

var ysp = round(speed_y); //Turn the theoretical value into an integer for collision and movement

//Vertical collision
if(place_meeting(x, y + ysp, obj_solid))
{
    while(!place_meeting(x, y + sign(ysp), obj_solid))
    {
        y += sign(ysp);
    }
    ysp     = 0
    speed_y = 0; //We still have to set the theoretical value to 0 here
}
y += ysp;
That is the vertical speed done. As you can see, the basic flow of the code is to do all of the speed settings with the theoretical code, round it into an integer, and then perform the collision checks and movement at the end. The horizontal movement is going to be the same methodology, except we need to add a small formula to make his speed accelerate and decelerate to make it feel a bit smoother. Again I will only include that code related to his horizontal speed.
Code:
var spd_wanted = 0; //The wanted horizontal speed for this step

if(k_left)
{
    spd_wanted -= 3;
}
if(k_right)
{
    spd_wanted += 3;
}

speed_x += (spd_wanted - speed_x) * 0.1; //Smoothly accelerate / decelerate to the wanted speed.

var xsp = round(speed_x); //Turn the theoretical value into an integer for collision and movement

//Horizontal collision
if(place_meeting(x + xsp, y, obj_solid))
{
    while(!place_meeting(x + sign(xsp), y, obj_solid))
    {
        x += sign(xsp);
    }
    xsp     = 0;
    speed_x = 0; //We still have to set the theoretical value to 0 here
}
x += xsp;
And thats it. If you run the game, your player will be able to move smoothly whilst still sticking to perfect integer positions on the screen.

Hopefully this tutorial was useful to you. Cheers.
 
M

maikerusan

Guest
Hi, my name's Michael. I'm pretty new to programming, taking online courses and following tutorials. Doing my best to make a game in GameMaker until I have the mettle to move over to Unity. Anyways, I've been trying to set up the foundation of my players movement for days now, and can't seem to get it just right. I came across your post last night and thought I finally had the answers...but something is still off. :( I was using Shaun Spauldings code originally, and did my best to adapt yours to mine.

A little context about what I'm trying to make:
A 2D endless runner with an isometric view (kind of) in the style of NES ExciteBike, meaning that the character can change "lanes" by moving up or down (3 lanes) and jump/run over obstacles that spawn randomly both on the ground and in the air.

CREATE CODE
scr_set_character_sprite();//I plan on having multiple "runners" so this sets that.
image_speed = .25;

grav = 1; // I had this set to 0.2 for a while, but that seems to cause all sorts of rounding errors.
hsp = 0;
vsp = 0;
moveSpeed = 4;
grounded = 0;
jumpSpeed_normal = 10; // This was set a bit lower until I upped gravity to 1.
takeoffPoint = 0;

key_up = 0;
key_down = 0;
key_jump = 0;


STEP EVENT CODE
//I haven't messed with the horizontal movement much as this seems to work just fine.
x += 1;
if (x>120)
{
x = clamp(120, 0, room_width); //keeping the runner in the center of the screen.
}


*****As of right now, the vertical movement sort of works. My character will switch lanes with up and down. And if I jump, they return to the same lane, which is what I want. However, the character isn't exactly jumping, but just being teleported up by my jumpSpeed_normal amount, and then brought back down by gravity.

//if (vsp < 10) //I know I'm supposed to work these two lines in here somehow...
//vsp += grav;

var vspINT = round(vsp); //I declared this outside of the following statements so that I could use it within each of them.


//lane changing and jumping while on Top lane/Platform
if ( y = 178)
{

if(key_jump)
{
takeoffPoint = round(y);
vsp = -jumpSpeed_normal;
vspINT = round(vsp);
y += vspINT;

}
/*
if (key_down)
{
y += 6;
}
*/
}

//lane changing and jumping while on Middle Lane/Platform
else if (y = 184)
{

if(key_jump)
{
takeoffPoint = round(y);
vsp = -jumpSpeed_normal;
vspINT = round(vsp);
y+=vspINT;
}
/*
if (key_down)
{
y += 6;
}
if (key_up)
{
y -= 6;
}
*/
}

//lane changing and jumping while on Bottom Lane/Platform
else if (y = 190)
{

if(key_jump)
{
takeoffPoint = round(y);
vsp = -jumpSpeed_normal;
vspINT = round(vsp);
y+=vspINT;
}
/*
if (key_up)
{
y -= 6;
}
*/
}

//Variable jump height
if (vsp < 0) && (!key_jump_held)
{
vsp = max(vsp,0);
}

//Checking to see if the player is midjump
if (y < takeoffPoint)
{
vsp += grav;
vspINT = round(vsp);
y += vspINT;
x -= 1.5; // This is here to add the feeling of air resistance knocking the player back just a bit every time they jump.
}

//player landed, so resetting takeoffPoint
else
takeoffPoint = 0;

*****If I comment out the two lines below, the character properly jumps up and comes back down (no teleporting), but they don't land at exactly the takeoffPoint, which then means they can no longer move up or down or jump again (since the y coordinate doesn't match any of the 3 lanes)
vsp = 0;
vspINT = 0;

//y+=vspINT; //again, not sure exactly how to get this working properly...



*****I originally had this built into the code above, in each lane, but thought it would be nicer to pull it out and just have a script for this. Both ways seem to work identically. Ideally, I'd like the character to be able to switch lanes while in midair as well, but I haven't tried to tackle that yet. My initial thought was that I could add the lane changing code to within the midjump check above and then add or subtrack 6 from "takeoffPoint"
//lane changing
if( ( y == 178 || y == 184) && (key_down))
{
y += 6;
}
else if( ( y == 184 || y == 190) && (key_up) )
{
y -= 6;
}

*****I have tried quite a few other ways to get this working, including enum states for 3 different lanes and calling up each lanes script. But because my lanes were defined by a y coordinate, the character seemed to be clamped there and unable to jump. I also tried using 3 hidden platforms (empty sprites), with the top two being one-way, so that i could "drop down" or "hop up" to them, but I couldn't get that code exactly correct either. And unless that is the way to handle this, I'd rather not have the extra objects in my game that I have to worry about colliding with when I start spawning obstacles. (I can share that code though if that is the better option. I saved it in notepad, just in case, since it was working similarly to the code above).

Sorry this reply is so long, but I really would love some help.
Also, I'd be happy to split up my post comments and actual code like you did above, if you could tell me how to do that.

Thanks in advance!:)
 
J

Jordan Robinson

Guest
Hi, my name's Michael. I'm pretty new to programming, taking online courses and following tutorials. Doing my best to make a game in GameMaker until I have the mettle to move over to Unity. Anyways, I've been trying to set up the foundation of my players movement for days now, and can't seem to get it just right. I came across your post last night and thought I finally had the answers...but something is still off. :( I was using Shaun Spauldings code originally, and did my best to adapt yours to mine.

A little context about what I'm trying to make:
A 2D endless runner with an isometric view (kind of) in the style of NES ExciteBike, meaning that the character can change "lanes" by moving up or down (3 lanes) and jump/run over obstacles that spawn randomly both on the ground and in the air.

CREATE CODE
scr_set_character_sprite();//I plan on having multiple "runners" so this sets that.
image_speed = .25;

grav = 1; // I had this set to 0.2 for a while, but that seems to cause all sorts of rounding errors.
hsp = 0;
vsp = 0;
moveSpeed = 4;
grounded = 0;
jumpSpeed_normal = 10; // This was set a bit lower until I upped gravity to 1.
takeoffPoint = 0;

key_up = 0;
key_down = 0;
key_jump = 0;


STEP EVENT CODE
//I haven't messed with the horizontal movement much as this seems to work just fine.
x += 1;
if (x>120)
{
x = clamp(120, 0, room_width); //keeping the runner in the center of the screen.
}


*****As of right now, the vertical movement sort of works. My character will switch lanes with up and down. And if I jump, they return to the same lane, which is what I want. However, the character isn't exactly jumping, but just being teleported up by my jumpSpeed_normal amount, and then brought back down by gravity.

//if (vsp < 10) //I know I'm supposed to work these two lines in here somehow...
//vsp += grav;

var vspINT = round(vsp); //I declared this outside of the following statements so that I could use it within each of them.


//lane changing and jumping while on Top lane/Platform
if ( y = 178)
{

if(key_jump)
{
takeoffPoint = round(y);
vsp = -jumpSpeed_normal;
vspINT = round(vsp);
y += vspINT;

}
/*
if (key_down)
{
y += 6;
}
*/
}

//lane changing and jumping while on Middle Lane/Platform
else if (y = 184)
{

if(key_jump)
{
takeoffPoint = round(y);
vsp = -jumpSpeed_normal;
vspINT = round(vsp);
y+=vspINT;
}
/*
if (key_down)
{
y += 6;
}
if (key_up)
{
y -= 6;
}
*/
}

//lane changing and jumping while on Bottom Lane/Platform
else if (y = 190)
{

if(key_jump)
{
takeoffPoint = round(y);
vsp = -jumpSpeed_normal;
vspINT = round(vsp);
y+=vspINT;
}
/*
if (key_up)
{
y -= 6;
}
*/
}

//Variable jump height
if (vsp < 0) && (!key_jump_held)
{
vsp = max(vsp,0);
}

//Checking to see if the player is midjump
if (y < takeoffPoint)
{
vsp += grav;
vspINT = round(vsp);
y += vspINT;
x -= 1.5; // This is here to add the feeling of air resistance knocking the player back just a bit every time they jump.
}

//player landed, so resetting takeoffPoint
else
takeoffPoint = 0;

*****If I comment out the two lines below, the character properly jumps up and comes back down (no teleporting), but they don't land at exactly the takeoffPoint, which then means they can no longer move up or down or jump again (since the y coordinate doesn't match any of the 3 lanes)
vsp = 0;
vspINT = 0;

//y+=vspINT; //again, not sure exactly how to get this working properly...



*****I originally had this built into the code above, in each lane, but thought it would be nicer to pull it out and just have a script for this. Both ways seem to work identically. Ideally, I'd like the character to be able to switch lanes while in midair as well, but I haven't tried to tackle that yet. My initial thought was that I could add the lane changing code to within the midjump check above and then add or subtrack 6 from "takeoffPoint"
//lane changing
if( ( y == 178 || y == 184) && (key_down))
{
y += 6;
}
else if( ( y == 184 || y == 190) && (key_up) )
{
y -= 6;
}

*****I have tried quite a few other ways to get this working, including enum states for 3 different lanes and calling up each lanes script. But because my lanes were defined by a y coordinate, the character seemed to be clamped there and unable to jump. I also tried using 3 hidden platforms (empty sprites), with the top two being one-way, so that i could "drop down" or "hop up" to them, but I couldn't get that code exactly correct either. And unless that is the way to handle this, I'd rather not have the extra objects in my game that I have to worry about colliding with when I start spawning obstacles. (I can share that code though if that is the better option. I saved it in notepad, just in case, since it was working similarly to the code above).

Sorry this reply is so long, but I really would love some help.
Also, I'd be happy to split up my post comments and actual code like you did above, if you could tell me how to do that.

Thanks in advance!:)
Hi Michael,

First of all, I'm very sorry for the super late reply. I didn't get any notification that you left this comment and have only just stumbled across it :confused:!!!

Anyway, the problem that you are having reminds me a little bit of this post in the programming Q&A forum. What I would recommend doing is to create a z axis variable in the player which will hold the value of his vertical position. Then when he jumps, adjust this variable (not his y), and manually draw him at position x, y+z. This will simulate jumping without moving his mask and will ensure that he always lands back at his original place by resetting the z variable to 0 whenever it becomes negative.

You can switch lanes by adjusting his y position. And then if his z value is greater than, say 32 pixels, adjust your code so that his collision mask no longer collides with obstacles to simulate jumping over them.

Hopefully this helps get you on the right track!
 
M

maikerusan

Guest
I appreciate you taking the time to reply. I had read through that forum thread that you linked before and thought the z-axis idea might be what I need. I came across something similar elsewhere too, but I couldn't find any examples of specific code showing how to implement the idea. I'm still trying to wrap my head around how to actually code it...

Since it's an endless runner, my player is generally always at the same x position so the y and z are all I really need to figure out.
(Since the main "floor" is isometric, i might add/subtract a bit from the x value when he switches lanes, and maybe even slightly shrink/enlarge the sprite, to give a better idea of the depth.)

So if I have:
jumpSpeed = -7 //to move 7 pixels up when jumping
grav = 0.2; //to bring their z (height) back to zero after jumping
y = 100 //y value at center lane, 6 pixels up for the top lane, 6 down for the bottom lane
zHeight = 0;//the player should be at a height of zero when simply running, correct?

//change height using z-axis variable when jump is pressed
if (jumpkey is pressed)
{
zHeight = y + jumpSpeed;
while(zHeight > 0)
{
draw_sprite(player,x,zHeight)
zHeight += grav;
}

//change lane (y position) when up (or down) is pressed
if (up/down is pressed)
{
y -= 6; // y+=6 for down
}

Am I on the right track here. I haven't had any free time to give any of this a shot, and I'm kinda hoping to have a much better idea of exactly what I need to do before I go chop up my code (again).

Is this isometric view, and use of the z-axis variable, going to make things a lot more difficult when it comes to collision detection? I would like to have both obstacles the player needs to jump over and/or slide under as well as item pickups, all of which the player only runs into when in the lane that obstacle/item is in.

Also, could you tell me how I can put any code i write in a post into a separate code block like you did in your original post? Is there an option within this forum setup? Or did you wrap your text in html you wrote yourself?

Thanks again,
Michael
 
Last edited by a moderator:
J

Jordan Robinson

Guest
I appreciate you taking the time to reply. I had read through that forum thread that you linked before and thought the z-axis idea might be what I need. I came across something similar elsewhere too, but I couldn't find any examples of specific code showing how to implement the idea. I'm still trying to wrap my head around how to actually code it...

Since it's an endless runner, my player is generally always at the same x position so the y and z are all I really need to figure out.
(Since the main "floor" is isometric, i might add/subtract a bit from the x value when he switches lanes, and maybe even slightly shrink/enlarge the sprite, to give a better idea of the depth.)

So if I have:
jumpSpeed = -7 //to move 7 pixels up when jumping
grav = 0.2; //to bring their z (height) back to zero after jumping
y = 100 //y value at center lane, 6 pixels up for the top lane, 6 down for the bottom lane
zHeight = 0;//the player should be at a height of zero when simply running, correct?

//change height using z-axis variable when jump is pressed
if (jumpkey is pressed)
{
zHeight = y + jumpSpeed;
while(zHeight > 0)
{
draw_sprite(player,x,zHeight)
zHeight += grav;
}

//change lane (y position) when up (or down) is pressed
if (up/down is pressed)
{
y -= 6; // y+=6 for down
}

Am I on the right track here. I haven't had any free time to give any of this a shot, and I'm kinda hoping to have a much better idea of exactly what I need to do before I go chop up my code (again).

Is this isometric view, and use of the z-axis variable, going to make things a lot more difficult when it comes to collision detection? I would like to have both obstacles the player needs to jump over and/or slide under as well as item pickups, all of which the player only runs into when in the lane that obstacle/item is in.

Also, could you tell me how I can put any code i write in a post into a separate code block like you did in your original post? Is there an option within this forum setup? Or did you wrap your text in html you wrote yourself?

Thanks again,
Michael
Hi again Michael,

I'll make up a 'demo' code for you here to help explain what I mean. Before I do, the way that you add code as a separate block is to go to the settings at the top of the text editor and select insert > code. This brings up a text box that you can type the code into. Alternatively, you can just write the code inside of the code blocks [*CODE] and [/CODE] (except remove the asterix *).

So first of all, you should create a faux z axis. We can just do this as a variable in the create even of the player:
Code:
//Create Event

axis_z = 0;
From here, we will handle horizontal movement with x, forward/back movement with y, and vertical movement with axis_z. Just like you would in a 3D engine. This means that you only need to worry about the y value when moving the player between lanes. I will just explain vertical movement:

Code:
//Step Event

axis_z += grav; //Apply gravity

if(jumpkey is pressed)
{
    axis_z -= jumpSpeed;
}

if(axis_z < 0)
{
    axis_z = 0; //If axis_z is 0, then the player is on the ground.
}
What you had for changing lane looked like it should work fine. The last bit is to combine the axis_z and y value to ensure the player is drawn at the correct position, since the game is drawn in 2D with only an x and y axis. We can do this by manually drawing the player:

Code:
//Draw Event

draw_sprite(spr_player, image_index, x, y + cos(degtorad(45)) * axis_z);
This bit right here
y + cos(degtorad(45)) * axis_z​
Will add the correct amount to the y value to simulate the player jumping. Because the camera is looking down on the player, if he jumped up it shouldn't move as far with a greater downward angle. This example assumes the camera is looking at 45 degrees, but you could change this to be any angle you like. 0 degrees would be looking straight and 90 would be looking directly down.

The cunning of using this method means that the players collision mask remains at his x and y position, even if he has jumped up. This makes it nice and easy to implement obstacles to either jump over or slide under. You will have to set up your levels in a particular way though: Every obstacle has to have it's collision mask positioned on the "ground". This is because, even when the player is jumping, his collision mask is just going to continue in a straight line. Obstacles designed to be jumped over could be called "obj_obstacle_ground" and those designed to be slid under could be called "obj_obstacle_air". Here is how you would code the collisions:

Code:
//Step Event

if(axis_z < 32) //32 is the height of the obstacles on the ground
{
    if(place_meeting(x, y, obj_obstacle_ground))
    {
        //Collision code
    }
}

if(not sliding)
{
    if(place_meeting(x, y, obj_obstacle_air))
    {
        //Collision code
    }
}
And because the players collision mask is remaining stationary, there shouldn't be any issue with the player colliding with obstacles in other lanes. All you have to ensure is that all collision masks for obstacles are located in the same y position on the screen, regardless of where the sprites are drawn. Then you have to determine whether a collision has actually occurred by comparing the heights of the player and obstacle / pickup.

Does this all make sense? Hopefully it has helped you get a better picture of how to accomplish your goal.

Feel free to private message me as well if you need things explained in more depth! Cheers!!
 
M

Mocgames998

Guest
I wonder. How would slopes work with this?
(Let me see what I can do though...)
 
M

maikerusan

Guest
Jordan, thanks again for all of your help, especially writing out specific lines of code for me. Should that code by itself solve all my vertical/jumping issues? Or was that meant to be implemented into my ground collision checks (like the ones in Shaun Spauldings tuts)? By itself, my character draws itself starting at whatever i originally set the players y value to, and just falls according to gravity, all the way out of the bottom of the game window. :(

i tried incorporating it into a very basic "grounded" setup, but, failed apparently.

Code:
axis_z += grav; //Apply gravity  
     
//on train (grounded)
if(y == 178 || y == 184 || y == 190)
//if(place_meeting(x,y+1,obj_ground))
{
var grounded = 1;
axis_z = 0;
}
else
{
grounded = 0;
axis_z += grav;
}
//jumping
    if(key_jump && grounded == 1)
    {
        axis_z -= jumpSpeed_normal;
    }

// if(grounded == 0)
 
   
    if(axis_z < 0)
    {
        axis_z = 0; //If axis_z is 0, then the player is on the ground.;
    }
One thing I'd like to know is, is this faux-z_axis actually necessary for what i'm trying to accomplish? And, if so, can you explain why my original attempt at using lanes and platforms won't work. I'll try to write up some psuedo code for how I thought it should work and I'll include comments to explain my thought process. My main issue always seemed to be that because my "ground" is isometric, my player's mask is over top of the whole thing, instead of just 1 pixel above it, so my place_meeting(x,y+1,obj_ground) checks never worked the way i wanted (or at least the way I thought they should). Because of that, I thought I could just use "platforms" of just a pixel or two in height, the width of my ground, one for each lane, and have them "hidden" by using a blank sprite drawn under everything else. That way I could also use the same ground collision checks for ground obstacles.
In addition to the code, I'll upload an image to give a better idea of the look of the game. Sorry it's so crappy, but i'm outa town and had to draw it in MS Paint with my touchpad :(

Code:
//Step Event
//After setting the players initial y value to equal the center of lane 2...
//using this lane switching code, i'm pretty sure i should be able to switch lanes midair after jumping. i Dont want lane switching to only be possible when grounded.
if (lane == 1 && downArrow)
{
     y+=6;
     x-=6;//move the player back just a bit to give the illusion of a depth change
     lane = 2; //i could have this as a script call like scr_lane2();
}
else if (lane == 2 && upArrow)
{
     y-=6;
     x+=6;//move the player forward just a bit to give the illusion of a depth change
     lane = 1;
}
else if (lane == 2 && downArrow)
{
     y+=6;
     x back
     lane = 3;
}
if (lane == 3 && upArrow)
{
     y-=6;
     x forward
     lane = 2;
}

if (lane = 1) // I imagine I could refactor this into the above if statement, but I just wanted to make it separate so my idea was clear.
{
     if (place_meeting(x,y+vsp,platform_1) //previously pressed jump, now falling above platform_1, gonna hit it soon..
     {
          while (!place_meeting(x,sign(y+vsp),platform_1)//slowly inching towards platform_1...
          {
               y += vsp;
          }
     vsp = 0; // landed on platform_1 so no more falling...
}

y += vsp;
//Same idea for lane = 2 and lane = 3, using platform_2 and platform_3 respectively. Wouldn't this allow me to press jump (jump code outside of all of the above if statements) and fall down onto whichever lane's platform is below (and not land on a platform below/above). So if i was in lane 1 and simply jumped, I'd land back on platform_1, but if I jumped from platform_1, pressed down, i'd then land on platform two, since lane would be equal to 2, and therefore only the platform_2 collision check would come into play? And if i was on the bottom platform_3, and had some kind of super jump ability, i would still come back down to platform_3, and not land up top on platform_1...

Also, I figured I could set my random obstacles to spawn in specific lanes, named accordingly and it wouldn't be so hard to do something like...
if (player is in lane 2 && Xcollision with lane2_obstacle)
player is injured/dead

else if(player is in lane 2 && Y+1collision with lane2_obstacle)
player is grounded // meaning the player can run on top of obstacles, and jump from one to the next (like blocks in mario)

And then I could change the player's depth value when they switch lanes, so the obstacles they don't run into appear to be passing by above/below them.

So essentially, I could have a script for each lane, and all of the collision checks are handled within each lane. All I'd need to have in the step event would be the code above checking for an up or down arrow press. And I wouldn't have to do any special pixel calculations for where to draw my sprite and when a "collision" occurs.

Without further ado, here's my crappy drawing. The player will be "running" along the rectangular isometric box, with obstacles coming from the right. If you have any questions, I'll do my best to answer. Otherwise, I appreciate you continuing to help me out whenever you have time.

gameProtoImg.png
 
J

Jordan Robinson

Guest
Jordan, thanks again for all of your help, especially writing out specific lines of code for me. Should that code by itself solve all my vertical/jumping issues? Or was that meant to be implemented into my ground collision checks (like the ones in Shaun Spauldings tuts)? By itself, my character draws itself starting at whatever i originally set the players y value to, and just falls according to gravity, all the way out of the bottom of the game window. :(

i tried incorporating it into a very basic "grounded" setup, but, failed apparently.

Code:
axis_z += grav; //Apply gravity
   
//on train (grounded)
if(y == 178 || y == 184 || y == 190)
//if(place_meeting(x,y+1,obj_ground))
{
var grounded = 1;
axis_z = 0;
}
else
{
grounded = 0;
axis_z += grav;
}
//jumping
    if(key_jump && grounded == 1)
    {
        axis_z -= jumpSpeed_normal;
    }

// if(grounded == 0)
 
 
    if(axis_z < 0)
    {
        axis_z = 0; //If axis_z is 0, then the player is on the ground.;
    }
One thing I'd like to know is, is this faux-z_axis actually necessary for what i'm trying to accomplish? And, if so, can you explain why my original attempt at using lanes and platforms won't work. I'll try to write up some psuedo code for how I thought it should work and I'll include comments to explain my thought process. My main issue always seemed to be that because my "ground" is isometric, my player's mask is over top of the whole thing, instead of just 1 pixel above it, so my place_meeting(x,y+1,obj_ground) checks never worked the way i wanted (or at least the way I thought they should). Because of that, I thought I could just use "platforms" of just a pixel or two in height, the width of my ground, one for each lane, and have them "hidden" by using a blank sprite drawn under everything else. That way I could also use the same ground collision checks for ground obstacles.
In addition to the code, I'll upload an image to give a better idea of the look of the game. Sorry it's so crappy, but i'm outa town and had to draw it in MS Paint with my touchpad :(

Code:
//Step Event
//After setting the players initial y value to equal the center of lane 2...
//using this lane switching code, i'm pretty sure i should be able to switch lanes midair after jumping. i Dont want lane switching to only be possible when grounded.
if (lane == 1 && downArrow)
{
     y+=6;
     x-=6;//move the player back just a bit to give the illusion of a depth change
     lane = 2; //i could have this as a script call like scr_lane2();
}
else if (lane == 2 && upArrow)
{
     y-=6;
     x+=6;//move the player forward just a bit to give the illusion of a depth change
     lane = 1;
}
else if (lane == 2 && downArrow)
{
     y+=6;
     x back
     lane = 3;
}
if (lane == 3 && upArrow)
{
     y-=6;
     x forward
     lane = 2;
}

if (lane = 1) // I imagine I could refactor this into the above if statement, but I just wanted to make it separate so my idea was clear.
{
     if (place_meeting(x,y+vsp,platform_1) //previously pressed jump, now falling above platform_1, gonna hit it soon..
     {
          while (!place_meeting(x,sign(y+vsp),platform_1)//slowly inching towards platform_1...
          {
               y += vsp;
          }
     vsp = 0; // landed on platform_1 so no more falling...
}

y += vsp;
//Same idea for lane = 2 and lane = 3, using platform_2 and platform_3 respectively. Wouldn't this allow me to press jump (jump code outside of all of the above if statements) and fall down onto whichever lane's platform is below (and not land on a platform below/above). So if i was in lane 1 and simply jumped, I'd land back on platform_1, but if I jumped from platform_1, pressed down, i'd then land on platform two, since lane would be equal to 2, and therefore only the platform_2 collision check would come into play? And if i was on the bottom platform_3, and had some kind of super jump ability, i would still come back down to platform_3, and not land up top on platform_1...

Also, I figured I could set my random obstacles to spawn in specific lanes, named accordingly and it wouldn't be so hard to do something like...
if (player is in lane 2 && Xcollision with lane2_obstacle)
player is injured/dead

else if(player is in lane 2 && Y+1collision with lane2_obstacle)
player is grounded // meaning the player can run on top of obstacles, and jump from one to the next (like blocks in mario)

And then I could change the player's depth value when they switch lanes, so the obstacles they don't run into appear to be passing by above/below them.

So essentially, I could have a script for each lane, and all of the collision checks are handled within each lane. All I'd need to have in the step event would be the code above checking for an up or down arrow press. And I wouldn't have to do any special pixel calculations for where to draw my sprite and when a "collision" occurs.

Without further ado, here's my crappy drawing. The player will be "running" along the rectangular isometric box, with obstacles coming from the right. If you have any questions, I'll do my best to answer. Otherwise, I appreciate you continuing to help me out whenever you have time.

View attachment 3186
Your idea to have a separate script for each lane is also a perfectly valid idea - what you are essentially talking about is to have a finite state engine for the player. These are really easy to put together using scripts.
You would have 3 states - one for each lane, so depending which lane (state) the player is in, it will execute the relevant code (script). Shaun Spalding has a tutorial on finite state machines which will give you an idea of how to make your own one.

There is really an unlimited number of ways to go about this - you could even combine it with the z axis idea, or find a way to do away with that and just use the x and y axis.

But do check out Shaun Spalding tutorial on State engines, it will prove really helpful!
 
M

maikerusan

Guest
I actually got the idea to use lanes, instead of just adjusting the players y value, after watching those state engine videos by Shaun :) as well as working with states in a text adventure game I made in a C# Unity course i'm taking. Which is what has made it so frustrating since I thought I had a good handle on all of that, but couldn't get it to work properly, so I started trying a whole bunch of other ways. I'm probably biting off more than I can chew as a new developer, new to GameMaker, but I really don't want my endless runner to just be a simple left to right jump and duck. I think the added lanes, ie ExciteBike, really add to the experience visually and with the gameplay, and there are actually very few (imo, quality) isometric 2D runners on either the Android Play store or the Apple App Store, so i'm hoping to be able to help fill that void. So, since I am still a noob, I'd like to stick to the logic that my brain is currently able to comprehend:confused:, which is using lanes, platforms and the x and y axis. Maybe in the future I can get into using the z-axis idea.

I have gone back and re-implemented the lanes and platforms. In doing so, the game actually almost works perfectly, so I might have fixed some minor error I had before. However, I also remembered what my main issue was, and still is, which is actually how I found your post in the first place. Rounding errors. Because all of these lane switches and platforms checks are dependent on my runner being exactly 1 pixel above the "hidden" platforms, I need that perfect integer calculation. But it just doesn't seem to work correctly, at least not all of the time. I have literally compiled and run the game, switched lanes and jumped properly, closed the game, re-run the game, and pressed jump only to have the player land anywhere from 2 to 7 pixels below the platform they should have landed on. (Didn't change a single line of code. Just run, close, run)...that of course screws up everything, as I can no longer jump (not on a platform) and my up and down keys just move me up or down 6 pixels (the distance between platforms).
Before I added the "grounded" variable, I was able to jump infinitely. I noticed that usually just one jump would work ok. But if i did a double, triple, etc jump, the runner would fall below the platform I jumped from. My assumption was that the falling speed was too fast for the code to properly calculate the landing zone, but I could be completely wrong there. I would honestly like my falling speed to be slower, but setting my "grav" variable to any non integer makes the whole rounding error problem even worse.

Would you mind taking a look at my "new" code and telling me where I'm going wrong? Unless there is some inherent problem with gamemaker's calcuations:mad:, I feel like I'm Right there. Like 1 little fix away from finally having it working.:):D

Code:
//initializing global variables in the first room

enum lanes
{
    top,
    middle,
    bottom
}

global.numberOfBoxes = 0;
global.boxX = 160;
global.boxY = 172;

global.runnerX = 75;
global.runnerY = 184;

global.characterExists = 0;
global.character = "0";
global.BoxType = "default";
global.background = 0;
Code:
//Runner Create Event

scr_set_character_sprite();
lane = lanes.middle;
image_speed = .25;

grav = 1;
vsp = 0;
grounded = 1;
jumpSpeed_normal = 15;

key_up = 0;
key_down = 0;
key_jump = 0;
Code:
//Runner Step Event

//Get the player's input
key_rightArrow = keyboard_check(vk_right);//could also use = keyboard_check(ord ("D")); if you wanted to use WASD for movement instead of the arrow keys
key_leftArrow = -keyboard_check(vk_left);//This is negative so that a TRUE boolean will result in -1, to be used with moving left. (Positive 1 for movement to the right)
key_jump = keyboard_check_pressed(vk_space);
key_jump_held = keyboard_check(vk_space);
key_down = keyboard_check_pressed(vk_down);
key_up = keyboard_check_pressed(vk_up);
key_S = keyboard_check(ord('S'));

switch(lane)
{
    case lanes.top: scr_lanes_top(); break;
    case lanes.middle: scr_lanes_middle(); break;
    case lanes.bottom: scr_lanes_bottom(); break;
}
Code:
//This is the script for the middle lane. The scripts for the other two lanes are identical (except, of course, they only check for the up key from the bottom lane, and the down key from the top lane.
//Oh, and check for collisions with "obj_platform1" and "obj_platform3")

//lane changing
if(key_down)
{
    x -= 5;
    y += 6;
    lane = lanes.bottom;
 
}
else if (key_up)
{
    x += 5;
    y -= 6;
    lane = lanes.top;
}

vspINT = round(vsp);

//applying gravity
if (vsp < 10)
vsp += grav;

//Jumping
if(key_jump && grounded)
{
    grounded = 0;
    vsp = -jumpSpeed_normal;
    vspINT = round(vsp); 
}

//Vertical Collision Middle Platform
if(place_meeting(x,y+sign(vspINT),obj_platform2))
{
    while (!place_meeting(x,y+sign(vspINT),obj_platform2))
    {
        y += sign(vspINT);
    }
        vspINT = 0;
        vsp = 0;
        grounded = 1;
}

y+=vspINT;

//animation
if(grounded)
{
    sprite_index = spr_salaryMan;
}
else
{
    if (vspINT < 0)
    sprite_index = spr_RunnerJumping;
    else
    sprite_index = spr_RunnerFalling;
}
As always, thanks for your time.:)
 
Last edited by a moderator:
J

Jordan Robinson

Guest
I actually got the idea to use lanes, instead of just adjusting the players y value, after watching those state engine videos by Shaun :) as well as working with states in a text adventure game I made in a C# Unity course i'm taking. Which is what has made it so frustrating since I thought I had a good handle on all of that, but couldn't get it to work properly, so I started trying a whole bunch of other ways. I'm probably biting off more than I can chew as a new developer, new to GameMaker, but I really don't want my endless runner to just be a simple left to right jump and duck. I think the added lanes, ie ExciteBike, really add to the experience visually and with the gameplay, and there are actually very few (imo, quality) isometric 2D runners on either the Android Play store or the Apple App Store, so i'm hoping to be able to help fill that void. So, since I am still a noob, I'd like to stick to the logic that my brain is currently able to comprehend:confused:, which is using lanes, platforms and the x and y axis. Maybe in the future I can get into using the z-axis idea.

I have gone back and re-implemented the lanes and platforms. In doing so, the game actually almost works perfectly, so I might have fixed some minor error I had before. However, I also remembered what my main issue was, and still is, which is actually how I found your post in the first place. Rounding errors. Because all of these lane switches and platforms checks are dependent on my runner being exactly 1 pixel above the "hidden" platforms, I need that perfect integer calculation. But it just doesn't seem to work correctly, at least not all of the time. I have literally compiled and run the game, switched lanes and jumped properly, closed the game, re-run the game, and pressed jump only to have the player land anywhere from 2 to 7 pixels below the platform they should have landed on. (Didn't change a single line of code. Just run, close, run)...that of course screws up everything, as I can no longer jump (not on a platform) and my up and down keys just move me up or down 6 pixels (the distance between platforms).
Before I added the "grounded" variable, I was able to jump infinitely. I noticed that usually just one jump would work ok. But if i did a double, triple, etc jump, the runner would fall below the platform I jumped from. My assumption was that the falling speed was too fast for the code to properly calculate the landing zone, but I could be completely wrong there. I would honestly like my falling speed to be slower, but setting my "grav" variable to any non integer makes the whole rounding error problem even worse.

Would you mind taking a look at my "new" code and telling me where I'm going wrong? Unless there is some inherent problem with gamemaker's calcuations:mad:, I feel like I'm Right there. Like 1 little fix away from finally having it working.:):D

Code:
//initializing global variables in the first room

enum lanes
{
    top,
    middle,
    bottom
}

global.numberOfBoxes = 0;
global.boxX = 160;
global.boxY = 172;

global.runnerX = 75;
global.runnerY = 184;

global.characterExists = 0;
global.character = "0";
global.BoxType = "default";
global.background = 0;
Code:
//Runner Create Event

scr_set_character_sprite();
lane = lanes.middle;
image_speed = .25;

grav = 1;
vsp = 0;
grounded = 1;
jumpSpeed_normal = 15;

key_up = 0;
key_down = 0;
key_jump = 0;
Code:
//Runner Step Event

//Get the player's input
key_rightArrow = keyboard_check(vk_right);//could also use = keyboard_check(ord ("D")); if you wanted to use WASD for movement instead of the arrow keys
key_leftArrow = -keyboard_check(vk_left);//This is negative so that a TRUE boolean will result in -1, to be used with moving left. (Positive 1 for movement to the right)
key_jump = keyboard_check_pressed(vk_space);
key_jump_held = keyboard_check(vk_space);
key_down = keyboard_check_pressed(vk_down);
key_up = keyboard_check_pressed(vk_up);
key_S = keyboard_check(ord('S'));

switch(lane)
{
    case lanes.top: scr_lanes_top(); break;
    case lanes.middle: scr_lanes_middle(); break;
    case lanes.bottom: scr_lanes_bottom(); break;
}
Code:
//This is the script for the middle lane. The scripts for the other two lanes are identical (except, of course, they only check for the up key from the bottom lane, and the down key from the top lane.
//Oh, and check for collisions with "obj_platform1" and "obj_platform3")

//lane changing
if(key_down)
{
    x -= 5;
    y += 6;
    lane = lanes.bottom;
 
}
else if (key_up)
{
    x += 5;
    y -= 6;
    lane = lanes.top;
}

vspINT = round(vsp);

//applying gravity
if (vsp < 10)
vsp += grav;

//Jumping
if(key_jump && grounded)
{
    grounded = 0;
    vsp = -jumpSpeed_normal;
    vspINT = round(vsp);
}

//Vertical Collision Middle Platform
if(place_meeting(x,y+sign(vspINT),obj_platform2))
{
    while (!place_meeting(x,y+sign(vspINT),obj_platform2))
    {
        y += sign(vspINT);
    }
        vspINT = 0;
        vsp = 0;
        grounded = 1;
}

y+=vspINT;

//animation
if(grounded)
{
    sprite_index = spr_salaryMan;
}
else
{
    if (vspINT < 0)
    sprite_index = spr_RunnerJumping;
    else
    sprite_index = spr_RunnerFalling;
}
As always, thanks for your time.:)
I think I MIGHT have found the problem ;)

In your codes, you are only rounding the vertical speed when you press the jump key. You just need to move that rounding code outside of the if statement so it does it every step. That should solve all your problems ;) but let me know if it doesn't and i'll take another look.

I see you also are doing it before applying gravity > that's unnecessary. You can just do it once after the jumping code and it will set everything straight.
 
M

maikerusan

Guest
Unfortunately that didn't seem to make a difference. However, I think I figured it out!! I was screwing around with my character mask to see if there was some problem there and decided to take a step back. I changed my character sprite to a simple 32x32 square. GameMaker auto-set its mask to a Bottom of 31, and voila, no problems jumping!...I checked my runner sprites and the runner sprite was set to a bottom of 32, and the falling sprite was set to a bottom of like 27. I set both of those to 31 and it works! :D:D:D But I was still having switching trouble. It looked to be off by a pixel when moving up. So I left the "key_down" code to "y +=6" but changed the "key_up" code to "y -= 7" and Bam, works beautifully!! Woohooo!...Here's my updated code for the top lane script. I added a grounded if statement to the top, as well as collision checks for a newly added "DangerBox" (comments below:().

Code:
if (place_meeting(x,y+1,obj_platform1) || place_meeting(x,y+1,obj_DangerBox))
grounded = 1;
else
grounded = 0;

//lane changing
if(key_down)
{
    x -= 5;
    y += 6;
}
// the middle and bottom platforms have the key_up check with y-=7 here (as well as an "x +=5)

//applying gravity
if (vsp < 10)
vsp += grav;

//Jumping
if(key_jump && grounded)
{
    vsp = -jumpSpeed_normal;  
}

vspINT = round(vsp);  

//Vertical Collision Top Platform
if(place_meeting(x,y+sign(vspINT),obj_platform1))
{
    while (!place_meeting(x,y+sign(vspINT),obj_platform1))
    {
        y += sign(vspINT);
    }
        vspINT = 0;
        vsp = 0;
}

y+=vspINT;

//New DangerBox Collision Checks

//Horizontal Collision with Obstacle
if(place_meeting(x+1,y,obj_DangerBox))
hspeed = -3;

//Vertical Collision with Obstacle (on top of box)
if(place_meeting(x,y+sign(vspINT),obj_DangerBox))
{
    while (!place_meeting(x,y+sign(vspINT),obj_DangerBox))
    {
        y += sign(vspINT);
    }
        vspINT = 0;
        vsp = 0;
}

//animation
if(grounded)
{
    sprite_index = spr_salaryMan;
}
else
{
    if (vspINT < 0)
    sprite_index = spr_RunnerJumping;
    else
    sprite_index = spr_RunnerFalling;
}
1. While I seemed to figure out the biggest problem, there still seems to be a tiny issue. Switching lanes while in mid air only seems to work for downward movement. If I jump and press up though, my character ends up landing below the actual platform I jumped from (not even under the one above as you might expect). Any ideas?:confused: Restricting lane switching to only when grounded is an option, but I'd really like to avoid that.

2. Since I almost have this working, I finally started messing around with some obstacles. You can see that I have a horizontal and vertical collision check in the above code for my cube-shaped (iso) "DangerBox" and they are partially working. If I'm in the middle lane, I don't hit the DangerBoxes in the top lane, so that's perfect.:rolleyes: The horizontal collision check seems to work fine. My player hits the box and get's carried backwards with it (the DangerBox's hspeed is also -3). However, when I jump on top of the box, my character lands on it, and gets stuck there (and carried off screen with the DangerBox) instead of running over it. If I turn off the horizontal collision check, then he "runs over the box" as I'd like (can't jump off of it), but he doesn't land exactly on the platform, so then my jumping code is broke.:mad: I think maybe there's something wrong with my vertical collision check code. I changed the sprite from the "3D" DangerBox to a normal 32x32 square and my character seems to land a couple pixels under the top of the box instead of right on top. I'm hoping that fixing that will also cause the player to land properly on the platform. And do you think that explains why my character gets stuck on top of the 3D box when the horizontal collision check is turned on?
 
J

Jordan Robinson

Guest
Make sure that the origin of the sprite is centre relative to the 32x32 mask. That's about all I can offer sorry, but let me know if that makes a difference!
 
C

carzo

Guest
Hi friend,

firstly thank you very much for making this tutorial.

I have been trying many different ways to accomplish aceleration and deceleration but without any good result.

I would like to have a little help, I am new programming and I have tried your code on my project but it provides no aceleration.



I think the problem lies within this lines:





var spd_wanted = 0; //The wanted horizontal speed for this step

if(k_left)
{
spd_wanted -= 3;
}
if(k_right)
{
spd_wanted += 3;
}

speed_x = spd_wanted; //Set the horizontal speed based on the wanted speed
speed_x += (spd_wanted - speed_x) * 0.1; //Smoothly accelerate / decelerate to the wanted speed.




If speed wanted is 3 and then speedX is 3 also, it will always provide 0*0.1=0




Thanks for help friends
 
J

Jordan Robinson

Guest
Hi friend,

firstly thank you very much for making this tutorial.

I have been trying many different ways to accomplish aceleration and deceleration but without any good result.

I would like to have a little help, I am new programming and I have tried your code on my project but it provides no aceleration.



I think the problem lies within this lines:





var spd_wanted = 0; //The wanted horizontal speed for this step

if(k_left)
{
spd_wanted -= 3;
}
if(k_right)
{
spd_wanted += 3;
}

speed_x = spd_wanted; //Set the horizontal speed based on the wanted speed
speed_x += (spd_wanted - speed_x) * 0.1; //Smoothly accelerate / decelerate to the wanted speed.




If speed wanted is 3 and then speedX is 3 also, it will always provide 0*0.1=0




Thanks for help friends
Hi there,

Well the spd_wanted variable sets the maximum speed that you want the player to move at. So if spd_wanted is 3, and speed_x is also 3 then the acceleration should be 0 so that it remains at 3. If you want the player to accelerate to a greater speed, change the spd_wanted to a larger value such as 5 or 6 and change the decimal 0.1 to a lower number such as 0.01 and the acceleration / deceleration will be more prominent. The other issue in your code is that just before the calculation for acceleration, you are setting the speed_x to speed_wanted. You can remove that line.
 
C

carzo

Guest
Hello Jordan Robinson,

firstly thank you very much for your reply, but I ended up creating my own aceleration system. Now that we are here, you can take a look at it. I would like to know your opinion about the logic i have decided to use for my game and if you think it could cause problems in the future. So far, it works very good:


//deceleration
if speed_x < 0 && !key_left
{speed_x += 0.15}
if speed_x > 0 && !key_right
{speed_x -= 0.15}

// basic movement
speed_x += (key_right - key_left)* 0.2
if speed_x < -speed_x_max
{speed_x = -speed_x_max}
if speed_x > +speed_x_max
{speed_x = +speed_x_max}
 
J

Jordan Robinson

Guest
Hello Jordan Robinson,

firstly thank you very much for your reply, but I ended up creating my own aceleration system. Now that we are here, you can take a look at it. I would like to know your opinion about the logic i have decided to use for my game and if you think it could cause problems in the future. So far, it works very good:


//deceleration
if speed_x < 0 && !key_left
{speed_x += 0.15}
if speed_x > 0 && !key_right
{speed_x -= 0.15}

// basic movement
speed_x += (key_right - key_left)* 0.2
if speed_x < -speed_x_max
{speed_x = -speed_x_max}
if speed_x > +speed_x_max
{speed_x = +speed_x_max}
Hi again, your own system, if it does what you want for your game, is perfectly good too. Its not too complex which means if it does cause issues later it won't be difficult to change it to work for the new situation.
 
O

Oliver

Guest
I love you, this might be too old but I was looking for a pixel perfect movement and this worked wonders, specially for the vertical movement, I was getting frustrated at how my player floated for a few miliseconds before hitting the ground, making jumps so hard to time and it looked very sloopy.

I have one question though, I don't see the difference between the first code you posted and the one you added the smooth acceleration and rounding, I mean I know what the difference in code is, but gaomeplay wise to me it looks the same, feels the same and works great without adding the extra lines on the corrected version.
 
Last edited by a moderator:
J

Jordan Robinson

Guest
I love you, this might be too old but I was looking for a pixel perfect movement and this worked wonders, specially for the vertical movement, I was getting frustrated at how my player floated for a few miliseconds before hitting the ground, making jumps so hard to time and it looked very sloopy.

I have one question though, I don't see the difference between the first code you posted and the one you added the smooth acceleration and rounding, I mean I know what the difference in code is, but gaomeplay wise to me it looks the same, feels the same and works great without adding the extra lines on the corrected version.
The difference is very subtle visually - you will notice pixels warping whenever the characters position is not a whole number. You will notice the difference between the two if you have the character draw his x and y coordinate. the y position after jumping will often be x.xxx.... and with the acceleration his x position will also be x.xxxx.... The purpose of the second lot of codes means that if you ever use the characters position in a calculation (for pixel based games where this would be important) you will always be dealing with a whole number value - even if you want to implement acceleration. This works because instead of his speed at a given frame accelerating by: 0, 0.25, 0.5, 0.75, 1 etc. It would be 0, 0, 1, 1, etc giving you the same effect visually but avoiding decimals which may cause issues.
 
P

pixelatedivan

Guest
Personally, I think lerp is better for accelerate and decelerate
 
Top