Posts Tagged collision

More entities and optimization

Most of my work on the platformer this week has been focused on improving the Entity class and all the related code. I made a number of changes and improvements to SpatialCollection aimed at improving its performance, and completely overhauled all my collision detection code to reduce the number of passes I make over the level when doing things like computing an entity’s StandingY or testing for a collision. The efforts have mostly paid off so far; my framerate with 6 entities walking around on curved surfaces right now is about the same as it was before with a single entity walking around on sloped surfaces, even though I’ve improved my collision model significantly. The optimizations ended up bringing my game’s performance profile back to where it was before the addition of entities, with the majority of CPU time being spent doing raw number-crunching to perform intersection checks on polygons.

My previous approach to collision detection was fairly brute-force: I had a few special methods defined in the Game class named things like FindObstruction and ResolveMotion, that took a bunch of parameters to control a collision detection sweep of the entire level. The addition of SpatialCollection<T> as mentioned in last week’s post made those functions faster, but that didn’t completely eliminate the performance issues – the most visible offender was ComputeStandingY. The original implementation of ComputeStandingY had to make four complete passes over the level to come up with a result, and I ended up invoking it twice per frame when updating an entity. The end result was that no matter what, it accounted for a significant amount of my CPU usage, no matter how efficient I made the underlying collision detection routine.

In order to address this problem, I set out to refactor my collision detection code. The first step was to unify the different collision test routines – they all shared many common elements, the most obvious one being the task of walking over the contents of the level. Using SpatialCollection meant that each routine had about 8 lines of boilerplate in order to do iteration, in addition to all of the code for actually performing collision tests, so my first step was to factor that out into a baseline ObstructionTest function that performed the simple task of walking over the contents of the level and invoking a ‘Collision Visitor’ delegate on each item.

    public ICollidable ObstructionTest (ICollisionVisitor visitor, ObstructionFlags flags) {
        ICollidable result = null;
        var bounds = visitor.GetBounds();

        if ((flags & ObstructionFlags.Geometry) == ObstructionFlags.Geometry) {
            using (var e = Level.Geometry.GetItemsFromBounds(visitor.GetBounds()))
            while (e.MoveNext()) {
                var current = e.Current;

                if (!Bounds.Intersect(ref bounds, ref current.Bounds))
                    continue;

                var rg = current.Item.GetRuntimeGeometry(this);

                if (visitor.Visit(rg, ref result))
                    return result;
            }
        }

        if ((flags & ObstructionFlags.Entities) == ObstructionFlags.Entities) {
            if (bounds.Intersects(Player.Bounds))
                if (visitor.Visit(Player, ref result))
                    return result;

            using (var e = Entities.GetItemsFromBounds(bounds))
            while (e.MoveNext()) {
                var current = e.Current;

                if (!Bounds.Intersect(ref bounds, ref current.Bounds))
                    continue;

                if (visitor.Visit(current.Item, ref result))
                    return result;
            }
        }

        return result;
    }

After that, I refactored all the existing routines to be built on top of ObstructionTest. After that I iteratively refined things down until I didn’t need any of the specialized routines anymore, and ObstructionTest ended up operating on specialized ICollisionVisitor objects that had two ‘Visit’ methods, one for ‘Collidable Objects’, and one for individual collision polygons. The pair of visit methods allowed a visitor to reject entire objects before any complex collision tests were performed, in addition to rejecting individual polygons due to non-collision. The use of visitor objects also meant that a visitor could record a list of the objects it encountered, or reject collision with an entire list of entities instead of just the entity that was performing the check. These improvements not only resulted in better performance, but they allowed me to address a bug in the Entity class that prevented entities from walking up sloped surfaces while the player was standing on them.

    public class StandingSurfaceVisitor : ICollisionVisitor {
        public Entity Entity;
        public Bounds Bounds;
        public List<IStandable> Results;

        public StandingSurfaceVisitor (Entity entity) {
            Entity = entity;
            Results = new List<IStandable>();
        }

        public Bounds GetBounds () {
            return Bounds;
        }

        public CollisionState VisitCollidable (ICollidable obj) {
            if (obj == Entity)
                return new CollisionState(false, false);

            var standable = (obj as IStandable);
            if (standable != null)
                Results.Add(standable);
            return new CollisionState(false, true);
        }

        public void Reset () {
            Results.Clear();
        }

        public CollisionState VisitPolygon (Polygon poly) {
            throw new NotImplementedException();
        }
    }

One of the other things I invested some time into was an overhaul of the logic for standing on surfaces. Previously, the Player class had a StandingOn property that held one of the entities/surfaces he was currently standing on. I used this to determine whether the player was standing on a pressure plate, and cause him to move with things he was standing on. The problem was that in almost all cases the player would be standing on multiple surfaces, so the nondeterministic nature of the collision detection code meant that sometimes you could be atop a pressure plate but not trigger it. I also had no easy way of determining whether entities were standing on top of each other, which meant that an entity being ridden by another entity could not walk up a sloped surface (his path would be blocked by his rider).

