GM:S 1.4 Basic Horizontal and Vertical Platforms using States

Discussion in 'Tutorials' started by Docker, Sep 1, 2016.

  1. Docker

    Docker Member

    Joined:
    Aug 31, 2016
    Posts:
    122
    GM Version: Studio
    Target Platform: Windows
    Download: see code below
    Links: N/A

    HORIZONTAL PLATFORM:
    I created this while doing Shaun Spalding's platformer series as I wanted to use states and my own implementations based on the theory of his videos.

    This guide assumes you have created your own vspd and hspd variables to use for movement, if you followed Shauns guide then change any vspd and hspd reference to vsp and hsp instead.

    There are probably better ways of doing this but it seemed like a logical fully functioning implementation so I thought i'd share it as I struggled to find anything similar when I tried looking for help while creating it.

    1. Create a sprite called spr_moving_platform_h sprite and an object called obj_moving_platform_h, add the sprite to the object.

    Add a create event in the object containing:
    Code:
    ///  Initialise platform
    state = choose(platform_left_state, platform_right_state);
    
    All this code will do is set your object's state to one of the above which we will be creating, the state will control the movement and basic collision detection

    Add a step event containing:
    Code:
    ///Execute state
    
    script_execute(state);
    
    Pretty self explanatory.

    2. Create a script called platform_left_state and another script called platform_right_state.
    Inside the platform_left_state put:
    Code:
    ///platform_left_state
    
    var wall_at_left = place_meeting(x-3, y, Solid); // Check for a wall 3 pixels left of platform
    
    if (wall_at_left){
      while (!place_meeting(x - 1, y, Solid)){ // while we are not 1 pixel away, move 1 pixel closer
      x -= 1;
      }
      if (place_meeting(x, y - 1, obj_player)){
      obj_player.hspd_carry = 3; // if a player is on platform, add 3 to their hspd, stops player moving 3 pixels on direction change
      }
      state = platform_right_state; // change state to right as we are 1 pixel from wall
    } else {
      if (place_meeting(x, y - 1, obj_player)){
      obj_player.hspd_carry = -3; // if player on platform, add -3 to hspd to move them left
      }
      x -= 3; // move platform 3 pixels left every step
    }
    
    Inside the platform_right_state put:
    Code:
    ///platform_right_state
    
    var wall_at_right = place_meeting(x+3,y,Solid); // Check for a wall 3 pixels left of platform
    
    if (wall_at_right){
      while (!place_meeting(x + 1, y, Solid)){  // while we are not 1 pixel away from wall, move 1 pixel closer
      x += 1;
      }
      if (place_meeting(x, y - 1, obj_player)){
      obj_player.hspd_carry = -3; // if a player is on platform, add 3 to their hspd, stops player moving 3 pixels on direction change
      }
      state = platform_left_state;
    } else {
      if (place_meeting(x, y - 1, obj_player)){
      obj_player.hspd_carry = 3; // if player on platform, add 3 to hspd to move them right
      }
      x += 3; // move platform 3 pixels right every step
    }
    
    Then create a collision event inside the player object with the obj_moving_platform_h and add:
    Code:
    /// Collision implementation
    
    var left_platform_side = x < other.x-16; // if my x position is 1 pixel left of platform
    var right_platform_side = x > other.x+16; // if my x position is 1 pixel right of platform
    
    if (left_platform_side){
      if (place_meeting(x-4, y, Solid)){ // if wall 4 pixels to the left of player
      with (other){
      state = platform_right_state; // change platform direction
      }
      } else {
      x -= 4; // push me away from platform by 4 pixels
      }
    }
    
    if (right_platform_side){
      if (place_meeting(x+4, y, Solid)){ // if wall 4 pixels to the right of player
      with (other){
      state = platform_left_state; // change platform direction
      }
      } else {
      x += 4; // push me away from platform by 4 pixels
      }
    }
    
    the variables left_platform_side and right_platform_side are assuming that your platform sprite is 32 pixels wide, if you have used a different width just replace 16 with the width of your sprite/2, I'm sure there is also a command you can use to get sprite width/2 that is probably better practice than using an absolute value.

    For this to work you will need the variable hspd_carry = 0; to be initialised in the player objects create event, you will need to update Solid to whatever you have named your wall object or parent object as and in your normal collision event code you will need:

    Code:
    var hspd_final = hspd + hspd_carry; // platform hspd plus player hspd
    hspd_carry = 0;
    
    Any reference to hspd inside the collision event checking statements need to be changed to hspd_final and where you reset hspd = 0 you also need to do hspd_final = 0 as well as update your x+= from hspd to hspd_final.

    This creates a solid platform that the player is fully capable of jumping and standing on, if the player jumps into the side it'll act like a wall and push them away to stop you getting stuck in it unless there is a wall between you and the platform in which case it will change direction.

    You could make this kill the player instead if you want but I chose to do it this way for simplicity.
     
    Last edited: Sep 25, 2016
  2. Docker

    Docker Member

    Joined:
    Aug 31, 2016
    Posts:
    122
    VERTICAL PLATFORM:
    1. Create a sprite called spr_moving_platform_v sprite and an object called obj_moving_platform_v, add the sprite to the object.

    Add a create event in the object containing:
    Code:
    ///  Initialise platform
    state = choose(platform_up_state, platform_down_state);
    
    All this code will do is set your object's state to one of the above which we will be creating, the state will control the movement and basic collision detection

    add a step event containing:
    Code:
    ///Execute state
    
    script_execute(state);
    
    2. Create a script called platform_up_state and another script called platform_down_state.

    Inside the platform_up_state put:
    Code:
    ///platform_up_state()
    
    var wall_above = place_meeting(x, y-20, Solid); // Check for wall above
    
    if (wall_above){
      state = platform_down_state;
    } else {
      if (place_meeting(x, y - 1, obj_player)){ // if player is on the platform
      obj_player.y -= 3; // move player 3 pixels up
      }
      y -= 3; // move platform y position up by 3
    }
    
    Inside the platform_down_state put:
    Code:
    ///platform_down_state()
    
    var wall_below = place_meeting(x, y+20, Solid); // Check for wall below
    
    if (wall_below){
      state = platform_up_state;
    } else {
      if (place_meeting(x, y - 1, obj_player)){ // if player is on the platform
      obj_player.y += 3; // move player down 3 pixels
      }
    
      y += 3; // move platform y position up by 3
    }
    
    Then create a collision event inside the player object with the obj_moving_platform_v and add:
    Code:
    /// Collision Implementation
    
    var below_platform = y > other.y+8; // // if my y position is 1 pixel below the platform
    
    if (below_platform){
      if (place_meeting(x, y+3, Solid)){ // if ground is 3 pixels below the player
      with (other){
      state = platform_up_state; // change platform direction
      }
      } else{
      while (place_meeting(x, y-1, obj_moving_platform_v)){ // while player is 1 pixel below platform
      y += 3; // push player down 3 pixels, stop player getting stuck in platform
      }
      }
    }
    
    the variable below_platform is assuming that your platform sprite is 16 pixels height, if you have used a different height just replace 8 with the height of your sprite/2, I'm sure there is also a command you can use to get sprite height/2 that is probably better practice than using an absolute value.

    Again you need to update Solid to whatever you have your wall or parent object named as as this is the name of my parent object for everything I want collision events with.

    This platform moves up and down until it gets 20 pixels of the wall/ceiling but you can adjust this.

    If the player is below the platform and on the ground it will change direction, you could make this kill the player instead but i chose not to for simplicity again, otherwise if you jump into the bottom of the platform it'll just push you down to stop you getting stuck in it.

    The vertical platform does have limitations such as if you put it up against a wall with gaps the player can also hang off the platform so it moves them into the wall. This will come down to your collision system however you can resolve this by implementing my collision system which will continue to make the platform change direction if the player has a collision with the wall while on the platform until they are no longer colliding.

    COLLISION CODE:
    create a script called move and add the code:
    Code:
    
    /// move(collision_object)
    
    var hspd_final = hspd + hspd_carry; // platform hspd plus player hspd
    hspd_carry = 0;
    
    var collision_object = argument0;
    
    // Horizontal collision checking
    if (place_meeting(x + hspd_final, y, collision_object)){
      while (!place_meeting(x + sign(hspd_final), y, collision_object)){
      x += sign(hspd_final);
      }
    hspd_final = 0;
    hspd = 0;
    }
    
    x += hspd_final;
    
    // Vertical collision checking
    if (place_meeting(x, y + vspd, collision_object)){
      while (!place_meeting(x, y + sign(vspd), collision_object)){
      y += sign(vspd);
      }
    vspd = 0;
    }
    
    y += vspd;
    
    Then create another script called move_platform and add the code:
    Code:
    
    /// move(collision_object)
    
    var hspd_final = hspd + hspd_carry; // platform hspd plus player hspd
    hspd_carry = 0;
    
    var collision_object = argument0;
    
    // Horizontal collision checking
    if (place_meeting(x + hspd_final, y, collision_object)){
      while (!place_meeting(x + sign(hspd_final), y, collision_object)){
      x += sign(hspd_final);
      }
    hspd_final = 0;
    hspd = 0;
    }
    
    x += hspd_final;
    
    // Vertical collision checking
    if (place_meeting(x-1, y - 3, collision_object)){
      if (obj_moving_platform_v.state == platform_up_state) { // if up state
      obj_moving_platform_v.state = platform_down_state; // change to down state
      } else {
      obj_moving_platform_v.state = platform_up_state; // otherwise up state
      }
    vspd = 0;
    }
    
    y += vspd;
    
    And in your player objects step event add:
    Code:
    
    if (place_meeting(x, y + 1, obj_moving_platform_v)){
      move_platform(obj_wall);
    } else {
      move(Solid);
    }
    
    make sure you use your wall object in the move_platform(parameter), if you use the parent of your wall object that is shared with the platform it won't be able to tell the difference between the wall and platform and will always trigger the collision event while standing on the platform.

    FINAL NOTE:

    I've only started trying to program games a few days ago so there may be problems with my implementation that I have no idea even exist, if any programming gurus see any problems with my implementation please let me know but as far as I am aware its fully working without bugs.

    Also sorry about the lack of code formatting and indentations, it disappeared when I created this guide for some reason.
     
    Last edited: Sep 25, 2016
  3. Docker

    Docker Member

    Joined:
    Aug 31, 2016
    Posts:
    122
    UPDATE: I no longer use the code above and have developed different scripts that allow me to have other moving objects such as spikes coming from the ground and also doesn't suffer from bugs I discovered in the other code

    HORIZONTAL PLATFORM:
    1. Create an object called obj_level_friendly_interactive_h_parent with no sprite, this will be used as a parent for any horizontal moving objects that you want the player to be able to interact with

    Add a create event containing:
    Code:
    ///Initialise interactive level
    spd = 1;
    start_x = x;
    start_y = y;
    distance_to_travel = sprite_width;
    finish_x = x + sprite_width;
    finish_y = y;
    
    state = scr_level_interactive_right;
    
    An alarm0 containing:
    Code:
    ///Move left
    
    start_x = x;
    finish_x -= distance_to_travel;
    state = scr_level_interactive_left;
    
    An alarm1 containing:
    Code:
    ///Move right
    
    start_x = x;
    finish_x += distance_to_travel;
    state = scr_level_interactive_right;
    
    A step containing:
    Code:
    ///Execute state
    
    script_execute(state);
    
    What this will do is store your current x and y position, set the width to travel to the sprites width by default which you can change to a set value in the creation code or inside the create event of any children that use this parent, you could also start this at a default of 0 in the creation code if you want but I've used the sprite width as a placeholder.

    You can also change the speed that the object moves at in the exact same way.

    Each time an alarm goes off it will change to the opposite direction, set the start x to its current x position and the target x position to the distance to travel.

    2. Create a script called scr_level_interactive_left containing:
    Code:
    ///scr_level_interactive_left
    
    dis = point_distance(start_x, start_y, finish_x, finish_y);
    
    if (dis != 0){
    move_towards_point(finish_x, finish_y, spd);
    }
    
    if (x == finish_x && y == finish_y){
        speed = 0;
        if (alarm[1] == -1){
            alarm[1] = 90;
        }
    }
    
    if (place_meeting(x, y-1, obj_player_parent)){
        obj_player_parent.x -= speed;
    }
    
    This will get the distance from your starting x to your finishing x and will move the object towards that -point at the speed set, once it reaches the point it will set the speed to 0 to stop the object moving and set the alarm1.

    If the player object is on the platform it will also move them at the same -speed as the platform, update obj_player_parent to the name of your player object.

    When alarm1 goes of it will set your current x to the starting x and add the distance onto the finishing x and change the state to move the other way.

    3. Create a script called scr_level_interactive_right containing:
    Code:
    ///scr_level_interactive_right
    
    dis = point_distance(start_x, start_y, finish_x, finish_y);
    
    if (dis != 0){
    move_towards_point(finish_x, finish_y, spd);
    }
    
    if (x == finish_x && y == finish_y){
        speed = 0;
        if (alarm[0] == -1){
            alarm[0] = 90;
        }
    }
    
    if (place_meeting(x, y-1, obj_player_parent)){
        obj_player_parent.x += speed;
    }
    
    This does EXACTLY the same as the other script but with + figures rather than - to move everything right.

    4. Create your platform sprite and object, i called mine obj_platform_h, and set the parent to obj_level_friendly_interactive_h_parent that we created above

    5. Create a collision event with your player object and add the code:
    Code:
    /// Collision implementation
    
    var left_platform_side = place_meeting(bbox_left, bbox_bottom, other); // if my x position is 1 pixel left of platform
    var right_platform_side = place_meeting(bbox_right, bbox_bottom, other) // if my x position is 1 pixel right of platform
    
    if (left_platform_side){
        with (other){
            if (place_empty(bbox_left-4, y)){ // if no wall is located 4 pixels to the left of player
                x -= 4; // move player 4 pixels
            } else {
                room_restart() // restart room
            }
        }
    }
    
    if (right_platform_side){
        with (other){
            if (place_empty(bbox_right+4, y)){ // if no wall is located 4 pixels to the right of player
                x += 4; // move player 4 pixels
            } else {
                room_restart() // restart room
            }
        }
    }
    
    Pretty self explanatory with the comments, feel free to change the else statement to whatever you want but I've changed it to room_restart() for the purpose of this guide as everyone could have different damage taking conditions and scripts
     
    Last edited: Sep 25, 2016
  4. Docker

    Docker Member

    Joined:
    Aug 31, 2016
    Posts:
    122
    VERTICAL PLATFORM:
    1. Create an object called obj_level_friendly_interactive_v_parent with no sprite, this will be used as a parent for any vertical moving objects that you want the player to be able to interact with

    Add a create event containing:
    Code:
    ///Initialise interactive level
    spd = 1;
    start_x = x;
    start_y = y;
    finish_x = x;
    distance_to_travel = sprite_height;
    finish_y = y-distance_to_travel;
    
    
    state = scr_level_interactive_up;
    
    An alarm0 containing:
    Code:
    ///Move down
    
    start_y = y;
    finish_y += distance_to_travel;
    state = scr_level_interactive_down;
    
    An alarm1 containing:
    Code:
    ///Move up
    
    start_y = y;
    finish_y -= distance_to_travel;
    state = scr_level_interactive_up;
    
    A step containing:
    Code:
    ///Execute state
    
    script_execute(state);
    
    What this will do is store your current x and y position, set the height to travel to the sprites height by default which you can change to a set value in the creation code or inside the create event of any children that use this parent, you could also start this at a default of 0 in the creation code if you want but I've used the sprite width as a placeholder.

    You can also change the speed that the object moves at in the exact same way.

    Each time an alarm goes off it will change to the opposite direction, set the start y to its current y position and the target y position to the distance to travel.

    2. Create a script called scr_level_interactive_up containing:
    Code:
    ///scr_level_interactive_up
    
    dis = point_distance(start_x, start_y, finish_x, finish_y);
    
    if (dis != 0){
    move_towards_point(finish_x, finish_y, spd);
    }
    
    if (x == finish_x && y == finish_y){
        speed = 0;
        if (alarm[0] == -1){
            alarm[0] = 90;
        }
    }
    
    if (place_meeting(x, y-1, obj_player_parent)){
        obj_player_parent.y = ceil(bbox_top-16-speed);
    }
    
    This will get the distance from your starting y to your finishing y and will move the object towards that -point at the speed set, once it reaches the point it will set the speed to 0 to stop the object moving and set the alarm1.

    When alarm1 goes of it will set your current y to the starting y and add the distance onto the finishing y and change the state to move the other way.

    The last if statement checks if the player object is on it, if it is then it 'locks' the player to the top of the platform and moves them with it by setting the players y position.

    This is set for a platform with a height of 16 however if yours differs then just change 16 to the height of your platform object.

    3. Create a script called scr_level_interactive_down containing:
    Code:
    ///scr_level_interactive_down
    
    dis = point_distance(start_x, start_y, finish_x, finish_y);
    
    if (dis != 0){
    move_towards_point(finish_x, finish_y, spd);
    }
    
    if (x == finish_x && y == finish_y){
        speed = 0;
        if (alarm[1] == -1){
            alarm[1] = 90;
        }
    }
    
    if (object_index == level_object_types.friendly_v_parent){
        if (place_meeting(x, y-1, obj_player_parent)){
            obj_player_parent.y = ceil(bbox_top-16+speed);
        }
    }
    
    This does EXACTLY the same as the other script but with + figures rather than - to move everything down. Don't forget to change the 16 again to your sprite height if you made it a different size.

    4. Create your platform sprite and object, i called mine obj_platform_v, and set the parent to obj_level_friendly_interactive_v_parent that we created above

    5. Create a collision event INSIDE your player object with the platform and add the code:
    Code:
    /// Collision implementation
    
    var below_level_object = y > other.bbox_bottom; // // if my y position is 1 pixel below the platform
    
    if (below_level_object){
        if (place_meeting(x, y+other.spd+1, obj_solid)){ // if ground is platform speed + 1 below the player
            room_restart()
        } else{
            y += other.spd+1; // move player down platform speed + 1
        }
    }
    
    Pretty self explanatory with the comments, feel free to change the else statement to whatever you want but I've changed it to room_restart() for the purpose of this guide as everyone could have different damage taking conditions and scripts

    I hope this guide helps some people, I spent a lot of hours creating this code from scratch and it seemed a waste not to share it.

    I've left the original code I created in the first two posts in case others find it useful or want to adapt it for other purposes.
     
    Last edited: Sep 25, 2016
  5. Docker

    Docker Member

    Joined:
    Aug 31, 2016
    Posts:
    122
    BONUS NOTE:
    You can duplicate obj_level_friendly_interactive_v_parent and call it obj_level_dangerous_interactive_v_parent and set this as a parent to any hurtful moving objects.

    Then you can edit the up and down script.

    scr_level_interactive_up:
    Code:
    ///scr_level_interactive_up
    
    dis = point_distance(start_x, start_y, finish_x, finish_y);
    
    if (dis != 0){
    move_towards_point(finish_x, finish_y, spd);
    }
    
    if (x == finish_x && y == finish_y){
        speed = 0;
        if (alarm[0] == -1){
            alarm[0] = 90;
        }
    }
    
    if (object_index == level_object_types.friendly_v_parent){
        if (place_meeting(x, y-1, obj_player_parent)){
            obj_player_parent.y = ceil(bbox_top-16-speed);
        }
    }
    
    Code:
    ///scr_level_interactive_down
    
    dis = point_distance(start_x, start_y, finish_x, finish_y);
    
    if (dis != 0){
    move_towards_point(finish_x, finish_y, spd);
    }
    
    if (x == finish_x && y == finish_y){
        speed = 0;
        if (alarm[1] == -1){
            alarm[1] = 90;
        }
    }
    
    if (object_index == level_object_types.friendly_v_parent){
        if (place_meeting(x, y-1, obj_player_parent)){
            obj_player_parent.y = ceil(bbox_top-16+speed);
        }
    }
    
    By doing this you can use the same two up and down scripts for both hurtful things such as spikes coming out of the ground as well as friendly things such as moving platforms, I try to make my code as modular as possible and this way all you need is a collision event inside the player with obj_level_dangerous_interactive_v_parent.
     
    Last edited: Sep 25, 2016

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