GMS 2.3+ Tileset Collision Handler script for pixel perfect detection


GM Version: Studio 2+
Target Platform: Windows
Download: see code below
Links: n/a

A simple solution to detect collision pixels on a tileset.

While there are a few tutorials online the workflows seemed overly messy to me, so I have created a Script which can just be added to any 2.3+ project and immediately gives you access to start testing collision with tilesets.

Just in case you're wondering, this isn't just for solid blocks and non solid blocks, but each individual pixel allowing for slopes, or tiles with arbitrary shapes and sizes.

Here are the 3 steps to using this, assuming you already have your collision tileset sprite and you've made a tileset from that:

The steps:
Create a script and paste the code at the bottom of this post, an explanation for it is below it (Name it whatever helps you sleep at night...)

2. Initiate it and save to a global variable, param1 is your tileset sprite, param2 is the name of the tile layer you have used, and param3 is the size of an individual tile
global.tilesetCollisionHandler = new TilesetCollisionHandler(spr_Collision, "TileLayer", 32);
3. After adding some tiles to your level, you can check any tile on the screen anywhere in code with:
isSolid = global.tilesetCollisionHandler.pointOverlappingPixel(x, y);

This is very simple to set up and I hope it helps someone else, I plan to add some more methods like rectangle collision area's among other things, but any idea's for improvements let me know.

Here is the code for the main script:

/// @function TilesetCollisionHandler(_colliderSprite, _tilemapLayer, _tileSize)
/// @description Handle pixel accurate collisions for non alpha parts of a tileset
/// @param {sprite} _colliderSprite The sprite resource being used by the tileset
/// @param {string} _tilemapLayer The layer the tilemap belongs to
/// @param {int} _tileSize The size of the individual tiles, eg 16, 32 or 64

function TilesetCollisionHandler(_colliderSprite, _tilemapLayer, _tileSize) constructor {
    /* Variables

    // Arguments
    colliderSprite = _colliderSprite;
    tilemapLayer = _tilemapLayer;
    tileSize = _tileSize;

    // TBD after initialisation
    spriteWidth = 0;
    spriteHeight = 0;
    spritePixelData = {};

    /* Initialisation

    // Reset sprite pixel data
    spritePixelData = {};

    // Get sprite info
    spriteWidth = sprite_get_width(colliderSprite);
    spriteHeight = sprite_get_width(colliderSprite);

    // Drawing sprite to surface
    var surface = surface_create(spriteWidth, spriteHeight);
    draw_sprite(colliderSprite, 0, 0, 0);
    draw_surface(surface, 0, 0);

    // Creating buffer and reading pixel data
    var bytes = surface_get_width(surface) * surface_get_height(surface) * 4; // w * h == total pixels and each pixel has 4 bytes
    var buffer = buffer_create(bytes,buffer_fast,1);

    // Buffer_get_surface gets pixel data in BGRA format
    for (var i = 0; i < bytes; i+=4) {
        // Get the 4th byte which relates to alpha. 0 = b, 1 = g, 2 = r, 3 = a
        var alpha = buffer_peek(buffer, i+3, buffer_u8);
        var pixelIndex = i / 4;

        // Convert 1D to 2D array of booleans based on width & height of collision
        spritePixelData[pixelIndex mod spriteWidth, floor(pixelIndex / spriteHeight)] = (alpha != 0);

    // Delete surface & buffer

    /* Public purposed functions

    /// @function pointOverlappingPixel(_x, _y)
    /// @description Returns whether the x, y coordinates passed is a non alpha pixel
    /// @param {int} _x The x coordinate to test
    /// @param {int} _y The y coordinate to test
    pointOverlappingPixel = function(_x, _y)
        // Get the tile index, and finally interpret to pixel index
        var tileIndex = tilemap_get_at_pixel(layer_tilemap_get_id(tilemapLayer), _x, _y);
        var tileIndexX = tileIndex mod (spriteWidth / tileSize);
        var tileIndexY = floor(tileIndex / (spriteHeight / tileSize));
        var tileSpecificX = _x mod tileSize;
        var tileSpecificY = _y mod tileSize;
        var tilePixelIndexX = (tileIndexX * tileSize) + tileSpecificX;
        var tilePixelIndexY = (tileIndexY * tileSize) + tileSpecificY;

        // Catch if pixel out of bounds for whatever reason
        if(tilePixelIndexX < 0 || tilePixelIndexX >= spriteWidth|| tilePixelIndexY < 0 || tilePixelIndexY >= spriteHeight)
            return 0;

        return (spritePixelData[tilePixelIndexX, tilePixelIndexY] == 1);
What is this script?
This is used kind of like a class in traditional programming. Once initiated it grabs all pixels from the given sprite, puts them on a surface and then reads the buffer information getting the alpha values and plugging all of that in to a 2 dimensional array. Once it has this information, you can call the pointOverlappingPixel(_x, _y) method, this will find the tile index you are overlapping, and then go further by reading the array down to the pixel to find whether it is transparent or not. I can explain this further if any one would like.

Collision tileset?
This is a collision tileset ready for use. The idea is to keep the collision tileset simple and reusable, then paint your fancy tiles over the top on a different tile layer, this keeps the collision system consistant and you can think of your art tiles as just art without worrying about collision.


An example?
Below is an example in action, I have just created a step event on the mouse that checks the current pixel and displays whether it is solid or not.


Hope this helps someone! I am brand new to this community, I only downloaded Game Maker Studio a couple of days ago so sorry if this is not the norm to post things like this, it was just a situation where I was looking for this exact thing but couldn't find it.

Final disclaimer, I originally built this to work as an object rather than a script, but it seems things are moving towards scripts / functions / structs now which IMO is just better programming. If anyone needs help doing this the old way let me know.

Thanks to:
nacho_chicken (
hippyman (
Binsk (

for posts that have helped in different ways. And turns out 10kb is not a lot of RAM to worry about :cool:
Last edited: