GMS 2 [SOLVED] pixel-perfect elevator

Discussion in 'Programming' started by xorphinindi, Jul 12, 2019.

  1. xorphinindi

    xorphinindi Member

    Joined:
    Sep 26, 2016
    Posts:
    56
    Hi, thank you for taking some time to look at my thread. I'm making a 2D platformer and am having a problem which, as narrowly as I can identify, is related to my vertical collisions. The problem, I think, is this: When I have my character use a vertical (player-controlled) elevator, the player-object, and the elevator-object do not move together in a pixel-perfect manner; which visually is not a problem except when starting and stopping their paths. Their motion results in single-pixel variances which either causes the two objects to clip together or float apart depending on the direction of the elevator. This can cause the player object to recognize a falling state and enter a falling animation for a single frame, or, for a reason which I cannot identify, reverse the image_xscale until a new command is given, including commands which do not affect image_xscale. A video may be more useful, which hopefully you can see here:
    https://drive.google.com/file/d/1hqX_jx990dZaxIEDKk8sEUtLiqA3eadf/view
    I believe this problem could be solved if the two objects were able to move together in a pixel-perfect manner, but I don't know how to make that happen or if it is even possible. Again, thanks for looking at this. Let me know if I can provide more useful bits of code. And sorry for inefficient code, especially the elevator.
    I have also discovered that the direction of the image_xscale is consistent with the vertical direction input. Moving the elevator up results in the character facing left and down faces it right. I still don't know why though.

    PLAYER OBJECT REGULAR MOVEMENT CODE
    Code:
    // calculate vertical movement
    var grv_final = grv;
    var vsp_max_final = vsp_max;
    
    if (onWall != 0) && (vsp > 0)
    {
        grv_final = grv_wall;
        vsp_max_final = vsp_max_wall;
    }
    
    vsp += grv_final;
    vsp = clamp(vsp, -vsp_max_final, vsp_max_final);
    
    // Face the player in the right direction
    if (hsp != 0) {image_xscale = sign(hsp);}
    
    RELAVENT PLAYER OBJECT COLLISION CODE
    Code:
    // vertical collision
    if (place_meeting(x, y+vsp, par_wall))
    {
        while(!place_meeting(x, y+sign(vsp), par_wall))
        {
                y += sign(vsp);
        }
        vsp = 0;
    }
    y += vsp;
    
    // if the player is in the air, change the animations
    onGround = place_meeting(x, y+1, par_wall);
    onWall = place_meeting(x+1, y, par_wall) - place_meeting(x-1, y, par_wall);
    
    if (!onGround && !onWall)
    {
            if(sign(vsp) < 0)
            {
                sprite_index = spr_player_jump;
                image_index = 1;
            }
      
            if(sign(vsp) > 0)
            {
                sprite_index = spr_player_fall;
                image_index = 1;
            }
    }
    
    ELEVATOR CODE (WHERE THE ELEVATOR AND PLAYER ARE MOVED --- NEAR THE BOTTOM)
    Code:
    var onVator = (place_meeting(x, y-64, obj_player));
    var inVator = (place_meeting(x, y, obj_player));
      
    if (onVator && (goingUp == true || goingDown == true))
    {
        obj_player.vsp += elevateSpd;
        obj_player.canJump = 0;
    }
    
    if(onVator && (goingDown) && (speed != 0)){
        obj_player.y -= 8;
    }
    
    // Checks if the elevator is in the right place and stops it if it is.
    if (point_distance(x, y, x, subLevel[0]) < elevateSpd && floorDest = subLevel[0]){
        speed = 0;
        move_snap(x, 32);
        goingUp = false;
        goingDown = false;
      
        if(inVator){
            obj_player.y -= 1;
        }
    }
    if (point_distance(x, y, x, subLevel[1]) < elevateSpd && floorDest = subLevel[1]){
        speed = 0;
        move_snap(x, 32);
        goingUp = false;
        goingDown = false;
      
        if(inVator){
            obj_player.y -= 1;
        }
    }
    if (point_distance(x, y, x, subLevel[2]) < elevateSpd && floorDest = subLevel[2]){
        speed = 0;
        move_snap(x, 32);
        goingUp = false;
        goingDown = false;
      
        if(inVator){
            obj_player.y -= 1;
        }
    }
    
    // Sub-Level:0
    if (onVator && floorDest == subLevel[0] && obj_player.key_down && goingDown == false){ /* Do nothing */ }
    
    if (onVator && floorDest == subLevel[0] && obj_player.key_up && goingUp == false){
        goingUp = true;
        goingDown = false;
        floorDest =  subLevel[1];
    }
    
    // Sub-Level:1
    if (onVator && floorDest == subLevel[1] && obj_player.key_down && goingDown == false){
        goingUp = false;
        goingDown = true;
        floorDest =  subLevel[0];
    }
    if (onVator && floorDest == subLevel[1] && obj_player.key_up && goingUp == false){
        goingUp = true;
        goingDown = false;
        floorDest =  subLevel[2];
    }
    
    // Sub-Level:2
    if (onVator && floorDest == subLevel[2] && obj_player.key_down && goingDown == false){
        goingUp = false;
        goingDown = true;
        floorDest =  subLevel[1];
    }
    if (onVator && floorDest == subLevel[2] && obj_player.key_up && goingUp == false){ /* Do nothing */ }
    
    
    
    // move the elevator and player
    if (floorDest == subLevel[0] && y != 1248){
      
        if(onVator && goingDown){ obj_player.y += 10; }
      
        if (point_distance(x, y, x, subLevel[0]) > 4){
            move_towards_point(x, subLevel[0], elevateSpd);
        }
    }
    
    if (floorDest == subLevel[1] && y != 832){
      
        if(onVator && goingDown){ obj_player.y += 10; }
        if(onVator && goingUp)  { obj_player.y -= 10; }
      
        if (point_distance(x, y, x, subLevel[1]) > 4){
            move_towards_point(x, subLevel[1], elevateSpd);
        }
    }
    
    if (floorDest == subLevel[2] && y != 416){
      
        if(onVator && goingUp)  { obj_player.y -= 8; }
      
        if (point_distance(x, y, x, subLevel[2]) > 4){
            move_towards_point(x, subLevel[2], elevateSpd);
        }
    } 
    
     
    Last edited: Jul 12, 2019
  2. the_dude_abides

    the_dude_abides Member

    Joined:
    Jun 23, 2016
    Posts:
    546
    I would think the simplest way to do an elevator is to remove player control once they've activated it. Press a button to "start" the elevator, press up or down to begin it moving, and then remove control of any sort until it reaches the previous or next level.

    Are they in contact with an elevator? yes / no
    If they are - get id of elevator, and then check: is it active? yes / no
    if it's active - set a variable to true within any code that deals with player movement, and remove control. Then make the player stick to the elevator until it is done.

    This is what you are doing...kind of, but to me it seems like you've approached it from the wrong way.

    You maybe should be linking the players y position to the elevators y position in the end step event. As pseudo code:

    if is_using_elevator
    {
    y = obj_elevator.y + whatever distance is appropriate; // if this length is negative it will still place them above the y point when added. If its positive it still places them below.
    }

    The elevator reaches its destination, and control is returned to the player. In your code it would be something like:

    Code:
    if (onVator && (goingUp  || goingDown)) //  is in collision with an instance of the elevator, and elevator is active
    {obj_player.vsp = 0; // is not falling, and vsp is not being used in any way to to control the players y position during this action
     obj_player.canJump = false; // can't move
     obj_player.y = obj_elevator.y + whatever distance is appropriate for the player sprite / elevator origin point etc ; // replace the object reference I use here, by obtaining the id of the elevator through a collision function that returns the id instead of place meeting
    }
    It seems to me that you wouldn't need anything more than that. So you'd essentially be merging this bit (which to my mind has some unnecessary elements)
    Code:
    if (onVator && (goingUp == true || goingDown == true))
    {
        obj_player.vsp += elevateSpd; // not a good way to manipulate the player, compared to directly linking its position to the elevators
        obj_player.canJump = 0;
    }
    
    if(onVator && (goingDown) && (speed != 0)) // redundant check with the change above - once the elevator stops moving, so does the player
    {
        obj_player.y -= 8; // this wouldn't be needed if using the simplified version above, and is a bit of an arbitrary value
    }
    into one piece of code that manipulates the player quite precisely to the elevators position.
     
    Phil Strahl likes this.
  3. xorphinindi

    xorphinindi Member

    Joined:
    Sep 26, 2016
    Posts:
    56
    Thank you for the help!
    I agree it would be simpler to remove player control here, but I don't want to. What you said was still very helpful, because I got it working the way I wanted. I think linking both objects' y positions in the end-step event is part of what fixed it. Additionally, I traded out some of my magic numbers for variables. Mainly, instead of just giving the player y value a + or- some distance when the elevator stops, it now has a set y value for where it ought to be (half the player sprite distance off the ground since it's centered. IE floor2 is set to 416 so the player's floor2 value is offset 37 pixels at 379). Much thanks!

    ELEVATOR STEP
    Code:
    onVator = (place_meeting(x, y-2, obj_player));
    inVator = (place_meeting(x, y, obj_player));
    
    // Sub-Level:0
    if (onVator                   &&
        floorDest == subLevel[0]  &&
        obj_player.key_down       &&
        goingDown == false        &&
        goingUp == false)
        { /* Do nothing */ }
    
    if (onVator                  &&
        floorDest == subLevel[0] &&
        obj_player.key_up        &&
        goingUp == false         &&
        goingDown == false)
        {
            goingUp = true;
            goingDown = false;
            floorDest =  subLevel[1];
    }
    
    // Sub-Level:1
    if (onVator                  &&
        floorDest == subLevel[1] &&
        obj_player.key_down      &&
        goingDown == false       &&
        goingUp == false)
        {
            goingUp = false;
            goingDown = true;
            floorDest =  subLevel[0];
    }
    if (onVator                  &&
        floorDest == subLevel[1] &&
        obj_player.key_up        &&
        goingUp == false         &&
        goingDown == false)
        {
            goingUp = true;
            goingDown = false;
            floorDest =  subLevel[2];
    }
    
    // Sub-Level:2
    if (onVator                  &&
        floorDest == subLevel[2] &&
        obj_player.key_down      &&
        goingDown == false       &&
        goingUp == false)
        {
            goingUp = false;
            goingDown = true;
            floorDest =  subLevel[1];
    }
    if (onVator                  &&
        floorDest == subLevel[2] &&
        obj_player.key_up        &&
        goingUp == false         &&
        goingDown == false)
        { /* Do nothing */ }
    
    ELEVATOR END-STEP
    Code:
    // Checks if the elevator is in the right place and stops it if it is.
    if (point_distance(x, y, x, subLevel[0]) < elevateSpd && floorDest = subLevel[0]){
        speed = 0;
        move_snap(x, 32);
        goingUp = false;
        goingDown = false;
        if (onVator || inVator){
           
            obj_player.y = SL0_player;
           
            if(goingUp || goingDown){
                obj_player.y = SL0_player;
            }
        }
    }
    
    if (point_distance(x, y, x, subLevel[1]) < elevateSpd && floorDest = subLevel[1]){
        speed = 0;
        move_snap(x, 32);
        goingUp = false;
        goingDown = false;
        if (onVator || inVator){
           
            obj_player.y = SL1_player;
           
            if(goingUp || goingDown){
                obj_player.y = SL1_player;
            }
        }
    }
    
    if (point_distance(x, y, x, subLevel[2]) < elevateSpd && floorDest = subLevel[2]){
        speed = 0;
        move_snap(x, 32);
        goingUp = false;
        goingDown = false;
        if (onVator || inVator){
           
            obj_player.y = SL2_player;
           
            if(goingUp || goingDown){
                obj_player.y = SL2_player;
            }
        }
    }
    
    // move the elevator and player
    if (floorDest == subLevel[0] && y != 1248){
       
        if(onVator && goingDown){ obj_player.y = y-37; }
       
        if (point_distance(x, y, x, subLevel[0]) > 4){
            move_towards_point(x, subLevel[0], elevateSpd);
        }
    }
    
    if (floorDest == subLevel[1] && y != 832){
       
        if(onVator && goingDown){ obj_player.y = y-37; }
        if(onVator && goingUp)  { obj_player.y = y-37; }
       
        if (point_distance(x, y, x, subLevel[1]) > 4){
            move_towards_point(x, subLevel[1], elevateSpd);
        }
    }
    
    if (floorDest == subLevel[2] && y != 416){
       
        if(onVator && goingUp)  { obj_player.y = y-37; }
       
        if (point_distance(x, y, x, subLevel[2]) > 4){
            move_towards_point(x, subLevel[2], elevateSpd);
        }
    }  
    
     
  4. the_dude_abides

    the_dude_abides Member

    Joined:
    Jun 23, 2016
    Posts:
    546
    It's good that you solved it :)

    I'm curious about some of the details.....

    Are all the floors the same distance apart? You have a lot of info covering specific y positions, and I'm wondering if that could be condensed. If the floors are all the same distance then I think it could be - which would make your code more readable, and most likely more efficient as it has less to check. If you are going on a set distance, then as long as I have figured this correctly it should remove the need for defining and checking all the level points.

    If the level points are different lengths (which could be the case as you seem to have an array in there) then it would need a bit of changing, but I'd still reckon you could do it in an easier way than your current method. I will also include how I'd do this if you are using an array with differently spaced values for the floors.

    Say for example you just had three variables permanently held (set in the create event): the first is the lowest y point that the elevator goes to, the second is the highest, and the third is the distance between floors (this is assuming that they are equal)

    create event:
    /////// equally spaced floors
    Code:
    min_y = whatever;
    max_y = whatever;
    travel_dist = whatever;
    is_level = undefined;
    ///// alternative using an array (ignore the the other variables from above)
    Code:
    is_level = undefined;
    elevator_pos = whatever floor position it starts in: numbered from 0 and equal to array length
    step event: (some pseudo code here)

    Code:
    if inVator && !goingUp && !goingDown
    {
    if is_active // a control variable set to true by "activating" the elevator. This just means that pressing up or down in contact with the elevator won't start it, unless the player intends to do so
    {
    if key_press(up)
    {
    if y - travel_dist >= min_y // equally spaced floor method. know that from elevators current static y position it can go up by travel_dist, and be more than, or equal to, min_y
    OR
    if elevator_pos - 1 >= 0 // array method
    {
    can set elevator to move up
    ////////////////// non array method etc
    is_level = y - travel_dist;
    ////////////////array method
    elevator_pos -= 1
    is_level = subLevel[elevator_pos];
    
    is_active = false;
    goingDown = false;
    goingUp = true
    }
    else
    {
    does nothing as y - travel_dist would result in a figure lower than min_y, or using array position it knows there is not another previous entry and is therefore at the top
    }
    }
    if key_press(down)
    {
    if y + travel_dist <= max_y // equally spaced floor method. know that from elevators current static y position it can go down by travel_dist, and be less than, or equal to, max_y
    OR
    if elevator_pos + 1 <= array_length(sublevel) // array method - can't recall the exact code for array length...
    {
    can set elevator to move down
    
    ////////////////// non array method etc
    is_level = y + travel_dist;
    ////////////////array method
    elevator_pos += 1
    is_level = subLevel[elevator_pos];
    
    is_active = false;
    goingUp = false;
    goingDown = true;
    }
    else
    {
    does nothing as y + travel_dist would result in a figure larger than max_y, or elevator_pos would be larger than array length and is therefore at the bottom
    }
    }
    }
    }
    
    if goingUp || goingDown
    {
    if (point_distance(x, y, x, is_level) > elevateSpd
    {
    move_towards_point(x, is_level, elevateSpd);
    obj_player.y = y-37;
    }
    else
    {
    speed = 0;
    move_snap(x, is_level);
    obj_player.y = y-37;
    goingUp = false;
    goingDown = false;
    is_active = false;
    }
    }

    is_active starts as false, and the player can then set it to true when in contact with an elevator

    goingUp or goingDown being false also acts as control measures

    if is_active is true, and the player presses up or down, it looks to see if movement in the appropriate direction is possible. It then sets a point to move to if it is possible, and sets goingUp or goingDown to be true.

    The assumption here is that you won't allow the player to interrupt the elevator, so once either of those variables is set to true (meaning it can't be activated again) it will only be set back to false once the elevator reaches the end point.

    While the elevator is moving the player is being moved with it, so there should be no need to alter the players position any further. When the elevator stops in place, so too does the player, and in the right y position to be in contact with the ground when they leave.

    If I've figured this out correctly it should combine your step and end step into one condensed event for the end step (you'd just need to include the collision checking with the elevator somewhere)
     
  5. Joe Ellis

    Joe Ellis Member

    Joined:
    Aug 30, 2016
    Posts:
    857
    This code is alot more complicated than it could be, (hope I don't sound elitist or anything)
    You can make objects inherit speeds from other moving objects in an incredibly general way,
    But what you've done here is actually really good, it works at least, that's the main goal obviously, I'm just saying, you can confine this code and use it in alot of other situations, instead of coding each situation individually. I'm mainly saying this so that you can learn from this situation and use what you've learnt here for alot of other things
     

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