Unityにおける異なるアスペクト比でのカメラサイズの調整

# 前書き
スマホゲームの開発で1つ悩ましくても避けられないことがマルチ解像度の対応です。特にAndroidデバイスにおいて画面サイズの豊富さが無法地帯みたいなので、せめて主流のアスペクト比を対応しておかないととせっかく作ったゲームが実機で画面崩れになります。

UnityではUIに関してはCanvasScalerとAnchorの調整でうまくやってくれますが、カメラのサイズは画面アスペクト比が変わっても変わりません。場合によっては表示したいオブジェクトが見切れたりもするのでこの記事では異なるアスペクト比でのカメラサイズの自動調整について話します。

# 平行投影（Orthographic）カメラ
カメラの投影方式（Projection）を平行投影（Orthographic）にした場合、「Size」フィールドがいじられます。この「Size」というのは、カメラの縦方向で表示可能なサイズの1/2のことです。例えば「Size」を5にした場合、縦方向では10単位まで見えます。ここの「単位」はUnityの標準物理距離の1 Unitのことです。Unityの備え付けのCubeが長さ1単位なので、10個のCubeが縦方向に並んだらちょうど画面いっぱい占めます。

Unityはどんな解像度の画面においても縦方向でこの10単位の範囲を表示します（画面サイズによってはCubeが大きく、もしくは小さく見えてきます）。そしてその画面のアスペクト比によって横方向の見える範囲が決められます。$$横方向サイズ=縦方向サイズ\timesアスペクト比$$

例えば9:16のアスペクト比でカメラの「Size」を5に固定した場合、縦方向のサイズが10になり、横方向では5.625単位まで見えます。それを3:4の画面のデバイスに入れたら横方向のサイズが7.5になるのでより広く見えます。これはピラーボックス（pillarboxing）と言います。逆に9:21の画面では横方向のサイズが約4.3になり、9:16の時横いっぱいに表示されるオブジェクトは見切れてしまいます。
![異なるアスペクト比の比較.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/475482/8b7abaf2-884e-7a86-e5a7-7c72d25fd4a7.png)
Unityのデフォルトでは縦方向のサイズを維持することになっていますが、横方向のサイズを保ちたい場合もあります。しかしカメラの「Size」は縦方向にしか対応していないので、横方向のサイズを決めてから縦方向のサイズを逆算する必要があります。逆算と言ってもその画面のアスペクト比を割るだけです。

これでどんな解像度においても横方向はこのサイズで表示されます。しかし縦長の端末においては縦方向のサイズが大きくなり、縦方向でより広く見えます。これはレターボックス（letterboxing）と言います。
![letterboxing.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/475482/a0141de0-fab1-58dd-c18f-1dcd05044887.png)
基準アスペクト比を事前に決めておいて、それより横長の画面だったらピラーボックスに、それより縦長の画面だったらレターボックスにすればどんな解像度でも画面真ん中に表示されるオブジェクトが見切れることがなくなります。Unityのデフォルトではすでにピラーボックスの対応になっているので、横方向のサイズを固定するレターボックスを実装すればよいです。
```c#
if (_camera.aspect < BaseAspect)
{
    // レターボックス
    // 基準（縦方向）サイズと基準アスペクト比から基準横方向サイズを算出
    var baseHorizontalSize = _baseCameraSize * BaseAspect;
    // 基準横方向サイズと対象アスペクト比で対象縦方向サイズを算出
    var verticalSize = baseHorizontalSize / _camera.aspect;
    _camera.orthographicSize = verticalSize;
}
else
{
    // ピラーボックスはそのまま
    _camera.orthographicSize = _baseCameraSize;
}
```

# 透視投影（Perspective）カメラ
カメラの投影方式（Projection）を透視投影（Perspective）にした場合、「Field of View」（FOV）フィールドがいじられます。「Field of View」は縦方向の見える範囲の角度（°）を表します。Unityはどんな解像度においても縦方向でこの角度の範囲を表示します。
![fov.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/475482/590b4317-93e8-4991-f445-83f2cb9a4a25.png)
「Field of View」の角度から正接関数で縦方向の画面サイズを求められます（スクリーンまでの距離*d*を仮で1とします）。これで平行投影カメラのように扱うことができます。
```c#
if (_camera.aspect < BaseAspect)
{
    // レターボックス
    // 基準縦方向サイズ
    var baseVerticalSize = Mathf.Tan(_baseCameraFOV * 0.5f * Mathf.Deg2Rad);
    // 基準横方向サイズ
    var baseHorizontalSize = baseVerticalSize * BaseAspect;
    // 対象縦方向サイズ
    var verticalSize = baseHorizontalSize / _camera.aspect;
    // 対象FOV
    var verticalFov = Mathf.Atan(verticalSize) * Mathf.Rad2Deg * 2;
    _camera.fieldOfView = verticalFov;
}
else
{
    // ピラーボックスはそのまま
    _camera.fieldOfView = _baseCameraFOV;
}
```
ちなみにUnityでは```Camera.HorizontalToVerticalFieldOfView```と```Camera.VerticalToHorizontalFieldOfView```を用意してくれているので、これらを用いてレターボックスの時のFOVを計算することもできますが、中間的に横方向FOVを計算するので上記の方法より計算ステップが多いです（ほんの少しに過ぎないですが）。
```c#
var baseHorizontalFOV = Camera.VerticalToHorizontalFieldOfView(_baseCameraFOV, BaseAspect);
var verticalFov = Camera.HorizontalToVerticalFieldOfView(baseHorizontalFOV, _camera.aspect);
_camera.fieldOfView = verticalFov;
```

# 最後に
[GitHub](https://github.com/RINNUXEI/CameraSizeAdjuster)上にカメラサイズ調整用のスクリプトとそれのカスタムエディタースクリプトを上げました。[ここから](https://github.com/RINNUXEI/CameraSizeAdjuster/releases/tag/v1.0.0)もunitypackageをダウンロードできます。
使い方
1. *CameraSizeAdjuster.unitypackage*をUnityプロジェクトにインポートする
2. *CameraSizeAdjusterEditor.cs*がEditorフォルダ以下に配置されていることを確認する
3. *CameraSizeAdjuster.cs*をカメラが付いているオブジェクトにアタッチする
4. *Base Aspect Ratio*フィールドを基準のアスペクト比に設定する（デフォルトは9:16になっている）
5. カメラが平行投影（Orthographic）になっている場合、*Base Camera Size*フィールドを基準のカメラサイズに設定する
6. カメラの「Size」が異なるスクリーンサイズによって自動的に調整されることを確認する
7. カメラが透視投影（Perspective）になっている場合、*Base Camera FOV*フィールドを基準のカメラFOVに設定する
8. カメラの「Field of View」が異なるスクリーンサイズによって自動的に調整されることを確認する
9. *Show Base Aspect Area*フィールドのチェックをオンにすると基準アスペクト比のビューポートを確認できる