AspectRatioEnforcer

From Unify Community Wiki
Jump to: navigation, search

Author: Eric Haines (Eric5h5)

Contents

Description

Forces the screen to a desired aspect ratio, using letterboxing/pillarboxing as needed. Includes functions that return corrected screen values for Screen.width/height and Input.mousePosition.

Usage

Put this script in a folder that compiles first, such as Standard Assets ( "Plugins" folder always compiles first ), so it can be accessed from Javascript and Boo, and call it AspectUtility. To use it, attach this script to your camera. It can also be attached to some other object, in which case it will attempt to find a camera tagged "Main Camera". For the WantedAspectRatio variable, enter the aspect ratio you want the camera to have. 4:3 is 1.333333, 16:10 is 1.6, and 16:9 is 1.777778, though any ratio can be used. If the aspect ratio is already the same as the desired one, nothing will happen. If the screen is less wide than the desired ratio, letterboxing with black bars will force a widescreen display. If the screen is more wide than the desired ratio, pillarboxing with black bars will force a more square display.

If letterboxing or pillarboxing is used, however, certain functions will no longer be correct. For one thing, Screen.width and Screen.height will still return values for the total screen size, not the new size of the main camera. This can potentially cause bad stuff to happen in your code. To compensate for that, you should use AspectUtility.screenWidth and AspectUtility.screenHeight instead. These are integers just like Screen.width and Screen.height, but are calculated based on the main camera's corrected ratio.

Similarly, Input.mousePosition also returns a value that's not appropriate for letterboxed/pillarboxed screens. Use AspectUtility.mousePosition instead, which will return a Vector3 with correct values. (It's true that only the x and y variables are used, but Input.mousePosition returns a Vector3 rather than Vector2, so the same is used for consistency.)

Note that these variables will not necessarily work if called in a script's Awake function. You should wait until Start or later to access them.

You can call the SetCamera function as necessary, such as when the screen size changes. For example, if you have a web player that uses a 4:3 aspect ratio window, and the user switches to full-screen mode that's 16:9, you'd want to call AspectUtility.SetCamera() to set pillarboxing. You can check the screen's width and height to tell if a screen resolution change has occurred.

Usage with OnGUI

OnGUI code will need some extra work when used with AspectRatioEnforcer. OnGUI code is always drawn on top of everything, and is independent of all cameras, so it doesn't know how to use altered camera rects. The AspectUtility script has a few variables that can help, however.

  • screenRect

If you're using GUILayout code, you can use this in concert with GUILayout.BeginArea and EndArea to define a rect, and put all of your GUILayout code inside. For example, if you normally wrote this:

function OnGUI () {
	GUILayout.Label("Hello");
	GUILayout.Label("there");
}

replace it with this instead:

function OnGUI () {
	GUILayout.BeginArea(AspectUtility.screenRect);
 
	GUILayout.Label("Hello");
	GUILayout.Label("there");
 
	GUILayout.EndArea();
}

If you were already using BeginArea/EndArea, it's fine to nest them, so this method will still work correctly.

  • xOffset and yOffset

If you're not using GUILayout, then you have to add an x and y offset to all rects. You can get these offsets with AspectUtility.xOffset and AspectUtility.yOffset. For example, if you normally wrote this:

function OnGUI () {
	GUI.Label(Rect(50, 50, 100, 30), "Hello");
	GUI.Label(Rect(75, 75, 100, 30), "there");
}

replace it with this instead:

function OnGUI () {
	var x = AspectUtility.xOffset;
	var y = AspectUtility.yOffset;
 
	GUI.Label(Rect(x + 50, y + 50, 100, 30), "Hello");
	GUI.Label(Rect(x + 75, y + 75, 100, 30), "there");
}

So all rects should have the xOffset and yOffset added to the x and y position.

  • guiMousePosition

Note that you should normally use Event.current.mousePosition in OnGUI code rather than Input.mousePosition. With AspectRatioEnforcer, you may want to use AspectUtility.guiMousePosition instead. This is mostly the same as Event.current.mousePosition, but will clamp the coordinates to the letterboxed/pillarboxed screen.

AspectUtility.cs

