FPE 3.3 (beta help needed)

Topics: Developer Forum, User Forum
Mar 15, 2011 at 11:02 AM

Hi Guys,

I know that the new version of Farseer is not to be out yet but the reason why I am using it is because at the moment it has made it easier for me to create a world similar to that of Pinball and even add images which are then changed into different fixtures and so on.... great so far. I am trying to implement a game right now and I am using the same collision code that was used in version 3.2 and for what I can see it works but my problem is that I want to remove a body from the world and in theory it is removing the body but it is not removing the Sprite and no matter what I do I can't seem to find out how :s

This is the code so far (from using the Pinball game on development)

using System.Text;
using FarseerPhysics.Collision.Shapes;
using FarseerPhysics.Common;
using FarseerPhysics.Dynamics;
using FarseerPhysics.Dynamics.Joints;
using FarseerPhysics.Factories;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Graphics;
using FarseerPhysics.Dynamics.Contacts;
using System.Collections.Generic;
using FarseerPhysics.Common.PolygonManipulation;

namespace FarseerPhysics.SamplesFramework
{
    internal class GameDemo2 : PhysicsGameScreen, IDemoScreen
    {
        private Sprite _rectangleSprite;
        private Sprite _rectangleObstacleSprite;
        private Sprite _rectangleObstacleSprite2;
        private Sprite _circleSprite;
        private List<Body> _removeBodies = new List<Body>();
        private Body _jumper;
        public Body bodyWorld;
        private Vector2 _position;

        bool resetPos = false;
        public float _halfWidth;
        public float _halfHeight;
        public int totalScore = 0;

        public const int myLimit = 10;
        private Fixture[] myRectangleRed = new Fixture[myLimit];
        private Fixture[] myRectangleBlue = new Fixture[myLimit];
        private Fixture myBall;
        Vector2 startPosition = new Vector2(0, 0);
        Vector2 endPosition = new Vector2(0, 0);

        #region IDemoScreen Members

        public string GetTitle()
        {
            return "Pinball";
        }

        public string GetDetails()
        {
            StringBuilder sb = new StringBuilder();
            sb.AppendLine("TODO: Add sample description!");
            sb.AppendLine(string.Empty);
            sb.AppendLine("GamePad:");
            sb.AppendLine("  - Exit to menu: Back button");
            sb.AppendLine(string.Empty);
            sb.AppendLine("Keyboard:");
            sb.AppendLine("  - Exit to menu: Escape");
            return sb.ToString();
        }

        #endregion

        public override void LoadContent()
        {
            base.LoadContent();
            World.Gravity = new Vector2(0.0f, 10.0f);
            //HasCursor = false;

            _halfHeight = ConvertUnits.ToSimUnits(ScreenManager.GraphicsDevice.Viewport.Height) / 2f - 0.75f;
            _halfWidth = ConvertUnits.ToSimUnits(ScreenManager.GraphicsDevice.Viewport.Width) / 2f - 0.75f;

            Vertices bounds = new Vertices(4);
            bounds.Add(new Vector2(-_halfWidth, _halfHeight));
            bounds.Add(new Vector2(_halfWidth, _halfHeight));
            bounds.Add(new Vector2(_halfWidth, -_halfHeight));
            bounds.Add(new Vector2(-_halfWidth, -_halfHeight));      
            
            bodyWorld = BodyFactory.CreateLoopShape(World, bounds);

            //Top arc
            FixtureFactory.AttachLineArc(MathHelper.Pi, 50, 12.5f, new Vector2(0, -1.7f), MathHelper.Pi, false, bodyWorld);

            //Launcher (internal where jumper and ball starts)
            FixtureFactory.AttachEdge(new Vector2(11.5f, 13.5f), new Vector2(11.5f, -2.5f), bodyWorld);
            FixtureFactory.AttachEdge(new Vector2(11.5f, 13.5f), new Vector2(12.44f, 13.5f), bodyWorld);
            FixtureFactory.AttachEdge(new Vector2(12.44f, 13.5f), new Vector2(12.44f, -2.5f), bodyWorld);
            FixtureFactory.AttachEdge(new Vector2(-12.44f, 13.5f), new Vector2(-12.44f, -2.5f), bodyWorld);

            //Jumper
            _jumper = BodyFactory.CreateRectangle(World, 0.83f, 0.3f, 5, new Vector2(12f, 10));
            _jumper.BodyType = BodyType.Dynamic;

            // create sprite based on body
            _rectangleSprite = new Sprite(ScreenManager.Assets.TextureFromShape(_jumper.FixtureList[0].Shape,
                                                                                MaterialType.Squares,
                                                                                Color.Orange, 1f));

            Vector2 axis = new Vector2(0.0f, -1.0f);
            FixedPrismaticJoint jumperJoint = JointFactory.CreateFixedPrismaticJoint(World, _jumper, _jumper.Position, axis);
            jumperJoint.MotorSpeed = 70.0f;
            jumperJoint.MaxMotorForce = 250.0f;
            jumperJoint.MotorEnabled = true;
            jumperJoint.LowerLimit = -3.2f;
            jumperJoint.UpperLimit = -0.1f;
            jumperJoint.LimitEnabled = true;

            LoadBall();
            LoadObstacles();
        }

