4
votes

I'm working on an XNA game and I am using ViewPort.Project and ViewPort.Unproject to translate to and from world coordinates. Currently I use these for each object I draw with SpriteBatch. What I would like to do is calculate a Matrix that I can send to SpriteBatch.Begin to do the screen-space transformation for me.

Here are the functions I currently use to translate to and from screenspace:

        Vector2 ToWorldCoordinates(Vector2 pixels)
    {
        Vector3 worldPosition = graphics.GraphicsDevice.Viewport.Unproject(new Vector3(pixels, 0),
                Projection, View, Matrix.Identity);
        return new Vector2(worldPosition.X, worldPosition.Y);
    }

    Vector2 ToScreenCoordinates(Vector2 worldCoords)
    {
        var screenPositon = graphics.GraphicsDevice.Viewport.Project(new Vector3(worldCoords, 0),
                Projection, View, Matrix.Identity);
        return new Vector2(screenPositon.X, screenPositon.Y);
    }

View is set to Matrix.Identity, and Projection is set like so:

Projection = Matrix.CreateOrthographic(40 * graphics.GraphicsDevice.Viewport.AspectRatio, 40, 0, 1);

And here is how I currently draw things:

            spriteBatch.Begin();
        foreach (var thing in thingsToDraw)
        {
            spriteBatch.Draw(thing.Texture, ToScreenCoordinates(thing.PositionInWorldCoordinates), thing.Color);
            spriteBatch.End();
        }
        spriteBatch.End();

This is what I would like to do instead (using XNA 4.0 version of SpriteBatch.Begin())

            // how do I calculate this matrix?
        Matrix myTransformationMatrix = GetMyTransformationMatrix();

        spriteBatch.Begin(SpriteSortMode.Immediate, null, null, null, null, null,
            myTransformationMatrix);

        foreach (var thing in thingsToDraw)
        {
            // note: no longer converting each object's position to screen coordinates
            spriteBatch.Draw(thing.Texture, thing.PositionInWorldCoordinates, thing.Color);
            spriteBatch.End();
        }
        spriteBatch.End();
1

1 Answers

12
votes

I've written about SpriteBatch and the various "spaces" (world, projection, client, etc) here, here and here. Those answers are probably worth reading.

SpriteBatch assumes that your World space is the same thing as Client space - which is however many pixels tall and wide the viewport is, origin top-left, Y+ is down.

It looks like (based on your use of CreateOrthographic) you want your World space to appear as 40 units tall, on screen, and however many units will fit widthways. You also want the World origin at the centre of the screen and Y+ is up.

So you have to stick another matrix in between to convert your World space to Client space. I believe the correct matrix is (in psudocode): scale(40/viewport.Height) * scale(1, -1) * translate(viewport.Width/2, viewport.Height/2). Scale, flip and translate to go from World to Client.

You must also remember that sprites assume that Y+ is down. So you have to pass a (1, -1) scale into SpriteBatch.Draw, otherwise they will be drawn inverted (and, due to backface culling, be invisible).