External Exam Download Resources Web Applications Games Recycle Bin

tilemap collisions | gml



tilemap collisions provide ultra fast collision correction. This is the way platformers are made commercially (e.g. Mario).

to have success with tilemap collisions it helps to have some knowledge of offsets, collision masks and bounding boxes:




sprPlayer / objPlayer: sprite & object



add objPlayer to Instances layer, we will cover Mask layer below:



sprMask: new tile mask sprite

Draw some lines on the right side of a new 128x64px sprite, so that you have a 64x64px tile we can use for a 'mask' or restricted area. Whenever working with tilesets in GMS2 the first 'tile' is left empty for optimization. You can also hide this layer in-game (and do your aesthetics in another layer) so it doesn't really matter what you draw:



tsMask: new Tileset, and new Mask layer

First create new tileset:



Draw some collision masks into the Mask layer:



If you want to add some visible tiles (or objects) into the Instances layer, go for it.

objPlayer → Step algorithm



objPlayer Create

grid_size = 64;
move_speed = 8;
jump_speed = 16;
grav = 0.75; //gravity (manual)
v_speed = 0; //vertical speed (manual)
dx = 0; //destination x
dy = 0; //destination y
t1 = 0; //corner 1 we are checking (either top-left/right / bottom-left/right)
t2 = 0; //corner 2 we are checking for collision (will depend which way we are going)


// tile map info
var layer_id = layer_get_id("Mask");
tilemask = layer_tilemap_get_id(layer_id);

// sprite bbox info
sprite_bbox_left = sprite_get_bbox_left(sprite_index) - sprite_get_xoffset(sprite_index);
sprite_bbox_right = sprite_get_bbox_right(sprite_index) - sprite_get_xoffset(sprite_index);
sprite_bbox_bottom = sprite_get_bbox_bottom(sprite_index) - sprite_get_yoffset(sprite_index);
sprite_bbox_top = sprite_get_bbox_top(sprite_index) - sprite_get_yoffset(sprite_index);

objPlayer Step

// check bottom left and right corners of bounding box for a tile ...
t1 = tilemap_get_at_pixel(tilemask, bbox_left, bbox_bottom + 1) & tile_index_mask;
t2 = tilemap_get_at_pixel(tilemask, bbox_right, bbox_bottom + 1) & tile_index_mask;

// ... and if there is a tile, it means i can enable jumping:
if (t1 != 0 || t2 != 0) && (keyboard_check(vk_up)) v_speed = -jump_speed;

// calculate x-axis destination:
dx = move_speed * (keyboard_check(vk_right) - (keyboard_check(vk_left)));

// calculate y-axis destination:
dy = v_speed;
v_speed += grav;

// apply y-axis destination to this object:
y += dy;

// if i'm moving down:
if (dy > 0) {
    // is there a tile or no tile at bottom left or bottom right corner:
    t1 = tilemap_get_at_pixel(tilemask, bbox_left, bbox_bottom) & tile_index_mask;
    t2 = tilemap_get_at_pixel(tilemask, bbox_right, bbox_bottom) & tile_index_mask;
    
    // if there is a tile at bottom left or bottom right corner:
    if (t1 != 0 || t2 != 0) {
        // snap the y value of this object to the grid size we are using:
        y = ((bbox_bottom & ~ (grid_size-1)) - 1) - sprite_bbox_bottom;
        v_speed = 0;
    }   
} else { // check top left and right corners:
    t1 = tilemap_get_at_pixel(tilemask, bbox_left, bbox_top) & tile_index_mask;
    t2 = tilemap_get_at_pixel(tilemask, bbox_right, bbox_top) & tile_index_mask;
    // if blocked up, snap y value to previous non-blocked tile area:
    if (t1 != 0 || t2 != 0) {
        y = ((bbox_top + grid_size) & ~ (grid_size-1)) - sprite_bbox_top;
        v_speed = 0;
    }
}

// apply x-axis destination to this object:
x += dx;

// if i'm moving RIGHT:
if (dx > 0) { 
    // is there a tile or no tile at TOP RIGHT or BOTTOM RIGHT:
    var t1 = tilemap_get_at_pixel(tilemask, bbox_right, bbox_top) & tile_index_mask;
    var t2 = tilemap_get_at_pixel(tilemask, bbox_right, bbox_bottom) & tile_index_mask;
    // if blocked RIGHT, snap x to 1 non-blocked tile area LEFT:
    if (t1 != 0 || t2 != 0) {
        x = ((bbox_right & ~ (grid_size-1)) - 1) - sprite_bbox_right;
    }   
} 

// otherwise if i'm moving LEFT (so not moving RIGHT):
else if (dx < 0) {
    // is there a tile or no tile at TOP LEFT or BOTTOM LEFT:
    var t1 = tilemap_get_at_pixel(tilemask, bbox_left, bbox_top) & tile_index_mask;
    var t2 = tilemap_get_at_pixel(tilemask, bbox_left, bbox_bottom) & tile_index_mask;
    // if blocked LEFT, snap x to 1 non-blocked tile area RIGHT:
    if (t1 != 0 || t2 != 0) {
        x = ((bbox_left + (grid_size-1)) & ~ (grid_size-1)) - sprite_bbox_left;
    }
}