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:

tilemap collisions | gml
objPlayer Create event:
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 event:
// 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; } }