Resolution Independent 2D Rendering in XNA 4

When I first started making my game, I hard coded the screen resolution to 800 x 600 for simplicity and built my menus and levels with the assumption that the screen was exactly 800 pixels wide and 600 pixels high. This was a mistake because all of my UI positions, tile positions, and entity placements were based on absolute positions and did not look proper when the resolution changed. If the user wanted to use a screen size that was not 4:3 and super tiny, the game looked completely wrong and was unplayable because the level layout was all over the place.

To solve this, I used David Amador’s Resolution class to introduce a means of rendering my scenes independent of the actual resolution of the window. I maintained an “internal” or “virtual” resolution of 1280 x 720 (Xbox standard) to be used for position of all my entities and UI elements. This virtual resolution never changes. Users can then adjust the “actual” resolution of the game which determines the real window size. We use a scaling matrix to transform the virtual resolution to fill the actual resolution with additional letterboxes or pillarboxes depending on the virtual and actual resolutions. I suggest you read the linked article from David Amador to understand the class and how to use it.

Changes for XNA 4

David’s article is what really counts when it comes making this work. However, it was written pre-XNA 4 and requires a minor change. Calls to SpriteBatch.Begin() require more parameters in the overload that allows for a transform matrix. The current method of using David’s code to scale your virtual resolution up to the actual resolution is:

spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend, null, null, null, null, Resolution.getTransformationMatrix());

Obviously, your needs will dictate the exact parameters, but this is the overload you need to use at the very basic level.

Transforming Mouse Coordinates

Another problem you’ll run into is when trying to use mouse coordinates to interact with the elements of your game. The transformation matrix is applied only to the sprite rendering and does not affect the state of the mouse cursor. For example, using a straight up MouseState.GetState() will return coordinates in terms of the actual resolution and not the virtual resolution. Since your game is working in terms of the virtual resolution, this behavior is wrong!

Luckily, one of the commentors in David’s article explained a way to transform mouse coordinates into the virtual screen space. Here is a simple static abstraction that I created to get mouse position in terms of virtual resolution.

First, add two static members to the Resolution class.

static private int virtualViewportX;
static private int virtualViewportY;

Second, assign values in the ResetViewport() method.

viewport.X = (_Device.PreferredBackBufferWidth / 2) - (width / 2);
viewport.Y = (_Device.PreferredBackBufferHeight / 2) - (height / 2);
virtualViewportX = viewport.X;
virtualViewportY = viewport.Y;

Third, expose the values in public properties.

public static int VirtualViewportX { get { return virtualViewportX; } }
public static int VirtualViewportY { get { return virtualViewportY; } }

And finally, create a static MouseHelper class (or whatever you want) that has a way of getting the mouse position and translating it into virtual coordinates for you.

public static class MouseHelper
   /// <summary>Translates the actual mouse position obtained from Mouse.GetState() into the virtual mouse position after a scaling matrix is applied to the viewport
   /// </summary>
   public static Point CurrentMousePosition
         MouseState mouse = Mouse.GetState();
         Vector2 mousePosition = new Vector2(mouse.X, mouse.Y);
         Vector2 virtualViewport = new Vector2(Resolution.VirtualViewportX, Resolution.VirtualViewportY);
         mousePosition = Vector2.Transform(mousePosition - virtualViewport, Matrix.Invert(Resolution.getTransformationMatrix()));
         Point virtualMousePosition = new Point((int)mousePosition.X, (int)mousePosition.Y);
         return virtualMousePosition;

Just remember to use the proper SpriteBatch.Draw() parameters and transform the mouse coordinates whenever necessary. Using the above approaches should allow you to continue assuming a set resolution for all in game activities while allowing users to choose their desired resolution. Win-win!

Add a comment

Your email address will not be published. Required fields are marked *


  1. Jackie

    This code works great for drawing simple background images, but I cannot get this to work inside the “XNA Game State Management” sample, where each screen is capable of drawing additional textures besides background images. When I use the Resolution code inside the “Gameplay” screen to draw a simple rectangle texture in the center of the screen(with position coordinates set at center of screen and origin set halfway through texture), it’s never centered at any resolution.

    1. jskiles1

      Hi Jackie. It’s hard to diagnose the issue without seeing how you’ve implemented it. You say it’s not centered, so where is it? Is it way off? Is it close? Can you provide a screenshot?

      1. Jackie

        OK, all of my new year’s events are out of the way, so I’m back. My textures were created in Gimp using 1280×720 resolution. When I implement the resolution code where SetVirtualResolution(1280,720) and SetResolution(1280,720, false), everything works fine so far.
        Inside game constructor Screenshot
        Inside gameplayscreen Screenshot
        Screenshot result Screenshot ).

        When I change SetResolution() to SetResolution(800,600,false), the white rectangle resizes to fit an 800×600 screen, but it’s not centered in the middle of screen anymore. It’s as if instead of drawing the rectangle from the center, it draws the rectangle from the bottom right.
        Result Screenshot ).

        The only thing changed were the parameters in SetResolution().

      2. jskiles1

        Hi Jackie. How are you establishing the ’tile’ object’s position? What’s the ’tile.Draw’ method look like? From the code you’ve provided, I can’t really see anything wrong with it.

        1. Jackie

          Gameplayscreen.cs contains a tile object. Inside gameplayscreen’s constructor tile gets instantiated, taking in a Vector2 at the center of the screen Vector2(ScreenWidth/2, ScreenHeigth/2)) and I set tile’s position to that. Tile’s origin is Vector2(texture.Width/2, texture.Height/2).

          It doesn’t seem to make a difference whether I place the Resolution draw code inside gameplayscreen.cs’s draw() or tile.cs’s Draw(). All i can think of now is reassigning tile’s position and origin right before draw or mulitplying position by some number to recenter it.


      3. jskiles1

        How is `ScreenWidth` and `ScreenHeight` determined? They should be based on the virtual resolution and not the actual rendered resolution. For example, I have a title screen image that is centered by using the following properties:


        VirtualViewport is defined as: new Viewport(0, 0, _VWidth, _VHeight);

        _VWidth and _VHeight are private members of the Resolution class that get adjusted when you call SetVirtualResolution.

        You should always position your elements based on the VirtualViewport (which never changes). The user can adjust the rendered resolution (as you’ve done by changing to 800×600), but as long as you render based on the constant virtual resolution, your positioning should be correct. Please confirm if you are using ScreenWidth and ScreenHeight based on the virtual viewport.

        1. Jackie

          That was it! After I set the rendered resolution, i set variables screen_width and screen_height to equal the rendered resolution, not the virtual resolution. So, did you just create some public properties in Resolution.cs to return the virtual resolutions?

          1. jskiles1

            I’m really glad that handled it. Yes, my Resolution class exposes the VirtualWidth and VirtualHeight as public properties. Feel free to contact me for more help.

  2. Tom

    I will be amazed if you respond after all these years but I am trying to implement this in Monogame and all is well… until I enter fullscreen. Then the co-ordinates of MouseHelper.CurrentMousePosition are ~10 pixels off of the real value.

    Any thoughts on why?