Optimizing Collision Code

As I said in a previous post about my platformer engine (the one I’m working on for Fuzeboy), I’m using Zack Bell‘s code as a base. Recently I started to look into ways to optimize such code without losing the functionality (slopes are a big feature of that simple collision/movement code).

Looks like I was able to gain some speed here…

And as someone once told me “sharing is how better games are made”… so here it is my “improved” version.

If you remember Zack’s code, it looks more or less like this

///scr_collision_zack()
var vxNew, vyNew;

// Handle sub-pixel movement
xVelSub += xVel;
yVelSub += yVel;
vxNew = round(xVelSub);
vyNew = round(yVelSub);
xVelSub -= vxNew;
yVelSub -= vyNew;

// Vertical
repeat(abs(vyNew)) {
    if (!place_meeting(x, y + sign(vyNew), obj_collision_par))
        y += sign(vyNew); 
    else {
        yVel = 0;
        break;
    }
}

// Horizontal
repeat(abs(vxNew)) {

    // Move up slope
    if (place_meeting(x + sign(vxNew), y, obj_collision_par) && !place_meeting(x + sign(vxNew), y - 1, obj_collision_par))
        --y;
    
    // Move down slope
    if (!place_meeting(x + sign(vxNew), y, obj_collision_par) && !place_meeting(x + sign(vxNew), y + 1, obj_collision_par) && place_meeting(x + sign(vxNew), y + 2, obj_collision_par))
        ++y; 

    if (!place_meeting(x + sign(vxNew), y, obj_collision_par))
        x += sign(vxNew); 
    else {
        xVel = 0;
        break;
    }
}

Collision Rectangle vs Place Meeting

There is quite a bit of optimization to be made there. The biggest performance hit are all those place_meeting checks inside the repeat loop. The loops themselves are almost inevitable but we could replace the place_meeting with the collision_rectangle function. Don’t ask me why but it looks already faster. Possibly because the place meeting is actually moving the instance to that position and doing a check with the bounding boxes.

sign()

The sign() function. Very useful beast indeed.

This function returns whether a number is positive, negative or neither and returns 1, -1, 0 respectively. For example – sign(458) will return 1, sign(-5) will return -1 and sign(0) will return 0.

Inside the collision functions there is a lot of repetition for this very simple function. What if we simply call it once for vertical and horizontal speeds and then reuse the vars (that I called xdir and ydir)?

Let go of the vertical repeat

This trick is actually borrowed from the twitter user @MythStorm24. I adapted his original code to prevent going through walls even at very high speeds.

Let’s say we’re going at a vertical speed of 10pps (Pixel Per Step, I made up that word). Do we really need to check 10 times if we’re hitting something, and if not, move 1px each time? No we don’t. We can simply check if the path is clear. How do we do that? We use a collision_rectangle but we expand this rectangle to take into account our current vertical speed. So we either stretch it 10px to the top in case we’re going up, or 10px to the bottom if we’re going down.

In case of a collision, let’s just move to contact and set the speed to 0. If no collision happens, we move by 10px in one go.

This way, in the case of a clear path, we saved a lot of collision checks.

Horizontal Conditional Code

The horizontal part must retain the repeat logic for the slopes to work correctly. You’ll see I simply nested the collisions because it’s useless to make the same exact collision check twice.

Nikles optimized collision code

///scr_collision_nick()
var vxNew, vyNew;

/***************************************************
  Horizontal Movement
 ***************************************************/
if xVel != 0
{
    xVelSub    += xVel;
    vxNew       = round(xVelSub);
    xVelSub    -= vxNew;
    var xdir    = sign(vxNew)

    // Horizontal
    repeat(abs(vxNew))
    {
        // In case of horizontal collision
        if collision_rectangle(bbox_left + xdir, bbox_top, bbox_right + xdir, bbox_bottom, obj_collision_par, true, true)
        {
            // If it's a slope up
            if !collision_rectangle(bbox_left + xdir, bbox_top - 1, bbox_right + xdir, bbox_bottom - 1, obj_collision_par, true, true)
            {
                --y         // Move Up
                x += xdir   // Move Ahead
            }
            // If it's not a slope
            else
            {
                xVel = 0    // Stop completely
                break       // Stop repeating. We're still.
            }
        }
        // If there is no obstacle ahead
        else
        {
            // In case it's a slope down
            if (yVel >= 0) && !collision_rectangle(bbox_left + xdir, bbox_top + 1, bbox_right + xdir, bbox_bottom + 1, obj_collision_par, true, true) && collision_rectangle(bbox_left + xdir, bbox_top + 2, bbox_right + xdir, bbox_bottom + 2, obj_collision_par, true, true)
                ++y         // Move Down
            
            // Move ahead then
            x += xdir
        }
    }
}

/***************************************************
  Vertical Movement
 ***************************************************/
if yVel != 0
{
    yVelSub    += yVel;
    vyNew       = round(yVelSub);
    yVelSub    -= vyNew;
    var ydir    = sign(vyNew)
    
    // Check our direction (up or down)
    if ydir == 0
        var coll = noone
    else if ydir == 1
        var coll = collision_rectangle(bbox_left, bbox_top, bbox_right, bbox_bottom + vyNew, obj_collision_par, true, false)
    else if ydir == -1
        var coll = collision_rectangle(bbox_left, bbox_top + vyNew, bbox_right, bbox_bottom, obj_collision_par, true, false)
    
    // If there's a collision, move to contact
    if coll
    {
        while (!collision_rectangle(bbox_left, bbox_top + ydir, bbox_right, bbox_bottom + ydir, obj_collision_par, true, false))
        {
            y += ydir
        }
        
        // Once in contact, set the speeds to 0
        vyNew   = 0
        yVel    = 0
    }
    else // If no contact, move freely
        y += vyNew
}

You can also download the project from this link: CollisionsTest.gmz

As you can see I kept the slopes functionality. The red square is Zack’s code. Mine is the yellow one. I’ve added a check to avoid having my player stick to the down slopes when jumping upwards (personal preference).

Looks pretty decent to me…

If you find any error in the code, please let me know. I have not tested all possible cases (and bugs happen).

Let me know what you think and feel free to share.

“Sharing is how better games are made” – MythStorm24

One thought on “Optimizing Collision Code”

What are your thoughts on this?

This site uses Akismet to reduce spam. Learn how your comment data is processed.