Tuesday, September 28, 2010

Controlling Aspect Ratio in Unity

Games made with Unity allow users to pick a screen resolution on startup through Unity's Display Resolution Dialog. While it's possible to disable this feature and force a game to use a particular resolution, it's generally not a good idea to deny users the ability to set the game's resolution to whatever they think is best. Such flexibility comes at a price, however, and one of the costs is the loss of control over the game window's aspect ratio.

Differences in aspect ratio aren't necessarily a problem, but I think it's generally a good idea to keep things as consistent as possible regardless of the system on which a game is running. For the camera, such consistency ensures that what you see during development and testing is also what players see once your game is released: Objects visible from a particular vantage point will be visible on all systems, and those that aren't visible will likewise remain out of view. A consistent view across systems means the context is the same for all players, with no player seeing more of the environment or the objects within it than intended by the game's designer.

While Unity's game editor allows developers to choose between a number of aspect ratios while developing a game, there's currently no way to specify a particular aspect ratio when a game is run outside of Unity's development environment. There is, however, a way to do this with a bit of extra effort. What follows, then, is a tutorial on how to guarantee the use a particular aspect ratio regardless of the game's resolution.

The first step is to create a camera script to adjust the camera's viewport according to the game window's current size and the desired aspect ratio. The script is created by choosing Assets -> Create and the desired script type from the editor's menu, after which the code to set the aspect ratio is added to the script's Start() function so that it's executed during the camera's initialization step. Here's some C# code that does just that:

// Use this for initialization
void Start ()
{
// set the desired aspect ratio (the values in this example are
// hard-coded for 16:9, but you could make them into public
// variables instead so you can set them at design time)
float targetaspect = 16.0f / 9.0f;

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

// current viewport height should be scaled by this amount
float scaleheight = windowaspect / targetaspect;

// obtain camera component so we can modify its viewport
Camera camera = GetComponent<Camera>();

// if scaled height is less than current height, add letterbox
if (scaleheight < 1.0f)
{
Rect rect = camera.rect;

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

camera.rect = rect;
}
else // add pillarbox
{
float scalewidth = 1.0f / scaleheight;

Rect rect = camera.rect;

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

camera.rect = rect;
}
}

Once you've saved your new camera script, add the script to the camera by dragging the script from the Project window onto the camera's listing in the Hierarchy window. Now when you run your game the camera's viewport will be scaled and positioned to match the desired aspect ratio*.

Now the next step is to add a second camera to render the "black bar" region of the screen. While you can choose to render whatever the second camera is pointed at, in most cases you'll want to set the camera to render only a flat color such as black. To do this:
  1. Create the camera by choosing GameObject -> Create Other -> Camera from the editor's menu.
  2. Set the camera's depth value to -2 so it's rendered underneath the main camera (whose depth value defaults to -1).
  3. To set the black bar region to a solid color, set the camera's Clear Flags to "Solid Color", set the Culling Mask to "Nothing", and finally the Background to the desired color.
That's all you need to do. Now your game will run with the desired aspect ratio regardless of the user's choice of resolution.



* When running your game from within Unity's editor, be sure to have the Game window open and visible in the editor when you run the game. There's currently a bug in Unity 3.0 (and possibly in earlier versions as well) where the window resolution reported to the script does not match the actual resolution of the window inside the editor if the window isn't visible at the time the play button is pressed, leading to a viewport with the wrong size.

23 comments:

Hakimo said...

Hi, this code works like a charm but I seem to have hit a wall. For some GUI textures, I used a script that has mouse button function to make the guitexture a button. After using the camera ratio code, the collision area for the mouse button is off for some reason. Is there a way to fix it? Thanks.
- Hakimo

Lajos said...

Works perfectly, thanks a lot for this! ;-)

retn said...

Thanks for this great code snippet. Works perfectly.

Anonymous said...

Is that work for android devices...

Anonymous said...

Thanks man, really helpful. It works great. On android too.

Anonymous said...

Awesome! Thank you very much!

Bal√°zs Srancsik said...

Booyaka!!! This works like a charm!!! I am soooo grateful!!!

Semihero .com said...

I have a question related to this script. It works fine and sets the aspect ratio, but if you put a screenshot line to the very end of the script, it makes a whole screen screenshot from the camera (so in my case it will be a 16:9 aspect ratio picture) instead of taking only the camera's viewable area (which in my case is A4 size 297:210). Is there any solution to force the screenshot to A4 size too?

Joss said...

Good script and it's very easily to use it.

Really thank you.

Anonymous said...

Hi Adrian,
this is genius!! Helpt me a lot :D
Thanks for your work.

Amon said...

This is perfect for my needs. Pretty much the best resolution, aspect ratio helper code that I have found.

Cheers!

Danny said...

Very Nice Thanks a lot ! Works for Android devices

DarkMecha said...

Absolutely perfect for what I need, thanks so much!!!

Anonymous said...

Merci.

Anonymous said...

you are a hero

ALI ADNAN said...

u r great works awesome

Sebastian Matheus Bevilaqua said...

Perfect'm more problems in guitextures can help me?

Sorry my english.

Douglas Sabo said...

Hey there.

An easier way that I'm using is to just always have a rectangular frame around my viewable area. The I gradually increase, decrease the rect while detecting the border with raycast. Once I get a hit on the border layer, I stop the rect scan. Takes literally a fraction of a second. This is fast too, just with a bit of faults. I mean, the real, REAL way to do it is to have individually coded resolutions lol

No one wants to do that, haha

GiantCatBallZ! said...

Thank God! Thank you very much!

Anonymous said...

godzilla hole on the black superman

Mohit Parihar said...

hi,
i will try above instruction, but its show nothing,
pls any one provide me any demo source asap.
thanks in advance.

Mohit Parihar said...

hi,
i will try above instruction, but its show nothing,
pls any one provide me any demo source asap.
thanks in advance.

Jason King said...

For people that are looking for additional aspect related support, I created a AssetStore extension that allows for easier aspect switching called AspectSwitcher(http://forum.unity3d.com/threads/released-aspectswitcher-easy-adjustments-to-ui-based-on-device-aspect.263004/). It provides a system for allowing you to easily specify different properties for different aspects. There are generally two method that most people use to switch aspects. One is to provide different game objects for each aspect. The other is to create custom code that modifies the properties of a single game object based on the current aspect. That generally requires a lot of custom coding. My extension attempts to alleviate a lot of that pain.