        public void LoadBall()
        {            
            myBall = FixtureFactory.CreateCircle(World, 0.4f, 2.0f, new Vector2(12f, 9.0f), _circleSprite);
            myBall.Body.BodyType = BodyType.Dynamic;
            myBall.Body.IsBullet = true;
            _circleSprite = new Sprite(ScreenManager.Assets.TextureFromShape(myBall.Shape,
                                                                    MaterialType.Squares,
                                                                    Color.White, 1f));
        }

        public void LoadObstacles()
        {            
            for (int i = 0; i < myLimit; i++)
            {
                startPosition = new Vector2(-2, 0);
                endPosition = new Vector2(-12, 0);
                myRectangleRed[i] = FixtureFactory.CreateRectangle(World, 0.4f, 0.4f, 1, Vector2.Lerp(startPosition, endPosition, i / (float)(myLimit - 1)), _rectangleObstacleSprite);
                myRectangleRed[i].Body.IsStatic = true;
                myRectangleRed[i].Body.Mass = 0.1f;

                // create sprite based on body
                _rectangleObstacleSprite = new Sprite(ScreenManager.Assets.TextureFromShape(myRectangleRed[i].Shape,
                                                                                    MaterialType.Squares,
                                                                                    Color.Red, 1f));
                myRectangleRed[i].OnCollision += OnCollision;
            }

            for (int i = 0; i < myLimit-3; i++)
            {
                startPosition = new Vector2(5, -5);
                endPosition = new Vector2(-7, -5);
                myRectangleBlue[i] = FixtureFactory.CreateRectangle(World, 0.4f, 0.4f, 1, Vector2.Lerp(startPosition, endPosition, i / (float)(myLimit - 1)), _rectangleObstacleSprite);
                myRectangleBlue[i].Body.IsStatic = true;
                myRectangleBlue[i].Body.Mass = 0.1f;

                // create sprite based on body
                _rectangleObstacleSprite2 = new Sprite(ScreenManager.Assets.TextureFromShape(myRectangleBlue[i].Shape,
                                                                                    MaterialType.Squares,
                                                                                    Color.BlueViolet, 1f));
                myRectangleBlue[i].OnCollision += OnCollision;
            }
        }

        private bool OnCollision(Fixture fixtureA, Fixture fixtureB, Contact contact)
        {
            totalScore++;
            Body body1 = fixtureA.Body;
            Body body2 = fixtureB.Body;
            float mass1 = body1.Mass;
            float restitution2 = body2.Restitution;
            float mass2 = body2.Mass;

            if (mass1 > 0.0f && mass2 > 0.0f)
            {
                if (mass2 > mass1)
                {
                    if (!_removeBodies.Contains(body1))
                    {
                        _removeBodies.Add(body1);
                    }
                }
            }
            return true;
        }

        public override void HandleInput(InputHelper input, GameTime gameTime)
        {
            if (input.KeyboardState.IsKeyDown(Keys.Space))
                _jumper.ApplyForce(new Vector2(0, 250));

            base.HandleInput(input, gameTime);
        }

        public override void Update(GameTime gameTime, bool otherScreenHasFocus, bool coveredByOtherScreen)
        {
            if (IsActive)
            {
                for (int i = 0; i < _removeBodies.Count; i++)
                {
                    World.RemoveBody(_removeBodies[i]);                    
                }
            }

            if (resetPos == true)
            {
                World.RemoveBody(myBall.Body);
                LoadBall();
                resetPos = false;
            }
            else
            {
                if (myBall.Body.Position.Y > 13 && myBall.Body.Position.Y < 13.5)
                {
                    resetPos = true;
                }
                else resetPos = false;
            }

            _removeBodies.Clear();
            base.Update(gameTime, otherScreenHasFocus, coveredByOtherScreen);
        }

