【Unity】全スマートフォン、全解像度で完全同じ画面を再現する

Unity
スポンサーリンク

Unityで開発するアプリの解像度が変わる時、カメラやキャンバス内の配置が若干変わってしまい思った通りに配置されないことがあったので今回その対策をいれてみました。

見た目の挙動

画面のサイズを変更するとこのように変化します。
キチッとゲームの画面比率やUIの位置・サイズが変わらず維持できていますね。

この対応を入れておかないと、幅が長いスマホなどで本来カメラ外の見えてはいけないモノが見えてしまったり、端末毎に操作感が変わってしまうので調整に大変苦労します。

もちろん一部画面外が黒く塗りつぶされていて画面を活かしきれてない感はあるのですが、そもそもスマホによっては見えてはいけないもの(例えばイベントが起きる前でキャラが消えてるとか)が見えてるのは論外です。

アスペクト比が違う画面などでも調整不要なこの機能を入れて楽をしてしまいましょう!

参考サイト

こちらのサイトを参考しました。

Unity2Dで画面のアスペクト比を固定にしたい【Unity】 - Qiita
2Dゲームを作っている時に色んなデバイスで画面サイズを背景に合わせたい時があったのでメモ。 背景にサイズを合わせたい アスペクト比は固定にしたい 背景からはみ出す部分は黒くしたい デバイスのスクリーンサイズから縦に合わせるか横に合...
【Unity】画面アスペクト比を画面の高さに自動的に合わせて調整する
Unityで画面アスペクト比を固定する方法は割と紹介されていますが、基本はアスペクト比を固定しつつも縦側の解像…

ただこれだけだとCanvasScalarなどを使ってもいつも同じUI位置やサイズが維持できないためプラスアルファその辺りも対応しています。

 

実装内容

おおまかな流れとしてはスクリプト2種類用意して
各オブジェクトに設定していく流れとなります。

カメラ(Camera)

カメラに対して、下記のスクリプトをアタッチします。
詳細な説明は省きますが、おおまかに説明すると

変更されたアスペクトの縦と横どちらが大きいか判別して
カメラの描画領域を調整しています。

これをゲームで使っているカメラに設定追加してください。

 

スクリプト内容

refCameraに使用するカメラコンポーネント、widthに基本解像度の幅、heightに基本解像度の高さ、pixelPerUnitについてもCanvasなどと統一した値を利用してください。

CameraStableAspect.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[ExecuteInEditMode()]
[RequireComponent(typeof(Camera))]
public class CameraStableAspect : MonoBehaviour
{
    [SerializeField]
    Camera refCamera;

    [SerializeField]
    int width = 1920;

    [SerializeField]
    int height = 1080;

    [SerializeField]
    float pixelPerUnit = 100f;


    int m_width = -1;
    int m_height = -1;

    void Awake()
    {
        if( refCamera == null ){
            refCamera = GetComponent();
        }
        UpdateCamera();
    }

    private void Update()
    {
        UpdateCameraWithCheck();
    }

    void UpdateCameraWithCheck()
    {
        if(m_width == Screen.width && m_height == Screen.height){
            return;
        }
        UpdateCamera();
    }

    void UpdateCamera()
    {
        float screen_w = (float)Screen.width;
        float screen_h = (float)Screen.height;
        float target_w = (float)width;
        float target_h = (float)height;

        //アスペクト比
        float aspect =  screen_w / screen_h;        
        float targetAcpect = target_w / target_h;
        float orthographicSize = (target_h / 2f / pixelPerUnit);

        //縦に長い
        if (aspect < targetAcpect)
        {
            float bgScale_w = target_w / screen_w;
            float camHeight = target_h / (screen_h * bgScale_w);
            refCamera.rect = new Rect( 0f, (1f-camHeight)*0.5f, 1f, camHeight);
        }
        // 横に長い
        else
        {
            // カメラのorthographicSizeを横の長さに合わせて設定しなおす
            float bgScale = aspect / targetAcpect;
            orthographicSize *= bgScale;

            float bgScale_h = target_h / screen_h;
            float camWidth = target_w / (screen_w * bgScale_h);
            refCamera.rect = new Rect( (1f-camWidth)*0.5f, 0f, camWidth, 1f);
        }

        refCamera.orthographicSize = orthographicSize;

        m_width = Screen.width;
        m_height = Screen.height;
    }
}

 

キャンバス(Canvas)直下のRectTransformオブジェクト

一般的な構造としてはCanvasの下にUIを配置していくと思うのですが、そこに全てのUIを子としてぶら下げる親を事前に追加しておきます。

少し手間なのですが、CanvasはScreenサイズ(解像度)に強制的に連動してしまうためスクリプトで制御できるRectTransformの親を一つ追加する必要があります。

追加方法としては「Hierarchyウィンドウ→右クリック→UI→Panel」で追加し、不要なImageなどを削除、位置をセンタリング」でご利用ください。

