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).
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).
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
very nice!