Starslinger Kings Dev Log

Starslinger Kings DevLog 02 - 2D Platformer Collision Hell

So I've spent the last week working on the physics and collisions of the game, and it's been a lot harder than I ever would have anticipated. Let me show you my struggle.

Okay so to start with, context. The physics were easy enough to code, they work in the following way:

  • When moving left or right, you quickly accelerate to a max running speed
  • When not moving left or right, your speed drags to a halt
  • Jumping is just a push upwards
  • Gravity is a constant force
  • I also added a kind of "force push": you can throw the player around in any direction with a given force that decays over time. Think explosion knockback, recoil etc.

Simple enough. After I coded that, it was time to make it interact with the environment. This is when this got tricky. 

Suppose we have this little dude and want to move him around:

Meet Mr Smiletimes, off to enjoy his Sunday by doing some friendly gun murders. The orange block is an obstacle, the grey bars are the ceiling and floor. He has a full set of physics, but how do we make him obey them within this environment?

The answer, for our purposes, is we denote bounding boxes around the areas will collide with one another. The idea being that if the yellow box begins to overlap with any of the red boxes, we stop it from doing so. This is a good way to approximate collisions when shape precision isn't particularly important.

 

Now here's where things get fun: Suppose he's moving forwards. I could just move this box around according to its velocity, and if it enters a red box move it back out, right? Well yes, but there will be a frame where Mr Smiletimes was inside the block and that looks sloppy.

Instead, we're going to predict his movement for the next step, and use it to transform his collision box to cover the area from where he is to where he's moving to. If this box overlaps a red rectangle, then we kill his velocity along the x-axis and stop him from moving there.

This could also be done by simply moving the collision box to where it will be in the next step, but if moving at high speeds it can be possible to "jump" past obstacles - transforming the box just leaves less room for error.

 

If he's jumping too? No problem, transform his collision box along the y axis and test that for collisions separately. Now when he jumps straight up, he hits the roof and comes down! And if he walks into the block, he stops.

Wait, but what if we're moving right and up simultaneously and approach a block at 45 degrees...

Right, yeah, crap. It passes the collision tests, moves there, then gets stuck. Really should have seen that one coming (though in actual fact, the pixel-quantities you're moving per frame are generally so small in a platform games this nearly never comes up, but the possibility is still bad so we'll fix it).

So what now?

Why don't we try and test the x and y axis simultaneously?

This seems like a more sensible approach, but raises the following problem: if you're going to stop movement by setting the velocity along an axis to zero, which axis do you choose when there's a collision? You're going to need to know more about the collisions to work that out.

What if you don't just check the rectangles for overlaps, but take the intersection between the two rectangles as a new rectangle? The width and height mark how much you are trespassing into the red box along the x and y axes respectively. This opens some doors!

And brought me to my Icarus moment: I flew too close to the sun. It occurred to me that using the intersection gives you the exact values you need to take from a player's movement to have them avoid a collision, a kind of "equal and opposite reaction" to trying to enter a box you weren't supposed to. I coded it up and it worked flawlessly.

Aside from one minor caveat: I was using the intersection to reduce the overall speed, but if the player kept running into a wall, there was nothing to decrease their velocity. It kept growing and growing until the calculation spazzed out and slingshot the player through the block and somewhere deep into orbit at the speed or light.

I then coded a method to reduce the player's velocity by exactly the amount it needed to in order to avoid a collision when the pixel-quantity movement calculation was done, and that worked, sometimes, but for one thing...

I was dead tired, and realised the next day that I got the sign wrong on one of the calculations and sent it haywire.

Before making that realisation though, my tired brain took the path of least resistance to solving the problem, and despite this being fairly shitty code, it is a solution. Here's what I did:

  1. Use the intersection to work out which wall of the player is having a collision.
  2. If there is a collision anywhere in the next frame, set the velocity along the x-axis to zero.
  3. Check for collisions again, without x-axis velocity if it was set to 0 in the previous step, and if there is a collision detected it must be in the y-axis, so set y-axis velocity to zero.
  4. Now, having set y-axis velocity to zero if required, restore the x-axis velocity to what it was before step 2, and check for collisions again, setting the x-axis velocity to zero if one is found.

And done. Yes, it looks like it runs in circles. It practically does. But if you follow the logic closely and think about it, it compensates for any false-positive collision checks that might be found along either axis due to the velocity of the other.

So despite taking 3 passes of game objects for collisions, it works well enough for now. When I've got my wits about me a bit though, I will definitely be going back to tackle the "equal and opposite" reaction approach: of all here, it seems the cleanest and most fool-proof to implement.

Next up on the agenda is shooting and flying! I'll try to work in some animations too.

Joomla BJ Metis template by ByJoomla.com