1
\$\begingroup\$

I created a little Windows Phone 8 game that supports multiple resolutions. If the resolution of the device is less than 720p, then the sprites and their corresponding Farseer bodies need to be scaled down. If I run my project with the 720p emulator, then everything looks fine. But if I choose an emulator with a lower resolution(for example WVGA emulator), then everything looks weird.

In this picture, I use the 720p emulator: http://s1.directupload.net/images/140520/gnpwvjsw.jpg

In this picture, I use the WVGA emulator: http://s14.directupload.net/images/140520/hxhicnph.jpg

If I use the WVGA emulator, then the Farseer collision shapes are drawn on another position than their corresponding sprites. In addition, a part of the red ball sprite is drawn in the blue rectangle sprite, but their corresponding shapes are drawn differently(the ball shape isn't in the rectangle shape).

How can I scale the Farseer bodies so that everything works correctly?

Here is my entire code:

Game1 class:

public class Game1 : Game
{
    GraphicsDeviceManager graphics;
    SpriteBatch spriteBatch;
    bool Draw_debugView = true;
    World world;
    Body Rectangle, Ball;
    Texture2D RectangleSprite, BallSprite;

    public Camera camera;

    public const int VirtualScreenWidth = 1280;
    public const int VirtualScreenHeight = 720;
    Vector2 vp, gameWorldSize = new Vector2(1280, 720);
    float ScaleX, ScaleY, Scale;

    DebugViewXNA physicsDebug;
    private Matrix _view;
    private Vector2 _cameraPosition;
    private Vector2 _screenCenter;

    private static float _displayUnitsToSimUnitsRatio = 100f;

    public static Vector2 ToDisplayUnits(Vector2 simUnits)
    {
        return simUnits * _displayUnitsToSimUnitsRatio;
    }   

    public Game1()
    {
        graphics = new GraphicsDeviceManager(this);
        Content.RootDirectory = "Content";
        graphics.IsFullScreen = true;
    }

    protected override void Initialize()
    {
        base.Initialize();
    }

    protected override void LoadContent()
    {
        spriteBatch = new SpriteBatch(GraphicsDevice);
        BallSprite = Content.Load<Texture2D>("ball");
        RectangleSprite = Content.Load<Texture2D>("tile");

        vp = new Vector2(GraphicsDevice.Viewport.Width, GraphicsDevice.Viewport.Height);

        ScaleX = vp.X / gameWorldSize.X;
        ScaleY = vp.Y / gameWorldSize.Y;
        Scale = Math.Min(ScaleX, ScaleY);
        camera = new Camera(new Vector2(640,360), Scale, vp);

        _view = Matrix.Identity;
        _cameraPosition = Vector2.Zero;

        _screenCenter = new Vector2(graphics.GraphicsDevice.Viewport.Width / 2f,
                                            graphics.GraphicsDevice.Viewport.Height / 2f);


        if (world == null)
        {
            world = new World(new Vector2(0, 1));
        }
        else
        {
            world.Clear();
        }

        physicsDebug = new DebugViewXNA(world);
        physicsDebug.DefaultShapeColor = Color.White;
        physicsDebug.SleepingShapeColor = Color.LightGray;
        physicsDebug.LoadContent(GraphicsDevice, Content);

        Ball = BodyFactory.CreateCircle(world, 0.25f*ScaleX, 1.0f);
        Ball.BodyType = BodyType.Dynamic;
        Ball.Position = new Vector2(6.4f, 0.6f);
        Ball.LinearVelocity = new Vector2(0, 2.0f);

        Rectangle = BodyFactory.CreateRectangle(world, 1.50f*ScaleX, 0.50f*ScaleY, 1.0f);
        Rectangle.BodyType = BodyType.Static;
        Rectangle.Position = new Vector2(6.4f, 3.6f);
        Rectangle.Rotation = 0f;
        Rectangle.Restitution = 0.5f;
        Rectangle.CollisionCategories = Category.Cat5;         
    }

    protected override void Update(GameTime gameTime)
    {
        float elapsed = (float)gameTime.ElapsedGameTime.TotalSeconds;
        world.Step(Math.Min(elapsed, (1f / 60f)));

        ScaleX = vp.X / gameWorldSize.X;
        ScaleY = vp.Y / gameWorldSize.Y;
        Scale = Math.Min(ScaleX, ScaleY);

        //The ball should always be the center of the camera
        camera.Update(gameTime, new Vector2(Ball.Position.X*100f, Ball.Position.Y*100f), Scale, vp);

        base.Update(gameTime);
    }

    protected override void Draw(GameTime gameTime)
    {
        GraphicsDevice.Clear(Color.CornflowerBlue);
        spriteBatch.Begin(SpriteSortMode.BackToFront, BlendState.AlphaBlend, null, null, null, null, camera.GetMatrix());
            spriteBatch.Draw(RectangleSprite, ConvertUnits.ToDisplayUnits(Rectangle.Position),
                                           null,
                                           Color.White, Rectangle.Rotation, new Vector2(RectangleSprite.Width / 2.0f, RectangleSprite.Height / 2.0f), 1f,
                                           SpriteEffects.None, 0f);



            spriteBatch.Draw(BallSprite, ConvertUnits.ToDisplayUnits(Ball.Position),
                                     null,
                                     Color.White, Ball.Rotation, new Vector2(BallSprite.Width / 2.0f, BallSprite.Height / 2.0f), 1f,
                                     SpriteEffects.None, 0f);

        spriteBatch.End();

        if (Draw_debugView == true)
        {
            // calculate the projection and view adjustments for the debug view
            Matrix projection = Matrix.CreateOrthographicOffCenter(0f, graphics.GraphicsDevice.Viewport.Width / _displayUnitsToSimUnitsRatio,
                                                             graphics.GraphicsDevice.Viewport.Height / _displayUnitsToSimUnitsRatio, 0f, 0f,
                                                             1f);
            Matrix view = Matrix.CreateTranslation(new Vector3((_cameraPosition / _displayUnitsToSimUnitsRatio) - (_screenCenter / _displayUnitsToSimUnitsRatio), 0f)) * Matrix.CreateTranslation(new Vector3((_screenCenter / _displayUnitsToSimUnitsRatio), 0f));
            // draw the debug view
           physicsDebug.RenderDebugData(ref projection, ref view);
        }
        base.Draw(gameTime);
    }
}

Camera class:

public class Camera
{
    private Vector2 BallPosition;
    private Vector2 Viewport;
    private float Scale;


    public Camera(Vector2 ballposition, float scale, Vector2 viewport)
    {
        BallPosition = ballposition;
        Scale = scale;
        Viewport = viewport;
    }

    public void Update(GameTime gameTime, Vector2 ballposition, float scale, Vector2 viewport)
    {
        BallPosition = ballposition;
        Scale = scale;
        Viewport = viewport;
    }

    public Matrix GetMatrix()
    {
        return Matrix.CreateTranslation(new Vector3(-BallPosition.X, -BallPosition.Y, 0))
     * Matrix.CreateScale(Scale, Scale, 1)
     * Matrix.CreateTranslation(Viewport.X / 2, Viewport.Y / 2, 0);
    }
}

UPDATE: I changed my code and tried it without scaling the sprites and Farseer objects. In addition, I changed my code in the Camera class to:

public Matrix GetMatrix()
{
    return Matrix.CreateTranslation(new Vector3(-BallPosition.X, -BallPosition.Y, 0))
 * Matrix.CreateTranslation(Viewport.X / 2, Viewport.Y / 2, 0);
}

But it's still not working. I get the following result if I don't use scaling: http://s7.directupload.net/images/140523/oumh3e7c.jpg

Why is the DebugView drawn on another position than the sprites? I found out that if I change something in this code block, then the DebugView is drawn on another position, but I don't know how to change this code block so that the DebugView is drawn on the correct position(on the same position as their corresponding sprites). I know that it's not working because I use a Camera class, but how should I change this code block so that it works with the Camera class?

if (Draw_debugView == true)
    {
        // calculate the projection and view adjustments for the debug view
        Matrix projection = Matrix.CreateOrthographicOffCenter(0f, graphics.GraphicsDevice.Viewport.Width / _displayUnitsToSimUnitsRatio,
                                                         graphics.GraphicsDevice.Viewport.Height / _displayUnitsToSimUnitsRatio, 0f, 0f,
                                                         1f);
        Matrix view = Matrix.CreateTranslation(new Vector3((_cameraPosition / _displayUnitsToSimUnitsRatio) - (_screenCenter / _displayUnitsToSimUnitsRatio), 0f)) * Matrix.CreateTranslation(new Vector3((_screenCenter / _displayUnitsToSimUnitsRatio), 0f));
        // draw the debug view
       physicsDebug.RenderDebugData(ref projection, ref view);
    }
\$\endgroup\$
  • \$\begingroup\$ Physics should always run at a fixed scale. If you scale the size of physics objects by screen resolution, you'll get different results out of the physics simulation on different devices! Only scale what you render. \$\endgroup\$ – Andrew Russell May 21 '14 at 9:49
  • \$\begingroup\$ I don't understand why the physics objects don't have the same position on screen than their corresponding sprites. Is it possible that the physics objects are drawn on other position because I use two different cameras, one for Farseer DebugView and one for scaling my sprites by screen resolution? I don't know how to solve the position problem. \$\endgroup\$ – Homer_Simpson May 22 '14 at 20:39
  • \$\begingroup\$ I found the answer in this thread. The code from the user Beringela. farseerphysics.codeplex.com/discussions/258898 \$\endgroup\$ – Homer_Simpson May 25 '14 at 19:54
2
\$\begingroup\$

You should abstract the dimensions of your simulations from the dimensions of your rendering. For your physics simulation just use meters and don't worry about how many pixels a meter is. You can provide SpriteBatch.Begin with a matrix as a parameter. That matrix applies a linear transformation to all coordinates fed to the SpriteBatch. You already have a camera class, if you apply the proper scaling there then everything should work.

\$\endgroup\$
  • \$\begingroup\$ Can you give me an example? What do you mean with "dimensions of your simulations"? \$\endgroup\$ – Homer_Simpson May 20 '14 at 22:27
  • \$\begingroup\$ I mean the dimensions objects have in your simulation. For example a character could be 1.8 units high. Units here representing meters. While the character could be drawn on screen using 238 pixels vertically. What I mean is that you should not need to change the scale of your bodies in farseer because the screen size changes. You should only change at what size you draw them. Its also not just scale of the size but also scaling of the position :) \$\endgroup\$ – Roy T. May 21 '14 at 7:15
  • \$\begingroup\$ I tried it like this but it's not working. You can see the WVGA emulator and my new code in this picture: s7.directupload.net/images/140521/iisf8beu.jpg What am I doing wrong? \$\endgroup\$ – Homer_Simpson May 21 '14 at 7:34
  • \$\begingroup\$ You still use ConvertToDisplay units, arent you doing double work? Also I do not see you use the center of the texture as origin. By default SpriteBatch uses the top left of the image as origin while Farseer uses the centre. Try setting origin to {Texture.Width / 2.0f, Texture.Height / 2.0f} \$\endgroup\$ – Roy T. May 21 '14 at 8:18
  • \$\begingroup\$ Yes, I normally use {Texture.Width / 2.0f, Texture.Height / 2.0f}. I changed the origin instead of the scale in spriteBatch. I changed it, but now my sprites are drawn far too big. s1.directupload.net/images/140521/kueoj4wd.jpg \$\endgroup\$ – Homer_Simpson May 21 '14 at 9:35

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.