toc

content

Unity3D Fixed Aspect Ratio

Using a fixed aspect ratio in Unity3D sounds like a relatively simple thing to do.

I saw this post which explains how to do it, but it doesn't quite cover everything you might want to do with a fixed aspect ratio. I have included a slightly revised version of the linked post below:

First, create a script with the following code in the Update() method.

// desired aspect ratio
float targetAspect = 16.0f / 9.0f;

// determine the game window's current aspect ratio
float currentAspect = (float)Screen.width / (float)Screen.height;

// current viewport height should be scaled by this amount
float scaleHeight = currentAspect / targetAspect;

// obtain the camera component so we can modify its viewport
var camera = GetComponent<Camera>();
Rect rect = camera.rect;

// if scaled height is less than current height, add letterbox
if (scaleHeight < 1.0f)
{
	rect.x = 0;
	rect.y = (1.0f - scaleHeight) / 2.0f;
	rect.width = 1.0f;
	rect.height = scaleHeight;
}
// otherwise, add pillarbox
else
{
	float scaleWidth = 1.0f / scaleHeight;

	rect.x = (1.0f - scaleWidth) / 2.0f;
	rect.y = 0;
	rect.width = scaleWidth;
	rect.height = 1.0f;
}
camera.rect = rect;

Then, do the following:

  • Add this script to each camera you want to restrict the aspect ratio of
  • Create a new camera
  • Set the new camera's depth value to some number lower than the main camera
  • Set the new camera's Clear Flags to "Solid Color" and the Background to some color
  • Set the new camera's Culling Mask to "Nothing"

While the code presented in this post will be written specifically for Unity3D, you will find that in general this code isn't specific to Unity3D and can be easily adapted for use in other engines or frameworks.

Arbitrary screen coordinates

The fixed aspect ratio code works great, but the entire formula isn't there because it assumes that the camera you are letterboxing is always using the viewport rect (0, 0, 1, 1). That is, it assumes that you are using that camera to render the entire screen. What if you wanted to render a camera only on a part of the screen, such as the viewport rect (.3, .2, .4, .4)? The above code would not work for you. Instead, you have to do this:

var viewport = new Rect(.2f, .1f, .8f, .8f);
float targetAspect = 16.0f / 9.0f;

float currentAspect = (float)Screen.width / (float)Screen.height;
float scaleHeight = currentAspect / targetAspect;

var camera = GetComponent<Camera>();
var rect = new Rect();

if (scaleHeight < 1.0f)
{
	rect.x = viewport.x;
	rect.y = (viewport.y * scaleHeight) + (1.0f - scaleHeight) / 2.0f;
	rect.width = viewport.width;
	rect.height = viewport.height * scaleHeight;
}
else
{
	float scaleWidth = 1.0f / scaleHeight;

	rect.x = (viewport.x * scaleWidth) + (1.0f - scaleWidth) / 2.0f;
	rect.y = viewport.y;
	rect.width = viewport.width * scaleWidth;
	rect.height = viewport.height;
}
camera.rect = rect;

Transforming coordinates

If you are trying to use the mouse position on the screen you will find that the screen coordinates of the mouse are in the wrong position as the top right of the screen is now not actually in the true top right of the screen. To transform screen coordinates, you'd have to do essentially the same calculation:

float targetAspect = 16.0f / 9.0f;

float currentAspect = (float)Screen.width / (float)Screen.height;
float scaleHeight = currentAspect / targetAspect;

float x = Mouse.x;
float y = Mouse.y;
if (scaleHeight < 1.0f)
{
	y = (y * scaleHeight) + (1.0f - scaleHeight) / 2.0f;
}
else
{
	float scaleWidth = 1.0f / scaleHeight;
	x = (x * scaleWidth) + (1.0f - scaleWidth) / 2.0f;
}

Taking screenshots

Taking a screenshot using Application.TakeScreenshot() will give you an image that includes letterboxing on the sides. This might not be the intended behaviour you want, and you can get around this by only taking screenshot of a part of the screen:

IEnumerator TakeScreenshot()
{
	float targetAspect = 16.0f / 9.0f;

	float currentAspect = (float)Screen.width / (float)Screen.height;
	float scaleHeight = currentAspect / targetAspect;
	var rect = new Rect();

	if (scaleHeight < 1.0f)
	{
		rect.x = 0;
		rect.y = ((1.0f - scaleHeight) / 2.0f) * Screen.height;
		rect.width = Screen.width;
		rect.height = Screen.height * scaleHeight;
	}
	else
	{
		float scaleWidth = 1.0f / scaleHeight;

		rect.x = ((1.0f - scaleWidth) / 2.0f) * Screen.width;
		rect.y = 0;
		rect.width = Screen.width * scaleWidth;
		rect.height = Screen.height;
	}

	var tex = new Texture2D((int)rect.width, (int)rect.height, TextureFormat.RGB24, false);

	// Read the pixels from the screen into a texture
	yield return new WaitForEndOfFrame();
	tex.ReadPixels(rect, 0, 0);
	tex.Apply();

	// Encode texture into PNG
	var bytes = tex.EncodeToPNG();
	Destroy(tex); // Need to delete this to prevent memory leaks

	// Save the image on the disk
	System.IO.File.WriteAllBytes(Application.dataPath + "/screenshot.png", bytes);
}

meta

tags: unity

created: published: migrated:

commit: 8e465e8a