A Simple Physics Game using Silverlight and Farseer Engine

Play Now!| Download the Farseer Physics Engine|Download Source These days, I have become very much interested in physics games. So I decided to search the internet for some good tutorials. In no time I discovered that such games were made using a 2D 'Physics Engine'. After more searching, I found the Box 2D Physics Engine. I decided to write a small physics based game using Silverlight, but was not able to find a good port of this engine for C#/Silverlight. So, I choose the Farseer Physics Engine which is freely available from Codeplex. As, I said, I am pretty new to using physics engines (and even writing games for that matter !), please don't expect too much from this tutorial.

Physics Engine Basics

A physics engine is basically a set of classes, that helps in simulating response of objects to forces and collisions. With a physics engine, we first programmatically create a 'World' with different types of 'Bodies'. Then we apply 'Force' to one or more body and 'Step' through time. The physics engine automatically updates the positions of these objects in the 'World' with respect to time.
        private  void Init()
        {
            _world = new World(new Vector2(0,0));
            InitializeGameBoard();
            CreateStaticObjects();
            CreateDynamicObjects();
     
        }

Two crucial things involved in using a physics engine are:
  • Continuously stepping the world forward in time and calculating new positions of bodies.
  • Representing the 'Bodies' of the "World" graphically on the game screen using a suitable drawing API.
Given below is the drawing subroutine which I have used in my game:
   public void DrawEverything()
        {
            canvas1.Children.Clear();
            canvas1.Children.Add(gameboard);
            foreach (Body b in _world.BodyList.Where(n => (n.UserData is ObjectTypes && ObjectTypes.Border != (ObjectTypes)n.UserData))) 
            DrawItem(b);
        }

        public void DrawItem(Body item)
        {
            if (item != null)
            {
                if (item.FixtureList != null && item.FixtureList.Count > 0)
                {                  
                  FarseerPhysics.Collision.Shapes.Shape s = item.FixtureList[0].Shape;
                    if (s != null)
                    {
                           
                        if (item.FixtureList[0].ShapeType == FarseerPhysics.Collision.Shapes.ShapeType.Polygon)
                        {
                            Polygon poly = new Polygon();
                            PolygonShape ps = (PolygonShape)item.FixtureList[0].Shape;
                            int vertexCount = ps.Vertices.Count;
                            poly.Points = new PointCollection();
                            gameTransform.Rotation = item.Rotation;
                            if (ps != null)
                                for (int i = 0; i < vertexCount; ++i)
                                { poly.Points.Add(gameTransform.Transform(new Point(ps.Vertices[i].X, ps.Vertices[i].Y))); }
                            poly.Stroke = new SolidColorBrush(Color.FromArgb(125, 0, 0, 0));

                            Canvas.SetLeft(poly, (item.Position.X-7.5)/scalefactor );
                            Canvas.SetTop(poly, (item.Position.Y+7.5)/scalefactor );

                            if (item.UserData is ObjectTypes)
                            {
                                ObjectTypes ot = (ObjectTypes)item.UserData;
                                switch (ot)
                                {
                                    case ObjectTypes.Sink :
                                        poly.Fill = SinkColorBrush;
                                        break;
                                    case ObjectTypes.Cubes :
                                        poly.Fill = normalObjectBrush;
                                        break;
                                    case ObjectTypes.Striker:
                                        poly.Fill = StrikerBrush;
                                        break;

                                }

                            }
                          
                            canvas1.Children.Add(poly);

                        }
                    }

                }
            }

        }

Basic idea behind the game

My game is pretty basic . It is very similar to carrom with the exception that all pieces are rectangular. The blue piece is called the 'striker' which you can move using arrow keys. All other pieces should be pushed in the four holes on the corner of the board, by colliding the striker against them. If you can get rid of all pieces within time, you win,else you loose.
The four white rectangles are of type "Border" and the black ones are "Sink". The 'Border' objects are never shown on the screen.
The actual game screen. The green rectangles are 'Cubes' and the blue one is the 'Striker'. There are four different categories of objects in the game. These are 'Border','Striker', 'Cubes' and 'Sink'. Graphically all objects are represented by rectangular shapes. The 'Border' and 'Sink' objects are "Static", i.e. they don't move if force is applied. Rest all objects are "Dynamic", i.e. they respond to collision and application of force. The 'Border' objects collide with all types of objects. The 'Sink' objects only collide with 'Cubes' (and not with 'Striker').The 'Striker' collides with all, except 'Sink'. The whole game has code behind of around 300 lines (excluding comments).

Using BackgroundWorker to do the processing

As shown in the above diagram, refreshing the game screen is part of continuous loop which constantly updates positions of all game objects. The process of calculation of new positions happens when we make the "World" take a "Step" in time . To achieve this in Silverlight application , I have used the BackgroundWorker class.
 bool World_OnCollision(Fixture fixtureA, Fixture fixtureB, FarseerPhysics.Dynamics.Contacts.Contact contact)
        {
            if (fixtureB.Body.UserData is ObjectTypes && ObjectTypes.Cubes == (ObjectTypes)fixtureB.Body.UserData)
            {
                _world.BodyList.Remove(fixtureB.Body);
            }
            score = (max_score - _world.BodyList.Where(n => n.UserData.Equals(ObjectTypes.Cubes)).Count());
            if (score == max_score)
            { isGameOn = false; button1.IsEnabled = true; }
            _worker.ReportProgress(0, score);
            return true;
        }

        void Worker_DoWork(object sender, DoWorkEventArgs e)
        {

            if ((_worker.CancellationPending == true))
            { e.Cancel = true; }
            else
            {
                _world.Step(1.0f);
                gametimectr--;

                if (gametimectr <= 0)
                    isGameOn = false;
            }
        }

        void Worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {label1.Content = e.UserState.ToString();}

        void Worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            DrawEverything();
            lblTime.Content = gametimectr.ToString();
            if (!_worker.IsBusy && isGameOn)
            { _worker.RunWorkerAsync(); }
            label1.Content = score;

            if (!isGameOn && max_score == score)
            { ShowEndGameMessage("You Won!"); return; }

            if (!isGameOn)
            { ShowEndGameMessage("Game Over!"); return; }

        }
The method 'Worker_DoWork' is executed via 'BackgroundWorker' to 'Step' further in our 'World'. The 'World_OnCollision' method is configured to handle all collisions that happen in our 'World'.Since this method is called from the 'BackgroundWorker', it does not have access to UI elements. Hence it calls '_worker.ReportProgress' to update the UI.

Doing more!

As I said earlier, this is my first attempt at using a physics engine. Currently my code only handles rectangles . Soon I will add code to handle more types of objects. I plan to write more articles after exploring features offered by the'Farseer' engine. I hope you found this article helpful for getting started with physics engines. Do let me know your comments, advice or criticism. Tip: Use the 'Write a Comment' link in the header bar to write a comment.


Copyright (c) 2007-2017 Ashish Patil . Please read FAQ for more details.

comments powered by Disqus