Unityで2Dスマホゲームを開発したいと考えているのですが、端末によって画面の大きさや比率が異なると思います。どの端末でも問題なくプレイできるように、スクリプトを用いてカメラの表示領域を動的に調整する方法を勉強しました。
Unity初心者なので解釈が間違っている部分などありましたらご教示いただけると幸いです。
#準備
Canvasのインスペクターは次のように設定しました。
- Canvas
- レンダーモード:スクリーンスペース - カメラ
- UIをスクリーンに描画
- 常にレンダーカメラの正面に描画される
- レンダーモード:スクリーンスペース - カメラ
- Canvas Scaler
- UIスケールモード:画面サイズに拡大
- 参照解像度に合わせてUIのスケールがサイズ調整される
- スクリーンマッチモード:Expand
- 縦横の解像度に合わせてスケーリングを行う
- ユニット毎の参照ピクセル
- Unityの距離の単位を「1ユニット=1ピクセル」に設定
- UIスケールモード:画面サイズに拡大
縦も横もすべて表示する
上の画像は私の大好きな「#コンパス」というスマホゲームから引用しています。
iPhoneの画面をiPadの縦のサイズに合わせるようにしてスケーリングすると、iPadの画面はカメラの表示域が左右に広いのが分かります。
これは以下のコードで実装できます。
[SerializeField] private Camera camera_; // 対象のカメラ
[SerializeField] private Vector2 aspectVec_; // 参照解像度
[SerializeField] private float pixelPerUnit_; // 画像のPixelPerUnit
private float currentAspect_ = 0.0f; // 現在のアスペクト比
private void SetCameraViewArea()
{
// カメラサイズの調整
camera_.orthographicSize = aspectVec_.y / pixelPerUnit_ / 2;
// ビューポートの調整
float baseAspect = aspectVec_.y / aspectVec_.x; // 基準のアスペクト比
if (baseAspect <= currentAspect_)
{
// 画面が縦に長い場合、画面の長さにアスペクト比を追従させる
float bgScale = currentAspect_ / baseAspect;
// カメラのサイズを縦の長さに合わせて設定しなおす
camera_.orthographicSize *= bgScale;
}
// viewportRectを設定(範囲内全てを表示)
camera_.rect = new Rect(0.0f, 0.0f, 1.0f, 1.0f);
}
-
orthographicSize
カメラのサイズを調整しています。投影方法を「平行投影」に設定した際のカメラの表示ボリュームを定義したもので、画面の縦半分にUnit(画素数 / Pixels Per Unit)がいくつ入るのかを求めその値を入れることで適切な表示領域が設定されます。 -
rect
カメラの描画範囲の矩形を調整しています。0 (左/下)から 1 (右/上) 範囲の値。
ここではカメラを画面いっぱいに表示したいのでrectの第1・第2引数は0、第3・第4引数は1に設定しています。
私がいつも利用している目覚ましアプリから引用しています。
先程の画面とは違い、iPadの画面はiPhoneの画面比率を壊さないよう余白部分は表示していない(黒塗りしている)ことが分かります。
これは以下のコードで実装できます。
[SerializeField] private Camera camera_; // 対象のカメラ
[SerializeField] private Vector2 aspectVec_; // 参照解像度
[SerializeField] private float pixelPerUnit_; // 画像のPixelPerUnit
private float currentAspect_ = 0.0f; // 現在のアスペクト比
private void SetCameraViewArea()
{
// カメラサイズの調整
camera_.orthographicSize = aspectVec_.y / pixelPerUnit_ / 2;
// ビューポートの調整
float baseAspect = aspectVec_.y / aspectVec_.x; // 基準のアスペクト比
if (baseAspect <= currentAspect_)
{
// 画面が縦に長い場合
float bgScale = aspectVec_.x / Screen.width;
// viewportRectの縦幅
float tmpHeight = aspectVec_.y / (Screen.height * bgScale);
// viewportRectを設定
camera_.rect =
new Rect(0.0f, (1.0f - tmpHeight) / 2, 1.0f, tmpHeight);
}
else
{
// 画面が横に長い場合
float bgScale = aspectVec_.y / Screen.height;
// viewportRectの横幅
float tmpWidth = aspectVec_.x / (Screen.width * bgScale);
// viewportRectを設定
camera_.rect =
new Rect((1.0f - tmpWidth) / 2, 0.0f, tmpWidth, 1.0f);
}
}
rectの部分で長い方の画面は表示しないように調整しているのが分かります。
#採用したコード
適当に拾った画像を背景に使っているので見づらいかもしれませんが、私が現在作りかけている開発のGIF画像です。縦をすべて表示し、横の余白部分は表示しないように実装しました。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
// カメラの表示領域を調整する
[ExecuteAlways]
public class AspectKeeper : MonoBehaviour
{
[SerializeField] private Camera camera_; // 対象のカメラ
[SerializeField] private Vector2 aspectVec_; // 参照解像度
[SerializeField] private float pixelPerUnit_; // 画像のPixelPerUnit
private float currentAspect_ = 0.0f; // 現在のアスペクト比
void Update()
{
// カメラの表示領域の調整
SetCameraViewArea();
}
// カメラの表示領域の調整
private void SetCameraViewArea()
{
// 現在のアスペクト比を取得し、変化がなければ処理を抜ける
float currentAspect = (float)Screen.height / (float)Screen.width;
if (Mathf.Approximately(currentAspect_, currentAspect))
{
return;
}
currentAspect_ = currentAspect;
// カメラサイズの調整
camera_.orthographicSize = aspectVec_.y / pixelPerUnit_ / 2;
// ViewportRectの調整
SetViewportRect();
}
// ViewportRectの調整
private void SetViewportRect()
{
float baseAspect = aspectVec_.y / aspectVec_.x; // 基準のアスペクト比
if (baseAspect > currentAspect_)
{
// 画面が横に広い場合、基準のアスペクト比に合わせて調整する
float bgScale = aspectVec_.y / Screen.height;
// viewportRectの幅
float tmpWidth = aspectVec_.x / (Screen.width * bgScale);
// viewportRectを設定
camera_.rect = new Rect(
(1.0f - tmpWidth) / 2, 0.0f, tmpWidth, 1.0f);
}
else
{
// 画面が縦に長い場合、画面の長さにアスペクト比を追従させる
float bgScale = currentAspect_ / baseAspect;
// カメラのサイズを縦の長さに合わせて設定しなおす
camera_.orthographicSize *= bgScale;
// viewportRectを設定(範囲内全てを表示)
camera_.rect = new Rect(0.0f, 0.0f, 1.0f, 1.0f);
}
}
}
縦画面固定のスマホアプリを制作予定なので正直Update()関数で呼び出す必要があるのかは謎なのですが、Android端末だと2画面表示があるとのことですし何かあっては怖いのでとりあえず毎フレーム処理を回しています。
#参考にした素晴らしいサイト様☺
https://pengoya.net/unity/aspect/