Silverlight Pirate Game and Farseer Physics Engine

I have recently been working on a Pirate Game in Silverlight 1.1 Alpha and decided to use the Farseer Physics Engine to handle collisions, etc. I used Andy Beaulieu's Getting Started with Farseer Physics and Silverlight to create my SpriteBase class. I originally used the Silverlight Games 101 tutorials to get started and got turned on to Farseer from that site.

Since this is a Pirate game you will be sailing around in a ship encountering other ships and various islands. One of the big challenges I ran into right from the start was getting the Vertices setup for collision with an island. I am unable to visualize more than a view Vertex points at a time so I was having trouble getting the island border setup correctly. For those unaware, Farseer provides a Body class which just represents a body in space. You can assign the Body a Geometry object that actually defines its physical structure within your world. The GeomFactory class provides methods for creating circle, rectangle and polygon geometries.Since I have a island I was in need of a ploygon geometry which is created using a collection of Vector2 instances within a Vertices instance.

Geom g = GeomFactory.Instance.CreatePolygonGeom(this.PhysicsSimulator, this.Body, vertices, 1);

When creating the Vector2 instances to add to the Vertices collection you have to specify the X and Y values in relation to the center of the geometry. In other words, if I have a geometry that will be 64 X 64 and want to set a Vertex at the top, left of the square the Vector2 instance would be new Vector2(-32, -32). For an island that is 512 X 512 that is a lot of going back and forth with the calculator.

Anyway, not matter what I did I still couldn't get the vertices correct. I eventually created a helper method in a Utils class that will accept the Vertices collection, a width and height and create a Path instance that I can add to my Canvas in order to see the Vertices. This greatly helped me to understand where I was placing my points. Here is the CreatePathFromVertices method I created:

public static Path CreatePathFromVertices(Vertices vertices, double width, double height)
        {
            Path path = new Path();
            path.Stroke = new SolidColorBrush(Colors.Magenta);
            path.StrokeThickness = 1;
            path.Fill = new SolidColorBrush(Colors.Transparent);
            PathGeometry pathGeom = new PathGeometry();
            PathFigureCollection figures = new PathFigureCollection();
            pathGeom.Figures = figures;
            PathFigure figure = new PathFigure();
            figure.StartPoint = new Point((double)vertices[0].X, (double)vertices[0].Y);
            figure.Segments = new PathSegmentCollection();
            pathGeom.Figures.Add(figure);

            foreach (var vector in vertices)
            {
                LineSegment line = new LineSegment() { Point = new Point((double)vector.X, (double)vector.Y) };
                figure.Segments.Add(line);
            }

            TranslateTransform trans = new TranslateTransform();
            trans.X = width / 2;
            trans.Y = height / 2;
            path.RenderTransform = trans;

            path.Data = pathGeom;
            return path;
        }

To use it all I do is call it after creating my Vertices collection and add the returned Path to my UserControl Canvas:

this.Root.Children.Add(Utils.CreatePathFromVertices(vertices, this.Root.Width, this.Root.Height));

This was working better but I wanted to be able to create the Vertices collection a little faster. I had originally created my island image in Photoshop and so I opened up the PSD and added a new transparent layer called Vertices. In the Vertices layer I proceeded to draw points around the island using a 1px Pencil of Magenta color. After I created a point based structure around the image I saved just the Vertices layer as a PNG. I then wrote a C# WinForms application to load up the PNG and parse it pixel by pixel, creating a Vertex for each of the Magenta points located within the image. The WinForms application then writes out the Vertices code required by the Farseer engine based on these Vertex instances.

Doing this gave me the Vertices I needed but not in the order I needed. The CreatePloygonGeom method requires the vertices to be in a clockwise or counter clockwise order. Parsing each pixel of an image only gives you the pixels in order from each x to y or y to x. I ended having to add the Image x and y location of each pixel as a comment to the end of the Vertices.Add line of code being generated by my WinForms application. From there I was able to use Photoshop to re-order the Vertices so that they rendered correctly.

vertices.Add(new Vector2(-208f, -173f)); // 48, 83
vertices.Add(new Vector2(-126f, -240f)); // 130, 16
vertices.Add(new Vector2(-61f, -200f)); // 195, 56