To solve those two problems, I defined a pair of simple interfaces called IRider and IRideable. These interfaces provided a simple way for me to manage all the logic around riding surfaces – entities could automatically build and update a list of rideable objects that they were currently occupying, and rideable objects were able to automatically maintain a list of rider objects on top of them, making it simple to exclude those riders from collision checks. This not only fixed pressure plates, but also made it easy for monsters to set off pressure plates, and meant that monsters could walk up sloped surfaces while being ridden by other entities, and that you could stack entities on top of each other to create big riding chains without any major issues (though in the video below, it was still unfinished and somewhat busted):

One of the other fixes I was able to make as a result of the new system for handling rideable surfaces was to the code for mantling up onto surfaces. Previously, if you tried to mantle up onto a moving surface, like a moving platform or an entity, you’d mantle up onto the space it previously occupied and typically fall right back down, making it pretty useless. With the new riding system I was easily able to adapt this so that you could mantle up onto a moving surface without being left behind by its motion or causing it to become obstructed. Since the mantling code doesn’t currently distinguish between different types of surfaces, this means you can basically crawl up on top of a crowd of enemies and run across it, which is pretty fun.

The video below shows how entity mantling looks, though it’s from before I implemented the riding system (so you can see it get broken in a couple places):

Tags: , , , ,

Sloped surfaces and moving platforms

I spent most of today getting sloped surfaces and moving platforms working with the new movement and collision code.

The first approach I tried was to extend my existing collision detection code to automatically figure out how to move up and down a slope. I spent a few hours struggling with this, coming up with lots of implementations that were almost right, but they all ended up having nasty edge cases or needing stupid hacks to work correctly. Even worse, they complicated the normal motion and collision detection code in ways I didn’t like (though one cute side effect was that the player character began naturally sliding down slopes when he stood still… heh.)

So after some prodding from a couple people on IRC, I tried to solve this problem a different way. I changed the representation of my level geometry to distinguish between different types of polygons – LevelGeometry objects became LevelTriangles and LevelRectangles. Then I did a bit of work to enable a LevelTriangle to automatically figure out which of its sides is the sloped one. Once I had those two things, it was straightforward to change the movement code:

  • Every frame, check to see if the player is standing on a piece of geometry.
  • If the player is standing on a piece of geometry, call its ComputeStandingY method and pass in the player’s new position (the player’s current position plus his current velocity) and width. The method will return a new Y coordinate for the player.
  • Apply the new Y coordinate to the player’s current position.
  • Perform motion and collision detection as usual. The previous ComputeStandingY call already took into account the player’s intended velocity and selected an appropriate Y coordinate for him, so he won’t intersect with the surface he’s walking on.

Once I figured out how to do this, it actually was really simple to code and worked just about right. There were a couple key details, though:

  • You need to make sure to take into account the width of the player. The right math for this on a sloped surface turned out to be something like: (playerWidth / slopeWidth) * (slopeMaximumYslopeMinimumY). That knocks the player up just enough so that his left and right edges don’t intersect with the slope, so that motion is smooth and it still looks more or less like he’s standing on the surface.
  • The order of these operations matters a little bit. If you do the slope correction after doing motion and collision detection, you get glitchy movement because the player will intersect with the slope and then get moved up onto it, repeatedly.
  • Determining exactly where the player is standing can be a little tough. I’m using my normal collision detection code to do it and just offsetting the player’s Y by a couple pixels to see what’s below him. It more or less works, but I don’t think it’s the correct solution.

Much simpler than the previous solution I had, for sure, and it looks a bit nicer too since a lot of the glitchiness is gone.

Once I had this solution in place, moving platforms were really easy. Moving platforms are level geometry just like anything else, so they can have their own implementation of ComputeStandingY that ensures the player is standing right on top of them while he’s riding them. The only other missing detail is making the player automatically move along with the platform while he’s riding it. That turned out to be simple; you just add a step at the end of the existing algorithm:

  • Perform motion and collision detection as usual. The previous ComputeStandingY call already took into account the player’s intended velocity and selected an appropriate Y coordinate for him, so he won’t intersect with the surface he’s walking on.
  • If the surface the player is standing on is moving, add the surface’s velocity to the player’s current position.

Note that we do this after doing motion and collision detection – this is important for vertical platforms. If you do it before, the collision detection will sometimes prevent the player from moving with the platform, and he’ll get stuck. Also note that this has the downside of allowing a platform to push the player through a solid wall or other obstacle; I don’t consider this a problem for my implementation, and there are ways you could solve it.

Tags: , , , , ,

Collision detection and motion

Initially I implemented collision detection and motion in this platformer prototype the same way I had done it previously in Fury², my game creation system project from a while back. Entities have a position and velocity, which are both pairs of floats (x, y). Every frame, I compute the new position of the entity – pos+vel – in a given axis (x or y), and then do a check to see if it’s obstructed. If it’s obstructed, I halve the velocity and try again, and I repeat that iteratively a few times until I give up. After doing that for both axes, I have a relatively good approximation of how far the entity can move in its desired direction.

