A downloadable asset pack

In this tutorial series we are building a system to handle Z-Axis collisions, this continues off from Tutorial #1 Here. (Make sure to download the tutorial package if you want to save some time typing out code, or if you don't quite understand how it is implemented)

Part 1: Updating the Z-Axis struct

To enhance the functionality, we need to incorporate the addition of a "z_ground" parameter, which will enable us to fix the minimum height to which the player can fall. This parameter will dynamically adjust to the height of the platform on which the player is standing to prevent them from falling to the ground unnecessarily. Moreover, we will utilize "z_height" to determine the highest point of both the shape and the player. Additionally, we will leverage "z_step" to facilitate climbing up slopes by allowing the player to ascend surfaces that are slightly taller than their current position. Finally, by introducing the "z_top()" function, we can expediently obtain the z coordinate for the highest point of the object.

function init_zaxis(_height){
   position = {
        z: 0,            /// position on z axis
        z_speed: 0,        /// speed in z direction
        z_gravity: 0.5,    /// accelerate down 
        z_ground: 0,    /// lowest position you can fall
        z_height: _height,    /// height of object
        z_step: 4,        /// climb on object a little higher
        z_top: function(){
            return z + z_height;    
        }
   };
}

We then should create a script to consolidate the changes for handling gravity / motion on the z-axis. This takes the gravity handling / motion code that was previously written in the step event of the player and adds in z_ground in place of the hard coded value of 0.

function update_zaxis(){
    /// always move when speed is applied
    position.z += position.z_speed;
    /// move in z direction, 
    /// Z_Axis is assumed to point in the -Y direction (+z)
    /// therefore, positive z_speed is up, -z_speed is down
    if(position.z > position.z_ground){
        position.z_speed -= position.z_gravity;
    } else {
        /// if below ground, adjust to ground position
        position.z = max(position.z, position.z_ground);
        /// if falling, stop falling
        var _floor_spd = 0;
        position.z_speed = max(position.z_speed, _floor_spd);
    }
}

Part 2: Collisions on Z-Axis

Having established a robust mechanism for managing Z-Axis collisions, we will aim to enable the player to jump onto objects seamlessly. The process involves detecting all the objects with which the player is colliding and adjusting the ground height to align with the top of the object if the player is above it. Fortunately, Game Maker offers a built-in function that retrieves all the objects that the player is likely to collide with at a designated location, streamlining the implementation of this feature.

function update_zground(_targets){
    /// check for collision
    var _list = ds_list_create();
    /// default lowest point
    position.z_ground = 0;
    
    /// get every instance under the object
    if(instance_place_list(x, y, _targets, _list, 0)){
    
        /// loop through all collision instances
        for(var _i = 0; _i < ds_list_size(_list); _i++){
            var _collide = _list[| _i];
        
            /// if collision occurs, we want to get on top of that object if possible
            if(_collide != noone && variable_instance_exists(_collide, "position")){
                var _pos2 = _collide.position;
                /// if self is above the top of the other object, z_ground will now be at that height
                if(position.z + position.z_step > _pos2.z_top()){
                    position.z_ground = max(position.z_ground, _pos2.z_top());
                } 
            }
        }
    }
    
    /// remove list to clear memory
    ds_list_destroy(_list);
}

The built in function instance_place_list() will fill our ds_list variable called "_list" with all of the objects that the player can collide with at point [x, y]. We then iterate over the whole list, and  check to see if the player is above that object. If the player is above that object, we set the height as the minimum value for the z_ground position.  This allows the player to land on top of objects that are below their z position.

Part 3: Collisions on X/Y-Axis

To examine collisions with objects when navigating the X/Y axis, we must devise a comparable script that yields a Boolean value. Since we don't want to collide with objects located above or below us, we need to verify whether there is an overlap on the Z-Axis for every object at the designated position.

function collision_check_zaxis(_xto, _yto, _targets, _get_obj=false){
    /// check for collision
    var _list = ds_list_create();
    if(instance_place_list(_xto, _yto, _targets, _list, 0)){
    
        /// loop through all collision instances
        for(var _i = 0; _i < ds_list_size(_list); _i++){
            var _collide = _list[| _i];
        
            /// if collision occurs, we want to get on top of that object if possible
            if(_collide != noone && variable_instance_exists(_collide, "position")){
                var _pos2 = _collide.position;
                /// if self is above the top of the other object, z_ground will now be at that height
                var _zfeet = position.z + position.z_step;
                var _zhead = position.z_top();
            
                /// check if connecting with block
                if(_zfeet < _pos2.z_top() && _zhead > _pos2.z){
                    /// also, going to fall into this!
                    if(position.z_ground - position.z_step < _pos2.z_top()){
                        ds_list_destroy(_list);
                        if(_get_obj){
                            return _collide;    
                        }
                        return true;
                    }
                } 
            }
        }
    }
    /// remove list to clear memory
    ds_list_destroy(_list);
    if(_get_obj){
        return noone;    
    }
    return false;
}

This function looks dense and hard to unpack, but it is almost exactly the same as the previous function. The difference being that we don't want to use it to place the object on top of another. This code detects when the players head is above the bottom of the collide-able object, and feet are below the top of the same object. That is how we define a collision on the X/Y axis. If that occurs we return true (or the collision object id if we set _get_obj = true, we will use that for moving platforms in a later tutorial). Otherwise, we are not colliding with the object and will want to return false.

Part 4: Putting it together

We will need an object to collide with, call it obj_block. In the create event place the following code:


depth = -y;
init_zaxis();

In the player step event we will add in the functions we just created, and update the movement to now include collisions with the block object.

/// @description Update Movement
/// move player on z axis with gravity
update_zaxis();
/// handle collisions on Z axis
update_zground(obj_block);
/// sort y position
depth = -y;
/// get movement axis
motion.x = keyboard_check(ord("D")) - keyboard_check(ord("A"));
motion.y = keyboard_check(ord("S")) - keyboard_check(ord("W"));
/// apply movement
if(motion.length() > 0){
    
    /// keeps movement smooth in all directions
    motion.normalize();    
    
    /// save motion vector so we can test it
    var move_vector = new Vector2(motion.x * max_spd, motion.y * max_spd);
    
    /// detect collision on x axis
    if(collision_check_zaxis(x+move_vector.x, y, obj_block)){
        move_vector.x = 0;
    }
    
    /// detect collision on y axis
    if(collision_check_zaxis(x, y+move_vector.y, obj_block)){
        move_vector.y = 0;
    }
    
    x += move_vector.x;
    y += move_vector.y;
}
/// jump
if(keyboard_check_pressed(vk_space)){
    var _jump_spd = 6;
    if(position.z <= 0){
           position.z_speed += _jump_spd;
        }
}

Then, update the draw event, we want to make sure to draw the shadow at the "ground" position.


var yy = y;
/// shadow
draw_set_alpha(0.5);
draw_circle_color(x,y-position.z_ground,8,c_black,c_black,0);
draw_set_alpha(1);
y -= position.z;
/// draw player at Z position
draw_self();
y = yy;

Conclusion:

In conclusion, we have successfully implemented collision code that enables the player to jump onto objects and provides more precise collision control for both the player and collideable objects in a 3D environment. In the subsequent tutorial, I will demonstrate how to include collisions with slopes and moving platforms, further enhancing the gameplay experience. (Click Here for the next tutorial in the series)

Download

Download
z-axis-tutorial_partB.zip 16 kB

Comments

Log in with itch.io to leave a comment.

part A seems more intuitive, I’m not sure the difference in this. Regardless, amazing work