• Hey Guest! Ever feel like entering a Game Jam, but the time limit is always too much pressure? We get it... You lead a hectic life and dedicating 3 whole days to make a game just doesn't work for you! So, why not enter the GMC SLOW JAM? Take your time! Kick back and make your game over 4 months! Interested? Then just click here!

Issue with my precise tile collision checking

kamiyasi

Member
Hello. I created a script for tile collision checking for my player object, but I have an issue that I haven't been able to figure out a solution for.
Right now, it works great, except that I need to add logic for a collision shape for the player. Currently, my code acts like the player's collision is only a single pixel because I'm using someone's constructor function called TilesetCollisionHandler to handle the tile collision detection. That function looks like this:
GML:
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);
    surface_set_target(surface);
    draw_sprite(colliderSprite, 0, 0, 0);
    surface_reset_target();
    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(buffer,surface,0);

    // 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
    surface_free(surface);
    buffer_delete(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);
    }
}
Because it's only checking a single pixel, it's not taking the player's collision shape into consideration.
Here's the code that I'm using for the player movement:

GML:
function playerCollision( mSpd, mDir )
{
    var collision = false;
   
    var x_target = x + lengthdir_x( mSpd, mDir );
    var y_target = y + lengthdir_y( mSpd, mDir );


    if ( tilemap_get_at_pixel( collisionMap, x_target, y_target ) )
    {
        //collision = true;
        collision = tilesetCollisionHandler.pointOverlappingPixel( x_target, y_target )
    }


    if ( collision == false )
    {
        x = x_target;
        y = y_target;
    }
    else
    {
        var angle_precision = 10;
   
        for ( var angle = 1; angle < 90; angle += angle_precision)
        {
            for ( var multiplier = -1; multiplier <= 1; multiplier += 2) {
           
                // iterate through directions plus and minus 90 degrees for free space to move to
                var angle_to_check = mDir + angle * multiplier;
           
                var speed_multiplier = 0.2 + 0.8 *abs( dcos ( angle_difference( angle_to_check, mDir ) ) );
           
                //coordinates to check for collision
                x_target = x + lengthdir_x( mSpd * speed_multiplier, angle_to_check );
                y_target = y + lengthdir_y( mSpd * speed_multiplier, angle_to_check );    
           
                // move if no collision is found
                collision = tilesetCollisionHandler.pointOverlappingPixel( x_target, y_target )
               
                if ( collision == false )
                {
                    x = x_target;
                    y = y_target;
                    exit;      
                }  
            }
        }      
       
    }
}
I tried adding a lengthdir adjusted value of 32 to the calls for tilesetCollisionHandler.pointOverlappingPixel to try and simulate a collision circle with a radius of 32, but this is not giving the correct results. Instead it just makes the collision jittery and not work properly. Any ideas? Thanks
 

Rob

Member
I recently made something similar and my solution for a collision mask was:

If moving horizontally check X amount of pixels either 1 pixel to the left of the left edge or 1 pixel to the right of the right edge, X pixels from top to bottom.
Eg if you want the collision box to be 8 pixels on all sides and you're moving left you'd check all 8 pixels from top to bottom, 1 pixel to the left of your players left edge.
Do that for every pixel of movement you want to do per step.
 
Top