🎮

【Unity】これ以上iOSのSafeAreaで消耗したくない人のため『Unity-SafeAreaCanvas』

に公開

はじめに

この記事はUnity Advent Calendar 201817日目の記事の代理投稿です!16日目は@namazuchinさんのUnityでTwilioを利用して固定電話に架電するでした。

<blockquote class="twitter-tweet" data-cards="hidden" data-lang="ja"><p lang="ja" dir="ltr">iPhone X/XSのSafeAreaに対応したUnityアセットをリリースしました🎉

1. iOSでの実行時にCanvasサイズ調整機能
2, Unityエディタ上でのプレビュー機能
3. GameビューにiPhone X/XS用サイズの追加機能

ぜひ使ってみてください!
nkjzm/Unity-SafeAreaCanvas<a href="https://t.co/If9zYqHrqG">https://t.co/If9zYqHrqG</a> <a href="https://twitter.com/hashtag/Unity?src=hash&amp;ref_src=twsrc^tfw">#Unity</a> <a href="https://twitter.com/hashtag/SafeArea?src=hash&amp;ref_src=twsrc^tfw">#SafeArea</a> <a href="https://t.co/9EFPkf1qGf">pic.twitter.com/9EFPkf1qGf</a></p>— Nakaji Kohki / リリカちゃん💜💊 (@nkjzm) <a href="https://twitter.com/nkjzm/status/1064703125678739461?ref_src=twsrc^tfw">2018年11月20日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

先日、iPhone X/XSのSafeAreaに対応したOSSをリリースしました。
今回は、使い方の紹介と、実装の解説をしていこうと思います。

リポジトリ: https://github.com/nkjzm/Unity-SafeAreaCanvas

使い方

UnityのSafeArea対応に関する記事は、実機での反映のみに言及したものが多いですが、実際に開発する際には、SafeAreaが適用された場合の見え方をプレビューしながら進めたいと感じました。そのため、プレビューと実機対応の両方に対応したのが今回のアセットになります。

sample (1) (1).gif

  1. ReleasesからSafeAreaCanvas.unitypackageをダウンロードしてください。
  2. プロジェクトにSafeAreaCanvas.unitypackageをインポートしてください。
  3. シーンにSafeAreaCanvas/Prefabs/SafeAreaCanvas.prefabをドラッグしてください。

あとはSafeAreaCanvas.prefabの子のSafeArea以下にUIなどを作成・配置していけば良いです。
その状態でGameビューのサイズを変更すると、自動的にSafeAreaの余白が更新されます。
また、実機でも同じように動作するはずです。

実装

シンプルな実装なのですが、簡単に紹介していきます。

iOS実機実行時のCanvasサイズ調整機能

SetCanvasBounds.cs
using UnityEngine;

namespace nkjzm.SafeAreaCanvas
{
    [ExecuteInEditMode()]
    public class SetCanvasBounds : MonoBehaviour
    {
        public RectTransform panel;
        Rect lastSafeArea = new Rect(0, 0, 0, 0);

        void ApplySafeArea(Rect area)
        {
            panel.anchoredPosition = Vector2.zero;
            panel.sizeDelta = Vector2.zero;

            var anchorMin = area.position;
            var anchorMax = area.position + area.size;
            anchorMin.x /= Screen.width;
            anchorMin.y /= Screen.height;
            anchorMax.x /= Screen.width;
            anchorMax.y /= Screen.height;
            panel.anchorMin = anchorMin;
            panel.anchorMax = anchorMax;

            lastSafeArea = area;
        }

        void Update()
        {
            if (panel == null) { return; }

            Rect safeArea = Screen.safeArea;
#if UNITY_EDITOR
            if (Screen.width == 1125 && Screen.height == 2436)
            {
                safeArea.y = 102;
                safeArea.height = 2202;
            }
            if (Screen.width == 2436 && Screen.height == 1125)
            {
                safeArea.x = 132;
                safeArea.y = 63;
                safeArea.height = 1062;
                safeArea.width = 2172;
            }
#endif
            if (safeArea != lastSafeArea)
            {
                ApplySafeArea(safeArea);
            }
        }
    }
}

https://github.com/nkjzm/Unity-SafeAreaCanvas/blob/master/Assets/SafeAreaCanvas/Scripts/SetCanvasBounds.cs

panelのサイズがSafeAreaのサイズと一致するようになっています。
Unity2017.3くらいから、Rect safeArea = Screen.safeAreaでsafeAreaのサイズが取得できるようになりました。その値をApplySafeArea関数でpanelに反映させています。

