SyntaxHighlighter

Saturday 5 March 2011

I promised two blog posts today, and for once I've exceeded a target!

I just want to post an update on DBP 2011. I've decided, rather than try to rush and finish Sphero in time for the June deadline, I took up an offer of a collaboration from one of the devs I follow on twitter.

I'm pretty excited about it, as it'll be the first collaborative project I've worked on.

I won't give anymore details about the collaboration at this stage, as its really my new team mates gig, but hopefully I'll be able to post an update in the future.

I'll still be working on Sphero and Alta (in fact we'll probably use Alta's editors in some form for the game), and another project I'll talk about in a second, but the collaboration will be the focus. You can still expect Sphero to make an appearance at DBP 2012! :)

One final note, I've started work on a side project, which I've dubbed 'Xnity'. As the name suggests, its a bridge between XNA and Unity, and will consist of some (but not all) of the core xna classes.

The aim is to minimize the number of code changes xna devs need to port their games to Unity, essentially just using Unity as a compatibility layer to reach more platforms than xna offers alone.

It will be an open source project, and I'm hoping other xna/ unity devs will get involved as well.

This is very much a side project, but I'd still like to get through a few classes a week.

Well, that's plenty of blogging for now, I have work to do!

Quick 2d terrain with Farseer

This is going to be a quick tutorial on one approach to implementing 2d terrain in Farseer. I'm going to assume you already have a reasonable grasp of xna and at least a basic grasp of Farseer, and that you either have access to an extensible map editor, you can build one, or that you're happy to create the coords for your terrain in some other way (perhaps in ASCII in a text file, or hard coded values).

However you get your data, for this tutorial you'll need to get it in the following format:

• Your terrain will need to be divided up into 'ledges', or continuous solid lines.

• Your ledges will need to be divided up into 'edges', a straight line between 2 'nodes'. The end node of one edge must be the beginning node of the next.

• Each ledge must be stored as a List of Vector2, each containing the coordinates in screenspace of the nodes (in the order they appear from one end of the ledge to the other).

Obviously you can adapt this, (array instead of list, Point instead of Vector2 etc) but for now I'll assume we are using the above.

We can now plug into the following method:


public CreatePhysicsLedge(List<Vector2> vectors, float friction, float restitution)
{
    body = BodyFactory.CreateBody(game.world);
    body.BodyType = BodyType.Static;
    body.IsStatic = true;
                
    fixtures = new List<Fixture>();

    for (int i = 1; i < vectors.Count; i++)
    {
        Vector2 tempVec1 = ConvertUnits.ToSimUnits(vectors[i - 1]);
        Vector2 tempVec2 = ConvertUnits.ToSimUnits(vectors[i]);
        EdgeShape shape = new EdgeShape(tempVec1, tempVec2);
        if (i != vectors.Count - 1)
        {
            shape.HasVertex3 = true;
            shape.Vertex3 = ConvertUnits.ToSimUnits(vectors[i + 1]);
        }
        if (i != 1)
        {
            shape.HasVertex0 = true;
            shape.Vertex0 = ConvertUnits.ToSimUnits(vectors[i - 2]);
        }
        fixtures.Add(body.CreateFixture(shape));
    }
    for (int i = 0; i < fixtures.Count; i++)
    {
        fixtures[i].Friction = friction;
        fixtures[i].Restitution = restitution;
    }
}

This makes use of the ConvertUnits class from the Farseer samples.

And that's it! Simple as that.

Hopefully that will save some other devs some time here or there. Enjoy!

Promised update!

Two posts today. First of all, a Sphero update. I originally intended to spend about a day implementing wall-crawling. In reality its taken me over a week, mostly due to me not understanding how Farseer works.

Needless to say I got there in the end, and I have a better understanding of Farseer as a result, which I'll go into in a second. First though, a video of wall-crawling in action:


Not much to look at, I know, but a lot of blood sweat and tears (ok, coffee, pondering, and swearing at my monitor) went into getting it working, so I feel I should share some of the lessons I've learned.