using UnityEngine;
 
public class AspectUtility : MonoBehaviour {
 
	public float _wantedAspectRatio = 1.3333333f;
	static float wantedAspectRatio;
	static Camera cam;
	static Camera backgroundCam;
 
	void Awake () {
		cam = camera;
		if (!cam) {
			cam = Camera.main;
		}
		if (!cam) {
			Debug.LogError ("No camera available");
			return;
		}
		wantedAspectRatio = _wantedAspectRatio;
		SetCamera();
	}
 
	public static void SetCamera () {
		float currentAspectRatio = (float)Screen.width / Screen.height;
		// If the current aspect ratio is already approximately equal to the desired aspect ratio,
		// use a full-screen Rect (in case it was set to something else previously)
		if ((int)(currentAspectRatio * 100) / 100.0f == (int)(wantedAspectRatio * 100) / 100.0f) {
			cam.rect = new Rect(0.0f, 0.0f, 1.0f, 1.0f);
			if (backgroundCam) {
				Destroy(backgroundCam.gameObject);
			}
			return;
		}
		// Pillarbox
		if (currentAspectRatio > wantedAspectRatio) {
			float inset = 1.0f - wantedAspectRatio/currentAspectRatio;
			cam.rect = new Rect(inset/2, 0.0f, 1.0f-inset, 1.0f);
		}
		// Letterbox
		else {
			float inset = 1.0f - currentAspectRatio/wantedAspectRatio;
			cam.rect = new Rect(0.0f, inset/2, 1.0f, 1.0f-inset);
		}
		if (!backgroundCam) {
			// Make a new camera behind the normal camera which displays black; otherwise the unused space is undefined
			backgroundCam = new GameObject("BackgroundCam", typeof(Camera)).camera;
			backgroundCam.depth = int.MinValue;
			backgroundCam.clearFlags = CameraClearFlags.SolidColor;
			backgroundCam.backgroundColor = Color.black;
			backgroundCam.cullingMask = 0;
		}
	}
 
	public static int screenHeight {
		get {
			return (int)(Screen.height * cam.rect.height);
		}
	}
 
	public static int screenWidth {
		get {
			return (int)(Screen.width * cam.rect.width);
		}
	}
 
	public static int xOffset {
		get {
			return (int)(Screen.width * cam.rect.x);
		}
	}
 
	public static int yOffset {
		get {
			return (int)(Screen.height * cam.rect.y);
		}
	}
 
	public static Rect screenRect {
		get {
			return new Rect(cam.rect.x * Screen.width, cam.rect.y * Screen.height, cam.rect.width * Screen.width, cam.rect.height * Screen.height);
		}
	}
 
	public static Vector3 mousePosition {
		get {
			Vector3 mousePos = Input.mousePosition;
			mousePos.y -= (int)(cam.rect.y * Screen.height);
			mousePos.x -= (int)(cam.rect.x * Screen.width);
			return mousePos;
		}
	}
 
	public static Vector2 guiMousePosition {
		get {
			Vector2 mousePos = Event.current.mousePosition;
			mousePos.y = Mathf.Clamp(mousePos.y, cam.rect.y * Screen.height, cam.rect.y * Screen.height + cam.rect.height * Screen.height);
			mousePos.x = Mathf.Clamp(mousePos.x, cam.rect.x * Screen.width, cam.rect.x * Screen.width + cam.rect.width * Screen.width);
			return mousePos;
		}
	}
}

AspectUtilityEnhanced.cs

On Unity 3.5.2f2 there is an issue on iOS devices in which, even though only landscape orientation is enabled by the application developer, screen.width and screen.height are taken with the device assumed to be in portrait mode for some reason, that is a current aspect ratio of 0.667 detected when this script runs on iPhone for example. Here is a fixed version of the script that works on device and inside the Editor's preview window:

using UnityEngine;
 
public class AspectUtility : MonoBehaviour {
 
    public float _wantedAspectRatio = 1.5f;
	public bool landscapeModeOnly = true;
	static public bool _landscapeModeOnly = true;
    static float wantedAspectRatio;
    static Camera cam;
    static Camera backgroundCam;
 