        public override void Draw(GameTime gameTime)
        {
            string Score = "Score: " + totalScore.ToString();
            _position = new Vector2(_halfWidth * 44, 25);

            // create moving bodies
            ScreenManager.SpriteBatch.Begin(0, null, null, null, null, null, Camera.View);

            for (int i = 0; i < myLimit; i++)
            {
                // draw Red obstacles
                ScreenManager.SpriteBatch.Draw(_rectangleObstacleSprite.texture, ConvertUnits.ToDisplayUnits(myRectangleRed[i].Body.Position), null,
                                            Color.White, myRectangleRed[i].Body.Rotation, _rectangleObstacleSprite.origin, 1f, SpriteEffects.None, 0f);
            }
            for (int i = 0; i < myLimit - 3; i++)
            {
                // draw Blue obstacles
                ScreenManager.SpriteBatch.Draw(_rectangleObstacleSprite2.texture, ConvertUnits.ToDisplayUnits(myRectangleBlue[i].Body.Position), null,
                                            Color.White, myRectangleBlue[i].Body.Rotation, _rectangleObstacleSprite2.origin, 1f, SpriteEffects.None, 0f);
            }

            // draw jumper
            ScreenManager.SpriteBatch.Draw(_rectangleSprite.texture, ConvertUnits.ToDisplayUnits(_jumper.Position), null,
                                            Color.White, _jumper.Rotation, _rectangleSprite.origin, 1f, SpriteEffects.None, 0f);
            // draw ball
            ScreenManager.SpriteBatch.Draw(_circleSprite.texture, ConvertUnits.ToDisplayUnits(myBall.Body.Position), null,
                                Color.White, myBall.Body.Rotation, _circleSprite.origin, 1f, SpriteEffects.None, 0f);

            //draw score
            ScreenManager.SpriteBatch.DrawString(ScreenManager.Fonts.DetailsFont, Score,
                                    _position + Vector2.One, Color.Black);
            ScreenManager.SpriteBatch.DrawString(ScreenManager.Fonts.DetailsFont, Score,
                                                    _position, Color.White);

            ScreenManager.SpriteBatch.End();

            // for background purposes only
            ScreenManager.LineBatch.Begin(Camera.SimProjection, Camera.SimView);

            // draw ground
            for (int i = 0; i < bodyWorld.FixtureList.Count; ++i)
            {
                ScreenManager.LineBatch.DrawLineShape(bodyWorld.FixtureList[i].Shape, Color.Black);
            }
            ScreenManager.LineBatch.End();

            base.Draw(gameTime);
        }
    }
}

I reckon the problem has to do with the Draw function but my knowledge on this part is not great.

If anyone has any ideas it would be of great help!

Developer
Mar 29, 2011 at 3:57 PM

I have not looked at all your code, cause it is way too much code for such an generic question. But regarding the sprites: They are only used within the samples and therefore not part of Farseer. Farseer does the physics part and graphics are completely decoupled from that. So you have to take care of sprite creation and deletion yourself. I'd recommend to write your own graphics solution, as the sprites from the samples are nice to show off Farseers features and maybe for prototyping things. But they were never meant for productive use and are therefore not optimized at all or tailored to fit any specific gametype for that matter.

Apr 11, 2011 at 12:24 PM

Hi Elsch,
Apart from using Silverlight and the XNA to create graphics. Where else can I use Farseer?. I have looked into OpenGL but the problem with OpenGL is that it is pretty much the same or worst in this case.

I have looked around and the closest is Chipmunk which is not in C# so not what I am looking for.

Now, I think I have solved the sprites problem by setting a couple of rules in the code but I have a question about the OnCollision bool function. Is there anyway that could be set for bodies rather than fixtures?

In Farseer 3.3 and onwards there are no more code for fixtures so they have to be created in the code so I was wondering if I could either change the function for OnCollision or is there anyway to change from a body to a fixture? (every time I try I keep getting a error for the fixture malcreation in the code)

