Thursday, October 31, 2019

How can I use Rectangle.Intersect() to resolve collisions in XNA?


I have a 2D game written in XNA, and I've been trying to fine-tune my collision resolution. All of my game objects are squares, which means detecting a collision is easy - use the position and the size of each entity (which is already known) to craft a corresponding Rectangle, then compare the two with Rectangle.Intersect().


This works fine for the most part, but has been running into weird corner cases where the collision resolution doubles an entity's jump height, or gets caught between tiles when moving horizontally across a flat surface.


I have recently expanded my code to include a velocity for each game entity, and I thought it would be possible to avoid some of these problems by preventing the need for these resolutions in the first place.


The idea would be to use an entity's position + velocity to predict a collision, then reduce the velocity to prevent a collision from occurring in the first place.


I can still use Rectangle.Intersect() to determine if the entity is in a collision, but I'm horribly confused on how to go from the Rectangle returned by Rectangle.Intersect() to an appropriate velocity counter-vector to avoid the collision.


Is there a straightforward way to go from the intersection Rectangle to an appropriate counter-velocity?



Answer



I've actually implemented this just the other day. I started from the XNA Platformer Sample but noticed it had a few problems on corner cases. This was especially noticeable when using it for Zelda-like movement, because the character would get stuck between tiles when hugging the walls in a certain direction. I tried several changes to the algorithm, but there would always be one direction where the problem would appear.


I looked around a bit, and although I was skeptic, ended up trying a simple solution that I read in one forum - and it worked! So, instead of moving the player all at once and then resolving each collision along the smallest axis of intersection depth, the trick was to update and resolve on the X and Y axes separately. The process is still very similar to before, except for that difference. In other words:




  1. Apply horizontal velocity to your position.

  2. Round position.X to prevent jittering on some edge cases.

  3. Iterate through each obstacle that the player is intersecting, calculating the horizontal intersection depth and subtracting it from the player's position so that he no longer intersects that obstacle.

  4. Repeat 1-3 but for the vertical axis.


And so far it seems to be working, even when trying different player sizes and movement speeds, and "hugging" every wall in every direction. I've also noticed that as long as I cap the maximum instantaneous speed to the smallest value between the player's size and the tile's size, then he won't ever funnel through the walls. And that's still moving pretty fast!


I intended to clean it up a little, maybe make it completely generic, and release on my blog one of these days. But meanwhile I'll just drop every relevant bit of the code in a pastie, albeit unorganized, so you can read through it:


http://pastie.org/3152377


And here's a little video of that code in action, where I'm forcibly trying to push against every wall. I used normal a player size slightly smaller than the tile, and a normal movement speed, but I've also tested with many different values.



Edit


By the way, I'm using a custom method to get the intersection depth (basically the one from the Platformer sample modified to work on 1 dimension at a time) instead of Rectangle.Intersect. That's mostly because while Rectangle.Intersect gives you the depth, it does not give you the direction of the intersection, so you'd need to do some extra checks to find that out.


No comments:

Post a Comment

Simple past, Present perfect Past perfect

Can you tell me which form of the following sentences is the correct one please? Imagine two friends discussing the gym... I was in a good s...