【Unity】セーフエリアをエディタでシミュレートする

Unityでセーフエリアをエディタ上でシミュレートする方法です。

Unity2018.3.1

作るもの

Unityにはセーフエリアを簡単に取得できるプロパティが用意されています。

docs.unity3d.com

ただしこれは(当たり前ですが)セーフエリアが画面サイズと異なるサイズでしか効果がありません。
しかしこれではエディタでUIを並べているときに正しくセーフエリア対応ができているかわかりません。
そこでこんな感じで解像度の比率が一定以上 / 一定以下になったときにSafeAreaを設定する仕組みを作ります。

f:id:halya_11:20190205142911g:plain:w400

Screenクラスの代替となるものを作る

画面サイズは通常Screenクラスを使って取りますが、これには

  • ランタイムでしか使えない
  • セーフエリアがエディタでシミュレートできない

という問題があります。
なのでまずはこのScreenクラスを代替するクラスを作ります。

public static class ScreenUtility
{

#if !UNITY_EDITOR
    /// <summary>
    /// エディタ上でSafeAreaを再現する際の最大アスペクト比の値
    /// </summary>
    private const float EDITOR_SAFE_AREA_RATIO_THRESHOLD = 2;
    private static float _lastAspectRatio;
#endif
    private static Rect _safeArea;
    
    /// <summary>
    /// 画面全体の横幅
    /// </summary>
    public static int FullWidth { 
        get {
#if !UNITY_EDITOR
            if (Application.isPlaying) {
                return Screen.width;
            }
            else {
                // エディタかつ再生中じゃない場合はUnityStatsからGameViewのサイズを取得
                string[] screenres = UnityStats.screenRes.Split('x');
                return int.Parse(screenres[0]);
            }
#else
            return Screen.width;
#endif
        }
    }

    /// <summary>
    /// 画面全体の高さ
    /// </summary>
    public static int FullHeight { 
        get {
#if !UNITY_EDITOR
            if (Application.isPlaying) {
                return Screen.height;
            }
            else {
                // エディタかつ再生中じゃない場合はUnityStatsからGameViewのサイズを取得
                string[] screenres = UnityStats.screenRes.Split('x');
                return int.Parse(screenres[1]);
            }
#else
            return Screen.height;
#endif
        }
    }

    /// <summary>
    /// 画面全体の範囲
    /// </summary>
    public static Rect FullArea { get { return new Rect(Vector2.zero, new Vector2(FullWidth, FullHeight)); } }

    /// <summary>
    /// 画面サイズの比の値
    /// </summary>
    public static float FullAspectRatio { get{ return (float)FullWidth / FullHeight; } }

    /// <summary>
    /// セーフエリアの横幅
    /// </summary>
    public static int SafeWidth { get { return (int)SafeArea.width; } }

    /// <summary>
    /// セーフエリアの高さ
    /// </summary>
    public static int SafeHeight { get { return (int)SafeArea.height; } }

    /// <summary>
    /// セーフエリアの範囲
    /// </summary>
    public static Rect SafeArea { 
        get{
#if !UNITY_EDITOR
            if (FullAspectRatio != _lastAspectRatio) {
                if (FullAspectRatio > EDITOR_SAFE_AREA_RATIO_THRESHOLD) {
                    // 画面が横長すぎたら補正
                    var width = FullHeight * EDITOR_SAFE_AREA_RATIO_THRESHOLD;
                    var diffWidth = FullWidth - width;
                    _safeArea = new Rect(diffWidth / 2, 0, width, FullHeight);
                }
                else if (FullAspectRatio < 1.0f / EDITOR_SAFE_AREA_RATIO_THRESHOLD) {
                    // 画面が縦長すぎたら補正
                    var height = FullWidth * EDITOR_SAFE_AREA_RATIO_THRESHOLD;
                    var diffHeight = FullHeight - height;
                    _safeArea = new Rect(0, diffHeight / 2, FullWidth, height);
                }
                else {
                    _safeArea = FullArea;
                }
                _lastAspectRatio = FullAspectRatio;
            }
#else
            _safeArea = Screen.safeArea;
#endif
            return _safeArea;
        }
    }
    
    /// <summary>
    /// セーフエリアのサイズの比の値
    /// </summary>
    public static float SafeAspectRatio { get{ return (float)SafeWidth / SafeHeight; } }
}

エディタから実行している場合、SafeAreaプロパティでシミュレート用のSafeAreaを作っています。

また、非ランタイムでGameビューのサイズを取るためにはUnityStatsクラスを使う必要があります。
これについては下記のサイトを参考にさせていただきました。

d.hatena.ne.jp

セーフエリアをRectTransformに適用する

あとはセーフエリアをRectTransformに適用するだけです。

[ExecuteInEditMode]
[RequireComponent(typeof(RectTransform))]
public class ScreenSizeFitter : MonoBehaviour
{
    private enum AreaType
    {
        FullArea,
        SafeArea
    }
    
    [SerializeField]
    private AreaType _areaType = AreaType.FullArea;

    private Rect _lastArea = new Rect();
    private RectTransform _rectTransform;
    public RectTransform RectTransform { get { return (_rectTransform = GetComponent<RectTransform>()); } }

    private void ApplyArea()
    {
        switch (_areaType) {
        case AreaType.FullArea:
            ApplyFullArea();
            break;
        case AreaType.SafeArea:
            ApplySafeArea();
            break;
        default:
            break;
        }
    }

    private void ApplyArea(Rect area)
    {
        if (_lastArea == area) {
            return;
        }

        RectTransform.anchoredPosition = Vector2.zero;
        RectTransform.sizeDelta = Vector2.zero;
        var anchorMin = area.position;
        var anchorMax = area.position + area.size;
        anchorMin.x /= ScreenUtility.FullWidth;
        anchorMin.y /= ScreenUtility.FullHeight ;
        anchorMax.x /= ScreenUtility.FullWidth;
        anchorMax.y /= ScreenUtility.FullHeight ;
        RectTransform.anchorMin = anchorMin;
        RectTransform.anchorMax = anchorMax;

        _lastArea = area;
    }

    private void ApplyFullArea()
    {
        ApplyArea(ScreenUtility.FullArea);
    }

    private void ApplySafeArea()
    {
        ApplyArea(ScreenUtility.SafeArea);
    }

    private void Start()
    {
        ApplyArea();
    }

    private void Update()
    {
#if UNITY_EDITOR
        ApplyArea();
#endif
    }
}

こちらは特に特別なことはやっていません。

結果

f:id:halya_11:20190205142911g:plain:w400

参考

docs.unity3d.com

d.hatena.ne.jp