using UnityEngine;
using System.Collections;
using System.Collections.Generic;
/// <summary>
/// Maintain PixelPerfect formatting as DPI increases.
/// This is achieved by scaling UI in whole steps: 1X, 2X, 3x, etc.
/// Atlas swapping is also managed, so higher resolution textures are used when appropriate.
/// </summary>
/// <example>
/// For example, a layout made for iPhone 3 (320x480@160dpi)
/// will look identical (but sharper) on iPhone 4 (640x960@320dpi)
/// because we scale the UI by 2x and use textures with 2x resolution.
/// </example>
public class PixelPerfectDPIScaler : MonoBehaviour {
/// <summary>
/// When DPI nears a multiple of this value, scale UIRoot appropriately.
/// </summary>
public int baseDPI = 160;
/// <summary>
/// Scale to use in Editor
/// </summary>
[Range(1,4)]
public int editorScale = 1;
public UIRoot[] uiRoots;
/// <summary>
/// At runtime, this atlas will be made to reference its appropriate scaled variant.
/// Scaled variants must:
/// 1) Live in a "Resources" folder.
/// 2) Follow the "@1x" naming convention. A reference atlas named "wood" will look for "wood@1x", "wood@2x", etc.
/// 3) The sprites must be named identically in each scaled variant.
/// </summary>
/// <remarks>
/// If scale is 3, but only "@1x" and "@2x" atlases exist, "@2x" atlas will be used (stretched to 3x size).
/// </remarks>
public UIAtlas[] referenceAtlases;
/// <summary>
/// Runtime changes on prefabs are not undone when playback ends.
/// Therefore, when running in editor, we cache original values to be restored later.
/// </summary>
private Dictionary<UIAtlas,UIAtlas> atlasReferences;
public int GetScale() {
if (Application.isEditor) {
return editorScale;
}
else {
// round to nearest multiple of baseDPI
return Mathf.Max(1, Mathf.RoundToInt(Screen.dpi / (float)baseDPI));
}
}
public void Awake() {
if (Application.isEditor) {
CacheAtlasReferences();
}
UpdateReferenceAtlases();
UpdateUIRoots();
}
public void OnApplicationQuit() {
if (Application.isEditor) {
RestoreAtlasReferences();
}
}
public void Update() {
// called each frame to handle device rotation and window resizing
UpdateUIRoots();
}
/// <summary>
/// Point each reference atlas at its appropriately scaled variant.
/// </summary>
private void UpdateReferenceAtlases() {
int scale = GetScale();
foreach (UIAtlas referenceAtlasI in referenceAtlases) {
string scaledAtlasName = "";
for (int i = scale; i >= 1; i--) { // runs at least once
scaledAtlasName = string.Format("{0}@{1}x", referenceAtlasI.name, scale);
if (referenceAtlasI.replacement != null && referenceAtlasI.replacement.name == scaledAtlasName)
break;
UIAtlas scaledAtlas = Resources.Load<UIAtlas>(scaledAtlasName);
if (scaledAtlas) {
referenceAtlasI.replacement = scaledAtlas;
break;
}
}
if (referenceAtlasI.replacement == null) {
Debug.LogError(string.Format("Cannot find atlas '{0}' in any 'Resources' folder", scaledAtlasName), this);
}
}
}
/// <summary>
/// Scale UIRoots.
/// </summary>
/// <remarks>
/// We "double" the rendering size by telling a UIRoot that the screen is half its actual size
/// </remarks>
private void UpdateUIRoots() {
int scale = GetScale();
// What if resolution is not divisible by scale? GOOD QUESTION
if ((Screen.width % scale != 0) || (Screen.height % scale != 0)) {
string msg = string.Format("Scaling is not pixel-perfect for {0}x{1} display @{2}x", Screen.width, Screen.height, scale);
Debug.LogWarning(msg, this);
}
int manualHeight = Mathf.RoundToInt(Screen.height / scale);
foreach (UIRoot rootI in uiRoots) {
if (rootI.scalingStyle != UIRoot.Scaling.FixedSize) {
rootI.scalingStyle = UIRoot.Scaling.FixedSize;
}
if (rootI.manualHeight != manualHeight) {
rootI.manualHeight = manualHeight;
}
}
}
private void CacheAtlasReferences() {
atlasReferences
= new Dictionary
<UIAtlas,UIAtlas
>(); foreach (UIAtlas referenceAtlasI in referenceAtlases) {
atlasReferences[referenceAtlasI] = referenceAtlasI.replacement;
}
}
private void RestoreAtlasReferences() {
foreach (KeyValuePair<UIAtlas,UIAtlas> pair in atlasReferences) {
pair.Key.replacement = pair.Value;
}
}
}