Table of Contents

Scaling Sprites Based On Screen Size

Demonstrates how to scale sprites using a matrix that is created based on the viewport width.

Overview

Ensuring that the textures you draw to the screen are always scaled the same regardless of screen resolution is key, especially when targeting so many different devices with varying screen sizes. The key to handling this is to generate a scaling matrix based on your "designed" screen size and then applying that to the SpriteBatch calls you make when drawing to the screen. This is crucial for Screen UI / UX for example.

To demonstrate this, we will design a view in a default resolution of 800 x 600 and define a scaling matrix that works on the same ratio.

Note

If you intend to work with multiple different scaling resolutions, you will need to define different scaling matrix's appropriately, if you allow the user to redefine what resolution the game runs in.

Scaling Sprites Based on Screen Size

  1. Set the PreferredBackBufferHeight and PreferredBackBufferWidth properties of GraphicsDeviceManager in the Game constructor to set the default screen size of your game.

    public Game1()
    {
        _graphics = new GraphicsDeviceManager(this)
        {
            PreferredBackBufferHeight = 600,
            PreferredBackBufferWidth = 800
        };
        Content.RootDirectory = "Content";
        IsMouseVisible = true;
    }
    
  2. Set up some variables to hold the debug textures we will draw, and then in your Game.LoadContent method, initialize them:

    private Viewport viewport;
    private Vector2[] scalingSpritePositions;
    private Texture2D squareTexture;
    private Vector2 spriteOrigin;
    private Matrix spriteScaleMatrix;
    protected override void LoadContent()
    {
        // Create a new SpriteBatch, which can be used to draw textures.
        _spriteBatch = new SpriteBatch(GraphicsDevice);
    
        viewport = _graphics.GraphicsDevice.Viewport;
        squareTexture = CreateTexture(GraphicsDevice, 50, 50);
        spriteOrigin = new Vector2(squareTexture.Width / 2, squareTexture.Height / 2);
    
        scalingSpritePositions = new Vector2[4];
        scalingSpritePositions[0] = new Vector2(25, 25);
        scalingSpritePositions[1] = new Vector2(25, viewport.Height - 25);
        scalingSpritePositions[2] = new Vector2(viewport.Width - 25, 25);
        scalingSpritePositions[3] = new Vector2(viewport.Width - 25, viewport.Height - 25);
    
        UpdateScaleMatrix();
    
        base.LoadContent();
    }
    
  3. To help make the debug texture squareTexture easier, we add a little helper function to just generate the texture rather than import it through the Content Pipeline, as follows:

    public static Texture2D CreateTexture(GraphicsDevice device, int width, int height)
    {
        // Initialize a texture
        Texture2D texture = new Texture2D(device, width, height);
    
        // The array holds the color for each pixel in the texture
        Color[] data = new Color[width * height];
        for (int pixel = 0; pixel < data.Length; pixel++)
        {
            data[pixel] = Color.White;
        }
    
        // Set the color
        texture.SetData(data);
        return texture;
    }
    
    Note

    This is a really handy reusable function to create simple textures at runtime.

  4. Next, we create another method to calculate the Scaling Matrix using Matrix.CreateScale based on the current Graphics Device's resolution, using our original 800x600 as a reference scale.

    This matrix should be recreated any time the resolution of the GraphicsDevice changes.

    Note

    Because you are scaling sprites, you should use only the x and y parameters to create the scaling matrix. Scaling the depth of sprites can result in their depth shifting above 1.0. If that happens, they will not render.

    private void UpdateScaleMatrix()
    {
        // Default resolution is 800x600; scale sprites up or down based on
        // current viewport
        float screenScale = _graphics.GraphicsDevice.Viewport.Width / 800f;
        // Create the scale transform for Draw. 
        // Do not scale the sprite depth (Z=1).
        spriteScaleMatrix = Matrix.CreateScale(screenScale, screenScale, 1);
    }
    
  5. To allow you to quickly change the current resolution, in your Game.Update method add the following.

    This example uses the A and B keys to switch between two resolutions.

    protected override void Update(GameTime gameTime)
    {
        if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.Escape))
            Exit();
    
        if (Keyboard.GetState().IsKeyDown(Keys.A))
        {
            _graphics.PreferredBackBufferHeight = 768;
            _graphics.PreferredBackBufferWidth = 1024;
            _graphics.ApplyChanges();
            UpdateScaleMatrix();
        }
        if (Keyboard.GetState().IsKeyDown(Keys.B))
        {
            _graphics.PreferredBackBufferHeight = 600;
            _graphics.PreferredBackBufferWidth = 800;
            _graphics.ApplyChanges();
            UpdateScaleMatrix();
        }
        base.Update(gameTime);
    }
    
  6. In your Game.Draw method, call SpriteBatch.Begin, passing the scaling matrix created in Game.LoadContent.

  7. Finally, draw your scene normally, then call SpriteBatch.End.

    All of the sprites you draw will be scaled according to the matrix.

    protected override void Draw(GameTime gameTime)
    {
        GraphicsDevice.Clear(Color.CornflowerBlue);
    
        // Initialize the batch with the scaling matrix
        _spriteBatch.Begin(transformMatrix: spriteScaleMatrix);
        // Draw a sprite at each corner
        for (int i = 0; i < scalingSpritePositions.Length; i++)
        {
            _spriteBatch.Draw(squareTexture, scalingSpritePositions[i], null, Color.White,
                0f, spriteOrigin, 1f, SpriteEffects.None, 0f);
        }
        _spriteBatch.End();
    
        base.Draw(gameTime);
    }
    

See Also

Concepts

Reference

© 2012 Microsoft Corporation. All rights reserved.