void ApplySafeArea(Rect area)
{
    panel.anchoredPosition = Vector2.zero;
    panel.sizeDelta = Vector2.zero;
    var anchorMin = area.position;
    var anchorMax = area.position + area.size;
    anchorMin.x /= Screen.width;
    anchorMin.y /= Screen.height;
    anchorMax.x /= Screen.width;
    anchorMax.y /= Screen.height;
    panel.anchorMin = anchorMin;
    panel.anchorMax = anchorMax;
    lastSafeArea = area;
}

UIのAnchorサイズをSafeAreaに合わせて更新しています。AnchorはVector2型で各値が0-1の範囲で表現されています。RectTransformの左下が(0,0)で右上が(1,1)です(下記画像参照)。Areaの座標を代入した後にScreen.widthScreen.heightで割ることで、0-1に正規化しているイメージです。

<img width="664" alt="スクリーンショット 2018-12-24 4.39.38.png" src="https://qiita-image-store.s3.amazonaws.com/0/55365/83f2617c-ffc3-b79b-47f3-025e67bdaaac.png">

ちなみにScreen.safeAreaRect型として返ってきます。矩形を表現するクラスで、Vector2型のPositionxyなどで直接アクセス出来たりする便利なクラスなのですが、Rect本来の座標系は左上が原点である点に注意してください。

Unityエディタ上でのプレビュー機能

上記コードでiPhone X/XSを判定する部分がこちらです(めちゃめちゃハードコーディングですみません)。

#if UNITY_EDITOR
if (Screen.width == 1125 && Screen.height == 2436)
{
    safeArea.y = 102;
    safeArea.height = 2202;
}
if (Screen.width == 2436 && Screen.height == 1125)
{
    safeArea.x = 132;
    safeArea.y = 63;
    safeArea.height = 1062;
    safeArea.width = 2172;
}
#endif

エディタ上ではScreen.safeAreaが値を返してくれないため、自前で値を入れています。大した処理じゃないのでUpdate()でやっています(ApplySafeArea()は繰り返し呼ばれません)

逆に言うと、エディタプレビュー機能はこんなに短いコードのみで対応しています。簡単ですね。

GameビューにiPhone X/XS用サイズの追加機能

インポート時、GameビューのサイズにiPhone X/XSの設定を追加する機能を入れています。Build TagetをiOSにしている場合はすでに含まれているので不要なのですが、別プラットフォームの設定で作業をする場合などに便利です。

<img width="354" alt="スクリーンショット 2018-12-24 4.49.55.png" src="https://qiita-image-store.s3.amazonaws.com/0/55365/77d2683c-fcbf-22d5-a760-c36d72260de0.png">

実装にはunity-GameViewSizeHelperというアセットを利用させていただいています。「ScriptからGameViewSizeを作成、また設定するヘルパークラス」とあり、まさにやりたいことでした。

GameViewSizeAdder.cs
using UnityEditor;
using UnityEngine;

namespace nkjzm.SafeAreaCanvas
{
    public class GameViewSizeAdder
    {
        [InitializeOnLoadMethod]
        static void Init()
        {
            var wide = new GameViewSizeHelper.GameViewSize
            {
                type = GameViewSizeHelper.GameViewSizeType.FixedResolution,
                width = 2436,
                height = 1125,
                baseText = "iPhone X/XS Landscape"
            };
            var tall = new GameViewSizeHelper.GameViewSize
            {
                type = GameViewSizeHelper.GameViewSizeType.FixedResolution,
                width = 1125,
                height = 2436,
                baseText = "iPhone X/XS Portrait"
            };
            GameViewSizeHelper.AddCustomSize(GameViewSizeGroupType.Standalone, wide);
            GameViewSizeHelper.AddCustomSize(GameViewSizeGroupType.Standalone, tall);
            GameViewSizeHelper.AddCustomSize(GameViewSizeGroupType.Android, wide);
            GameViewSizeHelper.AddCustomSize(GameViewSizeGroupType.Android, tall);
        }
    }
}

https://github.com/nkjzm/Unity-SafeAreaCanvas/blob/master/Assets/SafeAreaCanvas/Scripts/Editor/GameViewSizeAdder.cs

[InitializeOnLoadMethod]というアトリビュートを使って自動的にサイズ追加をしています。コンパイルが終わった後などにエディタ上で呼び出されるものです。複数回AddCustomSizeを呼び出していますが、GameViewSizeHelperでは同一の設定は追加されないようになっているみたいなので、大丈夫でした。ありがたいですね!

iOSと同じプロジェクトで使う可能性が高そうなStandAloneとAndroidの対応を入れています。

最後に

『Unity-SafeAreaCanvas』は、私がSafeArea対応で消耗した時に作成したアセットです。ぜひ多くの方に使っていただきたいですし、気になった点のIssueやPull Requestもお待ちしております!

Unity Advent Calendar 201818日目の記事は@kaiware007さんのThetaのストリーミング映像をリアルタイムにリフレクションさせてみたです。

Discussion