feature image

2026年4月2日 | ブログ記事

Unityのアスペクト比問題と戦いたい

この記事は新歓ブログリレー2026 22日目の記事 (だったはずのもの) です

(著者の遅刻により28日目の早朝に投稿されています すみません...)

はじめに

皆さんこんにちは。24Bの @zoi_dayo と申します。現在traPで代表をしたり、最近はゲームや動画を作ったり競技プログラミングをしたりしています。

今回はUnityの話です。

画面解像度とUIの話

Unityでゲームを作ると、どうしても画面解像度の問題に悩まされることになります。PCゲームとしては (フルスクリーン指定をして) 1920x1080を前提にUIを設計することが多いのかなと思うのですが、Let's Noteなど一部のコンピュータでは画面のアスペクト比が16:9になりません。また、1920x1080 (フルHD) を前提にUIを組んでいた場合、4Kなどのディスプレイでは要素が思った場所・サイズで配置されない可能性があります。

もちろん、UnityのUI機能は強力なので、上手く活用して (現実的な範囲で) どんな画面であってもうまく表示されるように、また見えてはいけない範囲が見えないように調整することが利用です。しかし、ハッカソンなど開発期間にきびしい制約があるイベントの場合、ここの対応がおろそかにされ、展示時に完全でない形で表示されてしまう可能性があります。

この記事は、「画面を1920x1080で設計する」「他の解像度であってもそのUIを崩さず表示させる」という方針で、これをどうにかしたいというものです。

2Dゲーム想定です。

1. アスペクト比対策

まず、アスペクト比にかかわらず適切に表示されるようにしたいです。
アスペクト比が16:9でない場合は、上下、または左右に黒帯を入れ、16:9の画面がちょうど画面中央に収まるように表示することにします。

この場合、最初に思いつくのは、Canvas ScalerのUI Scale ModeをScale With Screen Sizeに設定し、Screen Match ModeをExpandなどに設定することではないでしょうか。

なお、この場合の背景色はCameraのEnvironment→Background TypeをSolid Colorにして、Blackgroundで色を指定することで決めることが出来ます。基本的に真っ黒でいいと思います。

これは基本的に想定通りの動きをするように見えるのですが、画面外にはみ出たオブジェクトも描画されてしまうという問題があります。

これも塗りつぶしたいですね。ので、別の方法を考えます。

2. RenderTextureの利用

ここで、「画面全体を1920x1080の画像として一度レンダリングし、それを再度画像として表示する」アプローチを取ってみます。

まず、CanvasのRender Modeを「Screen Space - Camera」に設定します。

次に、RenderTextureを作成します。適当な場所に作成しましょう。

そして、画素数をフルHDにします。カラーフォーマットなど、他の設定はお好みに応じて変更してください。

今作ったRenderTextureをMain CameraのOutputにセットしてあげると、ゲーム画面に何も描画されなくなるはずです。

では、このRenderTextureを表示させましょう。

シーン上に新しくCanvasとCameraを用意します。今回は「RenderCanvas」「RenderCam」としました。この時点では何も表示されないはずです。

RenderCanvasには、「1. アスペクト比対策」の操作をしておきます。
Canvas内にRawImageを追加し、サイズを調整して先程作ったRenderTextureを描画させます。

これで、画面外を非表示にしつつ、アスペクト比を固定し、画面に移りきるように拡大縮小させることができました。

3. クリックイベントの対応

ここで一つ問題があります。ここにボタンなどを設置するとき、RenderCanvas側でどこにカーソルがあたっているかではなく、「非表示のCanvas上で」どこにカーソルがあるかによってイベントが処理されてしまいます。すなわち、表示先画面が1920x1080でない場合、「ボタンが表示されている位置をタップしても反応しない」ことになります。

この対処のため、CanvasのGraphics Raycasterを乗っ取ります。

以下のようなスクリプトを作成し、

using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.InputSystem;
using UnityEngine.UI;

public class RenderGraphicsRaycaster : GraphicRaycaster
{
    [SerializeField] private Camera renderCamera;
    [SerializeField] private RectTransform rawImageRect;
    [SerializeField] private Vector2 rtRes = new(1920, 1080);

