3
\$\begingroup\$

"sprite" is the player object, velocity is a Vec2 that tracks movement (is set to -1, 1, or 0 at any given time), each sprite in "sprites" is a wall of the same size as the player (everyone is a block for testing purposes).

if (velocity != Vec2.Zero)
{
    Vec2 oldPos = sprite.Position;
    sprite.Position = sprite.Position + velocity;
    foreach (Sprite s in sprites.Except(new List<Sprite> { sprite }))
    {
        if (sprite.BoundingBox.Intersects(s.BoundingBox))
            sprite.Position = new Vec2(oldPos.X, sprite.Position.Y);
        if (sprite.BoundingBox.Intersects(s.BoundingBox))
            sprite.Position = new Vec2(sprite.Position.X, oldPos.Y);
    }
    velocity = Vec2.Zero;
}

So, this actually works quite well - except for some reason it only works in one direction. Collisions work in both, but when sliding against a wall by holding two direction keys it only works sliding along the Y axis.

The bounding box for "sprite" is updated whenever the "Position" variable is written to via an accessor, so the bounding box is always up to date. The walls do not move, so theirs are also up to date.

At first glance, it seemed that there were a few obvious spots to check - I am modifying the bounding box for the second check, and at the time I hadn't been doing it automatically, so I tried disabling that. Doing so broke the working parts, so it wasn't that. I get no change in behavior when I add "else" to the second if statement, confirming that the first block always runs and the second block never does. If I flip them around, the other axis works instead.

I tried separating them into two foreach loops (just to see) and the behavior still remained the same. I'm kinda confused as to why none of these changes produced any hints as to the issue - it's likely something I'm overlooking, so I was hoping someone else would see.

If you need me to post any other code, just ask.

I was trying to do this in 3D and had a very bad time, so I figured I'd make a 2D version of my engine and get things sorted there before moving the collision back into 3D, heh..

\$\endgroup\$
  • \$\begingroup\$ Not exactly the same but maybe its a start gamedev.stackexchange.com/questions/49956/… \$\endgroup\$ – Terrance Aug 29 '14 at 20:51
  • \$\begingroup\$ Can't use his method because my stuff isn't set up to magically hand out normals, heh. \$\endgroup\$ – Yushatak Aug 29 '14 at 22:18
0
\$\begingroup\$

OK, after much fiddling and reading and re-reading of my and others' code, I've figured it out. If anyone sees anywhere I could improve this or sees any issues, please do let me know. Also, I hope that this saves somebody out there from the same frustration that I dealt with, lol.

As I'd discovered, it would only work right on one axis at a time. The key was then to determine how to decide what axis to work with, which sounded straightforward but was eluding me last week for whatever reason, even though I'd stared right at code that could do it for me (Collision Detection - Slide Around Sprite , second answer). Somehow while nearly passing out this morning I was able to decipher the issue.

Anyway, the concept is to calculate the difference in positions, which (if normalized) is the direction from the moving object to the collidable one in question. You then check the absolute version of each coordinate of that to determine which has a greater magnitude, and do that axis first.

I also moved my collision resolution into its own function so that it could act recursively within the same update loop. Here's the resolver ("sprite" is now "a", "s" is now "b":

    void Resolve(Sprite a, Sprite b, Vec2 oldPos, int depth = 0)
    {
        if (++depth > 9) return; //abort if too deep
        Vec2 diff = (b.Position - a.Position);
        float adx = (float)Math.Abs(diff.X);
        float ady = (float)Math.Abs(diff.Y);
        if (adx > ady)
        {
            if (a.BoundingBox.Intersects(b.BoundingBox))
                a.Position = new Vec2(oldPos.X, a.Position.Y);
            if (a.BoundingBox.Intersects(b.BoundingBox))
                a.Position = new Vec2(a.Position.X, oldPos.Y);
        }
        else if (adx < ady)
        {
            if (a.BoundingBox.Intersects(b.BoundingBox))
                a.Position = new Vec2(a.Position.X, oldPos.Y);
            if (a.BoundingBox.Intersects(b.BoundingBox))
                a.Position = new Vec2(oldPos.X, a.Position.Y);
        }
        Resolve(a, b, oldPos, depth);
    }

Note that the second condition is important - if you just leave it as "else", you will get weird sticking behavior when adx and ady are equal (i.e., when you just move out of collision on one axis while holding down two directions, and both end up as 1).

I haven't tested too many values for the max depth, but 5 didn't seem to be enough and I arbitrarily picked 9 since it is greater than the number of directions (8), allowing it to compensate for collisions in more directions than can be moved in at once in the first place (I might reduce that if it ends up causing any slowdown, perhaps make it an argument or global setting).

And here's the code that now resides where the old snippet was:

    if (velocity != Vec2.Zero)
    {
        Vec2 oldPos = sprite.Position;
        sprite.Position = sprite.Position + velocity;
        foreach (Sprite s in sprites.Except(new List<Sprite> { sprite }))
        {
            Resolve(sprite, s, oldPos);
            velocity = Vec2.Zero;                
        }
    }

This now works perfectly as far as I can tell. Perhaps I'll reorganize it into a movement function that performs the loop and initial movement all within the same function to be a bit tidier.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.