For example using it in this code:

        public void LoadObstacle(string image)
        {
            //load texture that will represent the physics body
            _polygonBombTexture = ScreenManager.Content.Load<Texture2D>(image);

            //Create an array to hold the data from the texture
            uint[] data = new uint[_polygonBombTexture.Width * _polygonBombTexture.Height];

            //Transfer the texture data to the array
            _polygonBombTexture.GetData(data);

            //Find the vertices that makes up the outline of the shape in the texture
            Vertices textureVertices = PolygonTools.CreatePolygon(data, _polygonBombTexture.Width, false);

            //The tool return vertices as they were found in the texture.
            //We need to find the real center (centroid) of the vertices for 2 reasons:

            //1. To translate the vertices so the polygon is centered around the centroid.
            Vector2 centroid = -textureVertices.GetCentroid();
            textureVertices.Translate(ref centroid);

            //2. To draw the texture the correct place.
            _originBomb = -centroid;

            //We simplify the vertices found in the texture.
            textureVertices = SimplifyTools.ReduceByDistance(textureVertices, 4f);

            //Since it is a concave polygon, we need to partition it into several smaller convex polygons
            List<Vertices> list = BayazitDecomposer.ConvexPartition(textureVertices);

            //Set the Scale factor of the object
            _scaleBomb = 0.2f;

            //scale the vertices from graphics space to sim space
            Vector2 vertScale = new Vector2(ConvertUnits.ToSimUnits(1)) * _scaleBomb;
            foreach (Vertices vertices in list)
            {
                vertices.Scale(ref vertScale);
            }

            //Create a single body with multiple fixtures
            _compoundBomb = BodyFactory.CreateCompoundPolygon(World, list, 1f, new Vector2(_compoundWizard.Position.X, _compoundWizard.Position.Y*5));
            _compoundBomb.BodyType = BodyType.Dynamic;
    
        }

Thanks

Developer
Apr 11, 2011 at 1:27 PM

Farseer is a physics engine written in C# and tailored towards XNA / Silverlight development for Windows, WP7, and Xbox. If you don't want to develop for any of these platforms or use another programming language which is not .net compatible you could just use Box2D for instance. Farseer is a Box2D port and Box2D is available for all kinds of platforms.

Farseer, Box2D, Chipmunk, etc. are all "just" physics engines. Their purpose is calculating physics for you, how you draw, render or whatever these objects is up to you. From Farseer you get your bodies, their position, and their rotation... that should be all that is needed to display a 2D object on the screen.

Regarding your other questions may I suggest you have a look at: http://box2d.org/manual.html

In Farseer 3.3 nothing changed about the way how bodies, fixtures, and shapes are connected and how they work. Collision detection is still (and will in the future be) done per fixture. Therefore it is not possible to move OnCollision to bodies (and it wouldn't make any sense). The only thing that changed is that we have BodyFactories instead of FixtureFactories now. You can still create your fixtures manually and attach them to any body. Bodies still have fixture lists for all attached fixtures etc.

The only difference is that the new factories return a body (with attached fixtures) instead of one or more of the attached fixture(s). That makes sense because as pointed out earlier you mainly need bodies for drawing and not fixtures.

In a nutshell: The Factories (in general) create exactly the same stuff as before (bodies with attached fixtures). They just return another reference (to another part) of the created object.

Apr 11, 2011 at 2:11 PM

I see, I have tried using Box2D before but I was not sure about the graphics engine because I have seen some very nice games out there made using either Farseer or Box2D but decided on Farseer because I thought it was supported graphically. Anyway, thank you for the reply and I shall see how it works under Silverlight as I would like it to work under Windows or a website :)

I see what you mean about the bodies and fixtures, I hadn't grasp the concept before so now what I have done is define a OnCollision after the body has been created and then set everything in the bool function.

                        lineBody.OnCollision += new OnCollisionEventHandler(lineBody_OnCollision);

and the function is more or less like

        bool lineBody_OnCollision(Fixture fixtureA, Fixture fixtureB, Contact contact)
        {
            Body body1 = fixtureA.Body;
            Body body2 = fixtureB.Body;
            float mass1 = body1.Mass;
            float mass2 = body2.Mass;

            if (mass1 == 0.0f && mass2 > 0.0f)
            {
                if (mass2 > mass1)
                {
                    body2.ApplyTorque(200);
                    if (!_removeBodies.Contains(body2))
                    {
                        World.RemoveBody(lineBody); //remove body
                    }
                }
            }
            return true;
        }
Thank you for your help and sorry for the stupid questions.