It was good enough for me, and the performance wasn’t *terrible*, so I didn’t worry about it much. But I always had this nagging doubt in my mind – this solution is obviously a hack. Isn’t there a way to do this with pure math?

Well, I’m not very good at math, but it turns out there is a way to do it with pure math. So today I threw out all the motion and collision code I had written for the platformer and decided to try a pure math approach, and see if I could resolve collisions non-iteratively. Suprisingly enough, it wasn’t actually that hard.

Here’s a summary of how it works:

I already do my collision detection using the separating axis theorem (SAT). To check for collision between a pair of convex polygons, I compute a set of axes based on the polygons (a vector perpendicular to every edge of each polygon) and then for each axis in the set, I project both polygons onto that axis and see if they overlap. As soon as I find an axis in which the polygons are not overlapping, I know that they are not intersecting – no need to check all of them.

Pretty simple, and actually quite easy to code – Around 20 lines of C# for a relatively efficient implementation of the check itself, and another 20 lines to compute the list of axes without any duplicates, since there’s no point in checking the same axis twice.

With the algorithm described above, it’s actually quite simple to build motion on top of it. Once I’ve projected both polygons onto a given axis, and confirmed that they don’t intersect, I can do another projection – I can project the velocity of the moving polygon onto that same axis, and add that to the original projection of the moving polygon to get the projection of its new, desired location. At that point, I know whether the polygon will intersect on this axis as a result of moving, and not only that, I can compute the amount of intersection by determining how much the two intervals overlap. Once I have that value, I can use it to compute a vector that will separate the two polygons.

Once I iterate over all the axes and compute separating vectors, I can pick the smallest separating vector and add it to the original velocity of the polygon. At this point I can do some checks to see if the new velocity is acceptable – for example, did the separating vector cause the polygon to change directions entirely? In that case, it’s probably not acceptable, and we can just flat-out reject movement and say that the polygon cannot move in this direction at all. Likewise, if the separating vector is enormous, that means the polygon moved all the way through the obstruction to avoid collision, which we obviously don’t want either, so we can reject it.

The end result was essentially this:

  • For every axis, project movingPolygon and stationaryPolygon onto that axis. This produces two intervals – movingInterval and stationaryInterval.
  • Check to see if movingInterval and stationaryInterval overlap. This tells us whether or not the polygons were already colliding.
  • Project the velocity of movingPolygon onto the axis, and add that projected value to movingInterval. This gives us the new interval of the moving polygon – newInterval.
  • Check to see if newInterval and stationaryInterval overlap. If they overlap, determine how much they overlap by – overlapAmount. At this point we know whether or not the two objects would have collided.
  • If the newInterval is overlapping, we can now compute a vector that will separate the two polygons, by multiplying the current axis by the amount of overlap (the axis is a unit vector, and the amount of overlap is a distance).
  • Add the separating vector to the original velocity of the polygon, to compute the new candidate velocity – newVector.
  • Check to see if Magnitude(newVector) <= Magnitude(movingPolygonVelocity). If not, reject the new vector.
  • Check to see if Normalize(newVector) ·dot· Normalize(movingPolygonVelocity) >= 0. If not, reject the new vector. (This ensures that we don’t push the object in the opposite direction to resolve the potential collision – in this case, the magnitude would have still passed the previous check)
  • If the new vector was not rejected, we know that the two objects will not be colliding.
  • Once you’ve iterated over all the axes, select the largest non-colliding vector (if any). That’s your resultVector.

At the end of this algorithm, you have four outputs:

  • areColliding (right now, before applying the velocity)
  • wouldHaveCollided (if we applied the original velocity)
  • willCollide (if you apply the result velocity)
  • resultVelocity

In the case where the collision could not be resolved, the resultVelocity will be 0. In the case where there was no collision, the resultVelocity will be the input velocity. In every other case, the resultVelocity is a vector that safely moves the polygon in the desired direction, without producing a collision.

One detail to note is that the quality of the result velocity depends on the axes that you check. If you only check axes generated from the shapes of the colliding objects, you can get strange (though valid) results – for example, two boxes will not produce any axes except X and Y to check, so if the object is moving at an angle, the algorithm will miss the ideal axis to compute the resultVelocity from. There are a few ways to solve this – you can pre-compute a set of ‘basic axes’ to check in every case, or you can derive a set of axes from the velocity of the object. Either way, though, the algorithm seems to work quite well – it works as well as the old one did in the platformer, and passes all my unit tests.

It’s always nice to go from 100 lines of code to 10 lines of code. Unfortunately, I’m not done yet. My previous implementation supported walking up and down slopes in a natural manner, and it also supported moving platforms. The new implementation supports neither, so I have some more work to do. Hopefully I can solve both of those problems using pure math, too – I’m working on sloped surfaces right now, and it looks like I can make it work with pure math by improving on my existing algorithm.

You can see the source code for my implementation here. Note that there’s some commented out bits in there from my work on sloped surfaces. :)

Tags: , , , , , , , ,

luminance is Digg proof thanks to caching by WP Super Cache