Here is a screencast of the MonoBehaviour you are going to create:
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
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.
This all changes when we set the resolution to 1920x1080
(landscape):
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.
SpriteStretch
and attach it to the sprite GameObjectDebug.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.
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:
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:
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.
You need to find a scale factor that fits the image to the screen. We will create two stretch modes:
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.
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 theScreenToWorldPoint
function.
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
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.
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:
Switch the resolution to portrait again and notice how it stretches horizontally correct as well:
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:
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.
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:
And in landscape mode in:
As you see the image is cut off but the aspect ratio is maintained.
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);
}
}
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!