My 2d terrain is made up of EdgeShapes, which are polygons with 2 vertices and a single edge. In other words, lines!
In order to stick to the various surfaces, I decided to look at all of the Fixtures that the character is touching each frame, check to see if they're terrain, and if they are, add the collision normals between the character and each fixture together, and apply a force to the character in the opposite direction.
That probably wasn't very clear, so here's a handy diagram to demonstrate the idea:

The first problem I hit was, well, it didn't work. My character, instead of sticking to surfaces, was instead floating above them.
Debugging showed that although most of the normals were reporting correctly, some were coming out as straight up.
Let me back track a bit, and show you how I implemented this:

{
    {
        ContactEdge tempContactEdge = circle.Body.ContactList;
        Contact tempContact;
        Vector2 normalSum = Vector2.Zero;
        int count = 0;
        if (tempContactEdge != null)
        {
            while (tempContactEdge != null)
            {
            if (tempContactEdge.Contact.FixtureA.CollisionFilter.IsInCollisionCategory(Category.Cat11) || tempContactEdge.Contact.FixtureA.CollisionFilter.IsInCollisionCategory(Category.Cat12))
            {
                tempContact = tempContactEdge.Contact;
                count++;
            }
            else if (tempContactEdge.Contact.FixtureB.CollisionFilter.IsInCollisionCategory(Category.Cat11) || tempContactEdge.Contact.FixtureB.CollisionFilter.IsInCollisionCategory(Category.Cat12))
            {
                tempContact = tempContactEdge.Contact;
                count++;
            }
            else
            {
                tempContactEdge = tempContactEdge.Next;
                continue;
            }
            if (tempContact.Manifold.PointCount != 0)
            {
                Vector2 tempNormal;
                FixedArray2<Vector2> tempPoints;
                tempContact.GetWorldManifold(out tempNormal, out tempPoints);
                normalSum += tempNormal;
            }
            else
            {
                count--;
            }
            tempContactEdge = tempContactEdge.Next;
        }
        if (count > 0)
        {
            circle.Body.IgnoreGravity = true;
            normalSum /= count;
            normalSum.Normalize();
            normalSum *= (-gravForce);
            circle.Body.ApplyForce(normalSum);
        }
    }
}

I grab a reference to the characters contact list, which is essentially a doubly linked list of Contacts. I iterate through it, looking for Contacts that contain fixtures that are in my terrain collision category, and then for those that are, I call the contact's GetWorldManifold() method, which gives me back a collision normal and the manifoldPoints of the collision in world coordinates. I've included a quick diagram to illustrate what a manifold is below, just in case you're not familiar with it:




I keep a cumulative sum of the normals as well as a count of how many there are, and then take an average at the end. Then all that remains is to normalize the average (just in case), and apply a force on at the centre of our character in the opposite direction.


So you can see that random additions of normals pointing in the wrong direction could cause problems!


After some digging inside Farseer I discovered that GetWorldManifold() returns Vector2.UnitY when called on a contact which has manifold.pointCount = 0 (the points it is referring to are those in the diagram above).


This confused me no end. My understanding of the engine was that a contact being included in an object's contact list meant that the two objects had collided. Turns out that this isn't quite the case.


After posting on the Farseer forums (a great bunch, just make sure you search before asking!) to see if this was a bug, I was put straight. A contact is formed when two AABBs (think of them as large bounding boxes) intersect. Each shape or fixture has a AABB to quickly check for possible collisions. The engine can then check whether the two Fixtures associated with the AABBs have collided and make them react appropriately. If you'd like an illustration of AABBs and what they do, download the Testbed solution from the Farseer codeplex page and hit F4 while its running.

So whenever my character got to the end of an EdgeShape, it would hit the AABB of the next EdgeShape, but wouldn't yet be in contact with the shape itself, causing manifold.pointCount to be zero, and giving me my floaty behaviour.

This was easy to fix, I just had to make sure manifold.pointCount > 0 and it was fixed. Or so I thought.

It worked just fine for concave edges and shapes, I.e. the inside of a room, which was exactly what my test map happened to be.

However, when I then built a map with an island in the middle, the character would get to a convex (or 'pointy') join between edges, and then fall off.

If we look back at the algorithm I described, it should be fairly obvious what was happening: the character would lose contact with all fixtures, and my code then reapplied gravity until it touched another fixture, I.e the floor.

So my solution was to attach a second, larger circle fixture to the character body with zero density, and set isSensor to true.

The idea was to use this as a backup, so that when the main character fixture loses contact with all fixtures, we can use the sensor to check if there are any other fixtures near by, and we can use that to give us the direction of our wall crawling force.

The final issue I came across was that sensors don't generate manifolds, so I had to manually calculate the normals on the EdgeShapes.

After all that, I ended up with the video above. *Phew*

That was a bit long winded, so for those of you that prefer code, I've added the finished code snippet below.



{
    {
        ContactEdge tempContactEdge = circle.Body.ContactList;
        Contact tempContact;
        Vector2 normalSum = Vector2.Zero;
        int count = 0;
        if (tempContactEdge != null)
        {
            while (tempContactEdge != null)
            {
            if (tempContactEdge.Contact.FixtureA.CollisionFilter.IsInCollisionCategory(Category.Cat11) || tempContactEdge.Contact.FixtureA.CollisionFilter.IsInCollisionCategory(Category.Cat12))
            {
                tempContact = tempContactEdge.Contact;
                count++;
            }
            else if (tempContactEdge.Contact.FixtureB.CollisionFilter.IsInCollisionCategory(Category.Cat11) || tempContactEdge.Contact.FixtureB.CollisionFilter.IsInCollisionCategory(Category.Cat12))
            {
                tempContact = tempContactEdge.Contact;
                count++;
            }
            else
            {
                tempContactEdge = tempContactEdge.Next;
                continue;
            }
            if (tempContact.Manifold.PointCount != 0)
            {
                Vector2 tempNormal;
                FixedArray2<Vector2> tempPoints;
                tempContact.GetWorldManifold(out tempNormal, out tempPoints);
                normalSum += tempNormal;
            }
            else if (tempContactEdge.Contact.FixtureA.CollisionFilter.IsInCollisionCategory(Category.Cat12))
            {
                EdgeShape eShape = (EdgeShape)tempContactEdge.Contact.FixtureA.Shape;
                Vector2 v = eShape.Vertex2 - eShape.Vertex1;
                Vector2 w = sensorCircle.Body.Position - eShape.Vertex1;
                float t = Vector2.Dot(w, v) / Vector2.Dot(v, v);
                v = eShape.Vertex1 + (t * v) - sensorCircle.Body.Position;
                v.Normalize();
                v *= -1;
                normalSum += v;
            }
            else if (tempContactEdge.Contact.FixtureB.CollisionFilter.IsInCollisionCategory(Category.Cat12))
            {
                EdgeShape eShape = (EdgeShape)tempContactEdge.Contact.FixtureB.Shape;
                Vector2 v = eShape.Vertex2 - eShape.Vertex1;
                Vector2 w = sensorCircle.Body.Position - eShape.Vertex1;
                float t = Vector2.Dot(w, v) / Vector2.Dot(v, v);
                v = eShape.Vertex1 + (t * v) - sensorCircle.Body.Position;
                v.Normalize();
                v *= -1;
                normalSum += v;
            }
            else
            {
                count--;
            }
            tempContactEdge = tempContactEdge.Next;
        }
        if (count > 0)
        {
            circle.Body.IgnoreGravity = true;
            normalSum /= count;
            normalSum.Normalize();
            normalSum *= (-gravForce);
            circle.Body.ApplyForce(normalSum);
        }
    }
}

That's it for this post, but I'll be putting another post up in a bit with a short tutorial on 2d terrain in Farseer.
See you in a bit.

Wednesday 2 March 2011

Updates soon!

I've been reinstalling my operating system recently, so I haven't been able to post, but I have an update that I'm prepping for the weekend, plus hopefully a quick tutorial on 2d terrain with Farseer.

More soon!