    public override void Raycast(PointerEventData eventData, List<RaycastResult> resultAppend)
    {
        if (!RectTransformUtility.ScreenPointToLocalPointInRectangle(
                rawImageRect,
                eventData.position,
                renderCamera,
                out var localPoint)
           )
            return;
        var r = rawImageRect.rect;
        var nx = Mathf.InverseLerp(r.xMin, r.xMax, localPoint.x);
        var ny = Mathf.InverseLerp(r.yMin, r.yMax, localPoint.y);
        var originalPos = eventData.position;
        eventData.position = new Vector2(nx * rtRes.x, ny * rtRes.y);
        base.Raycast(eventData, resultAppend);
        eventData.position = originalPos;
    }
}

(RenderCanvasではなく、非表示になっている方の) Canvasにアタッチします。もともとついているGraphicsRaycasterは消しましょう。

そして、[SerializeField] としているRenderCamera, RawImageRectに対し、それぞれ「RenderCam」「RenderTextureを貼ったRawImage」を指定します。

RenderCanvasのRender Modeを「Screen Space - Camera」にすると、画面上に配置したボタン、また IPointerEnterHandler で購読したイベントなどが適切に (1920x1080の画面のように) 動作するはずです。

4. マウス位置の変換

もしかすると、C#スクリプト内で Input.mousePosition などとしてマウス位置を取得している部分があるかもしれません。これは画面上のマウスの位置を取得してしまうので、変換対応が必要です。

先ほど作成した RenderGraphicsRaycaster にプロパティを生やすと簡潔でしょう。次のように実装しておきます。

using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.InputSystem;
using UnityEngine.UI;

public class RenderGraphicsRaycaster : GraphicRaycaster
{
    [SerializeField] private Camera renderCamera;
    [SerializeField] private RectTransform rawImageRect;
    [SerializeField] private Vector2 rtRes = new(1920, 1080);

    public override void Raycast(PointerEventData eventData, List<RaycastResult> resultAppend)
    {
        // 略
    }

    public Vector2 MousePosition
    {
        get
        {
            if (rawImageRect == null) return Vector2.zero;

            var screenPos = Mouse.current.position.ReadValue();

            if (!RectTransformUtility.ScreenPointToLocalPointInRectangle(rawImageRect, screenPos, renderCamera,
                    out var localPoint)) return Vector2.zero;
            var r = rawImageRect.rect;
            var nx = Mathf.InverseLerp(r.xMin, r.xMax, localPoint.x);
            var ny = Mathf.InverseLerp(r.yMin, r.yMax, localPoint.y);

            return new Vector2(nx * rtRes.x, ny * rtRes.y);
        }
    }
}

あとは、Input.mousePosition のような記法を利用している部分をすべてこれに書き換えることで、ゲーム内ではすべての処理を「1920x1080の画面」だとみなして実装することが出来ます。

おわり

ここまで読んでいただきありがとうございました。画面サイズによってはボケや文字の潰れが発生しそうだし、RenderTextureへレンダリング→画面にレンダリングという2度手間となることでパフォーマンスに悪影響がある気もしますが、途中までできている作品に対しガッと対応させる方法としてはそれなりに使えるんじゃないかと思います。

明日 (今日? 4/2) は @haruka1012 さんの記事が投稿されるようです。楽しみ〜

zoi_dayo icon
この記事を書いた人
zoi_dayo

24B 競プロやWebをちょびっとやったりしていた

この記事をシェア

このエントリーをはてなブックマークに追加
共有

関連する記事

2023年11月21日
School Breakin' Tag -新感覚おにごっこ-
s9 icon s9
2023年9月3日
タイピング&アクション『TypeTheCode』作りました
wal icon wal
2023年4月17日
ポケモンを飼いたい夢を叶える
tqk icon tqk
2025年9月18日
泥タブに夢と希望を見出した男の物語 【Lenovo Yoga Tab Plus】
mutv625 icon mutv625
2025年2月4日
冬ハッカソン2024 22班 「Queen Bee」
YHz_ikiri icon YHz_ikiri
2025年3月5日
2024年度冬ハッカソン12班「Hero Girl」
gurukun41 icon gurukun41
記事一覧 タグ一覧 Google アナリティクスについて 特定商取引法に基づく表記