Stretch a Unity Sprite to fill the screen in a 2D game (GameObject, not UI-canvas)

Would you like to have a background image in a 2D Unity game that fills the available screen? Most examples online show how to use the Canvas rect to do this. But what if your sprite is not in a canvas? And what if you want to fit an image while keeping the aspect ratio? This article shows you how to do this.

Here is a screencast of the MonoBehaviour you are going to create:

animate scale

In this example you see a Unity project (2D) with an image placed at the origin. The image is 1024x1024 pixels. In the game view I’ve set the resolution to 800x400 and the camera preview shows how your game would look like on such a screen

viewport

Switch the resolution to 1080x1920, and notice a slight difference in the camera preview but unity still tries to fit everything nicely even if the resolution was increased a lot.

ratio set

1080

This all changes when we set the resolution to 1920x1080 (landscape):

1920

The square image is still shown at the origin and the blue background is exposed.

In order to do some resize calculations, I’ll start by checking the available screen size.

Debug.Log("Screen Width: " + Screen.width + ", Screen Height: " + Screen.height);

Output:

Screen Width: 1920, Screen Height: 1080

If you’ve worked with pixel based GUI’s before, it would be obvious to set the sprite size to the screen size to stretch the image so it covers the whole screen. But although your image has a pixel dimension, the sprite that holds that image does not. It only has world space coordinates.

Pixels v.s. World Space

If you are new to Unity, you might wonder what world space is. To answer this, let’s have a look at the transform properties of our sprite:

transform

Here you can set the position, rotation and scale. To fit the image in the available screen, you are going to scale the image up. But somehow, you need to find the right scale factor. Here’s what happens if you set the Scale X factor to 2:

scale

The image is stretched but the aspect ratio is broken. This is easily fixed by setting the Scale Y to 2 as well. But as you can see a 2x factor is not exactly what you want because the image is larger than the viewport.

scale

Find a scale factor

You need to find a scale factor that fits the image to the screen. We will create two stretch modes:

Scale horizontally

To scale the sprite horizontally you have to divide the screen width by the sprite width:

scalex = screenwidth / spritewidth

We already know we cannot use pixel values for this. Instead we use world space coordinates. To get the world space coordinates for the “screen”, we can use the camera. After all, what you see on the screen is what the camera sees.

Here we convert the right/top pixel position to the right/top position in World Space:

var topRightCorner = Camera.main.ScreenToWorldPoint(new Vector3(Screen.width, Screen.height, Camera.main.transform.position.z));
Debug.Log(topRightCorner);

Output:

(8.9, 5.0, -20.0)

This value represents the top right corder of the screen.

scale

For a 2D game you can ignore the z=-20.0 value but you might wonder where it comes from. It is the camera size times the camera z position. Please note that changing the camera size will impact the values calculated by the ScreenToWorldPoint function.

Calculating World Space Width and Height

The width and height of the world space is easily calculated by doubling the right/top world space position:

var worldSpaceWidth = topRightCorner.x * 2;
var worldSpaceHeight = topRightCorner.y * 2;
Debug.Log("Word Space Width: " + worldSpaceWidth + ", World Space Height:" + worldSpaceHeight);

Output:

Word Space Width: 17.77778, World Space Height:10

Calculate sprite width/height in World Space

var spriteSize = gameObject.GetComponent<SpriteRenderer>().bounds.size;
Debug.Log(spriteSize);

Output:

(10.2, 10.2, 0.2)

Now we have all the values we need to calculate the scale factor.

Calculate Scale Factor X

As you saw before, calculating the scale factor x is done by dividing the screen width by the sprite width like this:

var scaleFactorX = worldSpaceWidth / spriteSize.x;
Debug.Log(scaleFactorX);

Output:

1.736111

That means we need to scale the sprite horizontally by 1.726111 to stretch it horizontally to fit the screen:

gameObject.transform.localScale = new Vector3(scaleFactorX, 1, 1);

Which fits the image perfect horizontally:

scale

Switch the resolution to portrait again and notice how it stretches horizontally correct as well:

scale

As you can see, the image is a bit larger than the camera viewport vertically. That is easily fixed by stretching vertically as well. Here is the complete code to stretch an image:

var topRightCorner = Camera.main.ScreenToWorldPoint(new Vector3(Screen.width, Screen.height, Camera.main.transform.position.z));
var worldSpaceWidth = topRightCorner.x * 2;
var worldSpaceHeight = topRightCorner.y * 2;

var spriteSize = gameObject.GetComponent<SpriteRenderer>().bounds.size;
var scaleFactorX = worldSpaceWidth / spriteSize.x;
var scaleFactorY = worldSpaceHeight / spriteSize.y;

gameObject.transform.localScale = new Vector3(scaleFactorX, scaleFactorY, 1);

The result:

scale

The image fits nicely in the viewport but the aspect ratio is not 1:1 anymore. If you need to keep the aspect ration, use the UniformToFill mode we will discuss next.

Keep aspect ratio (UniformToFill)

If you have worked with XAML, you probably are familiar with the Image Stretch Mode called UniformToFill. This will try to fit the image as well as possible. To do this, you need to calculate the scale factors for x and y and scale both axes to the largest scale factor:

Here is the full code for that:

var topRightCorner = Camera.main.ScreenToWorldPoint(new Vector3(Screen.width, Screen.height, Camera.main.transform.position.z));
var worldSpaceWidth = topRightCorner.x * 2;
var worldSpaceHeight = topRightCorner.y * 2;

var spriteSize = gameObject.GetComponent<SpriteRenderer>().bounds.size;

var scaleFactorX = worldSpaceWidth / spriteSize.x;
var scaleFactorY = worldSpaceHeight / spriteSize.y;

if (scaleFactorX > scaleFactorY)
{
    scaleFactorY = scaleFactorX;
}
else
{
    scaleFactorX = scaleFactorY;
}

gameObject.transform.localScale = new Vector3(scaleFactorX, scaleFactorY, 1);

Which results in portrait mode in:

portrait scale

And in landscape mode in:

lanscape scale

As you see the image is cut off but the aspect ratio is maintained.

Wrap it up, full code

Here is the complete MonoBehaviour code that you can attach to a sprite.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class SpriteStretch : MonoBehaviour
{
    public bool KeepAspectRatio;

    void Start()
    {
        var topRightCorner = Camera.main.ScreenToWorldPoint(new Vector3(Screen.width, Screen.height, Camera.main.transform.position.z));
        var worldSpaceWidth = topRightCorner.x * 2;
        var worldSpaceHeight = topRightCorner.y * 2;

        var spriteSize = gameObject.GetComponent<SpriteRenderer>().bounds.size;

        var scaleFactorX = worldSpaceWidth / spriteSize.x;
        var scaleFactorY = worldSpaceHeight / spriteSize.y;

        if (KeepAspectRatio)
        {
            if (scaleFactorX > scaleFactorY)
            {
                scaleFactorY = scaleFactorX;
            }
            else
            {
                scaleFactorX = scaleFactorY;
            }
        }

        gameObject.transform.localScale = new Vector3(scaleFactorX, scaleFactorY, 1);
    }
}

animate scale

Conclusion

I hope this article helps you with scaling your sprite GameObjects that are outside of the UI Canvas. Fire up Unity and try it yourself!

Written by Loek van den Ouweland on October 09, 2019. Questions regarding this artice? You can send them to the address below.