    void Awake () {
		_landscapeModeOnly = landscapeModeOnly;
        cam = camera;
        if (!cam) {
            cam = Camera.main;
			Debug.Log ("Setting the main camera " + cam.name);
        }
		else {
			Debug.Log ("Setting the main camera " + cam.name);
		}
 
        if (!cam) {
            Debug.LogError ("No camera available");
            return;
        }
        wantedAspectRatio = _wantedAspectRatio;
        SetCamera();
    }
 
    public static void SetCamera () {
		float currentAspectRatio = 0.0f;
		if(Screen.orientation == ScreenOrientation.LandscapeRight ||
			Screen.orientation == ScreenOrientation.LandscapeLeft) {
			Debug.Log ("Landscape detected...");
        	currentAspectRatio = (float)Screen.width / Screen.height;
		}
		else {
			Debug.Log ("Portrait detected...?");
			if(Screen.height  > Screen.width && _landscapeModeOnly) {
				currentAspectRatio = (float)Screen.height / Screen.width;
			}
			else {
				currentAspectRatio = (float)Screen.width / Screen.height;
			}
		}
        // If the current aspect ratio is already approximately equal to the desired aspect ratio,
        // use a full-screen Rect (in case it was set to something else previously)
 
		Debug.Log ("currentAspectRatio = " + currentAspectRatio + ", wantedAspectRatio = " + wantedAspectRatio);
 
        if ((int)(currentAspectRatio * 100) / 100.0f == (int)(wantedAspectRatio * 100) / 100.0f) {
            cam.rect = new Rect(0.0f, 0.0f, 1.0f, 1.0f);
            if (backgroundCam) {
                Destroy(backgroundCam.gameObject);
            }
            return;
        }
 
        // Pillarbox
        if (currentAspectRatio > wantedAspectRatio) {
            float inset = 1.0f - wantedAspectRatio/currentAspectRatio;
            cam.rect = new Rect(inset/2, 0.0f, 1.0f-inset, 1.0f);
        }
        // Letterbox
        else {
            float inset = 1.0f - currentAspectRatio/wantedAspectRatio;
            cam.rect = new Rect(0.0f, inset/2, 1.0f, 1.0f-inset);
        }
        if (!backgroundCam) {
            // Make a new camera behind the normal camera which displays black; otherwise the unused space is undefined
            backgroundCam = new GameObject("BackgroundCam", typeof(Camera)).camera;
            backgroundCam.depth = int.MinValue;
            backgroundCam.clearFlags = CameraClearFlags.SolidColor;
            backgroundCam.backgroundColor = Color.black;
            backgroundCam.cullingMask = 0;
        }
    }
 
    public static int screenHeight {
        get {
            return (int)(Screen.height * cam.rect.height);
        }
    }
 
    public static int screenWidth {
        get {
            return (int)(Screen.width * cam.rect.width);
        }
    }
 
    public static int xOffset {
        get {
            return (int)(Screen.width * cam.rect.x);
        }
    }
 
    public static int yOffset {
        get {
            return (int)(Screen.height * cam.rect.y);
        }
    }
 
    public static Rect screenRect {
        get {
            return new Rect(cam.rect.x * Screen.width, cam.rect.y * Screen.height, cam.rect.width * Screen.width, cam.rect.height * Screen.height);
        }
    }
 
    public static Vector3 mousePosition {
        get {
            Vector3 mousePos = Input.mousePosition;
            mousePos.y -= (int)(cam.rect.y * Screen.height);
            mousePos.x -= (int)(cam.rect.x * Screen.width);
            return mousePos;
        }
    }
 
    public static Vector2 guiMousePosition {
        get {
            Vector2 mousePos = Event.current.mousePosition;
            mousePos.y = Mathf.Clamp(mousePos.y, cam.rect.y * Screen.height, cam.rect.y * Screen.height + cam.rect.height * Screen.height);
            mousePos.x = Mathf.Clamp(mousePos.x, cam.rect.x * Screen.width, cam.rect.x * Screen.width + cam.rect.width * Screen.width);
            return mousePos;
        }
    }
}
Personal tools
Namespaces

Variants
Actions
Navigation
Extras
Toolbox