Using my CreatePathFromVertices method I was able to see the outline of the shape I created as the collision geometry for my island. One this I did notice was that I needed the very first line of the Vertices code to be repeated at the very end in order to join the geometry shape.

vertices.Add(new Vector2(-208f, -173f)); // 48, 83
vertices.Add(new Vector2(-126f, -240f)); // 130, 16
vertices.Add(new Vector2(-61f, -200f)); // 195, 56
...
vertices.Add(new Vector2(-208f, -173f)); // 48, 83

Here is a screenshot of the game with the Path around the island displayed. Granted the path is not perfect but sufficient for the game I think. I can always alter it but the more Vertices you add the longer it takes to load.

Here is the full code from the Constructor of my island class:

public SpringIsland(PhysicsSimulator physicsSimulator)
            : base (physicsSimulator)
        {
            System.IO.Stream s = this.GetType().Assembly.GetManifestResourceStream("Pirates.SpringIsland.xaml");
            this.Initialize(new System.IO.StreamReader(s).ReadToEnd());

            this.Body = BodyFactory.Instance.CreateRectangleBody(this.PhysicsSimulator, (float)this.Root.Width, (float)this.Root.Height, 1000);
            this.Body.IsStatic = true;

            #region Vertices
            Vertices vertices = new Vertices();
            vertices.Add(new Vector2(-208f, -173f)); // 48, 83
            vertices.Add(new Vector2(-126f, -240f)); // 130, 16
            vertices.Add(new Vector2(-61f, -200f)); // 195, 56
            vertices.Add(new Vector2(9f, -201f)); // 265, 55
            vertices.Add(new Vector2(43f, -241f)); // 299, 15
            vertices.Add(new Vector2(152f, -256f)); // 408, 0
            vertices.Add(new Vector2(235f, -209f)); // 491, 47
            vertices.Add(new Vector2(218f, -155f)); // 474, 101
            vertices.Add(new Vector2(118f, -145f)); // 374, 111
            vertices.Add(new Vector2(23f, -50f)); // 279, 206
            vertices.Add(new Vector2(114f, 55f)); // 370, 311
            vertices.Add(new Vector2(90f, 92f)); // 346, 348  
            vertices.Add(new Vector2(152f, 97f)); // 408, 353
            vertices.Add(new Vector2(235f, 148f)); // 491, 404
            vertices.Add(new Vector2(216f, 219f)); // 472, 475
            vertices.Add(new Vector2(147f, 252f)); // 403, 508
            vertices.Add(new Vector2(26f, 241f)); // 282, 497   
            vertices.Add(new Vector2(-62f, 234f)); // 194, 490
            vertices.Add(new Vector2(-118f, 203f)); // 138, 459
            vertices.Add(new Vector2(-126f, 167f)); // 130, 423
            vertices.Add(new Vector2(-156f, 143f)); // 100, 399
            vertices.Add(new Vector2(-218f, 129f)); // 38, 385
            vertices.Add(new Vector2(-233f, 103f)); // 23, 359
            vertices.Add(new Vector2(-232f, 35f)); // 24, 291
            vertices.Add(new Vector2(-200f, 16f)); // 56, 272
            vertices.Add(new Vector2(-195f, -32f)); // 61, 224
            vertices.Add(new Vector2(-230f, -95f)); // 26, 161
            vertices.Add(new Vector2(-199f, -141f)); // 57, 115
            vertices.Add(new Vector2(-208f, -173f)); // 48, 83
            #endregion

            this.Root.Children.Add(Utils.CreatePathFromVertices(vertices, this.Root.Width, this.Root.Height));

            Geom g = GeomFactory.Instance.CreatePolygonGeom(this.PhysicsSimulator, this.Body, vertices, 1);
            g.CollisionHandler += new FarseerGames.FarseerPhysics.Collisions.Geom.CollisionHandlerDelegate(this.HandleCollision);
        }

The code in the "Vertices" region was generated by the C# WinForms application and I re-ordered manually. I will release the source to the Pirate game once it is complete, if you want the prototype code just contact me. I will probably post the prototype online once I get the Ship AI and cannons working so at least there would be more to do that just sail around the island. :)

Here is the code for the C# WinForms app that will read the image pixel data and create the Vertices code segment: SilverlightImageToVertices.zip

12/29/2007