その追加したオブジェクトに下記スクリプトを追加します。

 

スクリプト内容

処理的には「CanvasScaler」の「Scale With Screen Size」という設定と同じです。

referenceResolutionにUIを構築する解像度、matchWidthOrHeightはCanvasScalerの処理を引用し、同じ処理にしています。

RectScalerWithViewport.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[ExecuteInEditMode()]
public class RectScalerWithViewport : MonoBehaviour
{
    [SerializeField]
    RectTransform refRect = null;

    [SerializeField]
    Vector2 referenceResolution = new Vector2(1920, 1080);

    [Range(0, 1)]
    [SerializeField] float matchWidthOrHeight = 0;

    float m_width = -1;
    float m_height = -1;

    private const float kLogBase = 2;

    private void Awake()
    {
        if(refRect == null){
            refRect = GetComponent();
        }
        UpdateRect();
    }

    private void Update()
    {
        UpdateRectWithCheck();
    }

    private void OnValidate()
    {
        UpdateRect();
    }

    void UpdateRectWithCheck()
    {
        Camera cam = Camera.main;
        float width = cam.rect.width * Screen.width;
        float height = cam.rect.height * Screen.height;
        if(m_width == width && m_height == height){
            return;
        }
        UpdateRect();
    }

    void UpdateRect()
    {
        Camera cam = Camera.main;
        float width = cam.rect.width * Screen.width;
        float height = cam.rect.height * Screen.height;
        m_width = width;
        m_height = height;

        // canvas scalerから引用
        float logWidth = Mathf.Log(width / referenceResolution.x, kLogBase);
        float logHeight = Mathf.Log(height / referenceResolution.y, kLogBase);
        float logWeightedAverage = Mathf.Lerp(logWidth, logHeight, matchWidthOrHeight);
        float scale = Mathf.Pow(kLogBase, logWeightedAverage);

        refRect.localScale = new Vector3(scale, scale, scale);

        // スケールで縮まるので領域だけ広げる
        float revScale = 1f / scale;
        refRect.sizeDelta = new Vector2(m_width * revScale, m_height * revScale);
   }
}

 

開発中ゲームのPR

こちらの機能を利用したゲームです。

予約サイトにも登録していますのでご興味のある方はぜひ!

iOS :yoyaku-top10.jp/u/a/MzAwMTA
Android :yoyaku-top10.jp/u/a/MzAwMDE

最後に

いかがでしたでしょうか。
この機能で一応は縦横比を維持したまま、ゲームの画面がスマートフォン端末ごとに変わってしまうことが避けられました。

ただ画面引き伸ばされていない場所は見えては行けないモノが見えるのでキャンバスの縁辺りは何か塗りつぶす、パネルなどを置いて隠す必要があります。

必要であれば別途対応を入れてみてください。
※簡単にいうと画面上下左右の端にピボットを置きます。そして塗りつぶし用イメージの位置を連動させて対応。

 

なにはともわれ端末画面ごとにレイアウトの配置が変わる負担が減ったので今後この機能はずっと使うことになりそうです。

解像度対応面倒くさい!!

という方はぜひご活用いただければ幸いです。

ではここまでこの記事をお読み頂きありがとうございました。

 

コメント

  1. うなぎ より:

    神スクリプトをありがとうございます!
    とっっっても助かりました!

  2. Tomo より:

    とても助かります!
    ありがとうございました!
    RectScalerWithViewport.csをアタッチする場所がよく分からなかったのですが、キャンバス内に空のゲームオブジェクトを作成してそこにアタッチすればいいんですか?

    • Tadashi Tadashi より:

      助けになったようで良かったです!
      下記回答となります。

      はい、RectScalerWithViewport.csはゲーム上でお使いのCanvasを親として
      その一つ下の階層にゲームオブジェクト(記事で説明しているPanel)を追加し、そこにRectScalerWithViewportスクリプトをアタッチします。

      その追加したゲームオブジェクトはRectTransformと呼ばれるCanvasの子どもになるためのオブジェクトとして作成されますので、そのRectTransformプロパティはCanvasのサイズに連動するように中心を原点とした完全フィット設定にしてください(上下左右のサイズは完全連動、原点を中心にする設定です)。

      そしてPanelで追加すると不要なImageコンポーネントが付いてきますのでこれを削除してください。

      最後にこれまでCanvas直下にあったUIなどのゲームオブジェクトをその追加したPanel(改名推奨、例:ScalerPanelなど)に全てドラッグ&ドロップで移動させます。

      少々初歩的な部分などを端折っている点もありますので
      気になることなどありましたらお気軽にご連絡ください~!

      追伸:ゲーム上でCanvasが複数ある場合、毎回対応入れないといけないのでPrefabしておくと便利です。

タイトルとURLをコピーしました