前回で効果音も付けてゲームとしては完成という状態になりました。
前回の記事↓
今回は最終回、スマホ解像度対応をして完成を目指します。
AndroidでもiPhoneでもタブレット端末でも、アスペクト比や解像度を自動対応させるスクリプトを作っていきます。
スマホ解像度とアスペクト比の考え方 固定解像度で対応させる方法
さて、今回のゲーム、縦長ですしクリッカーゲームのゲーム性からしてもスマートフォンで動かせると良さそうです。
と、言いましたが、Unityは元よりマルチプラットフォーム開発のためのゲームエンジン。
実は何もしなくても、このままスマートフォン(Android/iOS)へ流し込んで実行することが出来ます。
しかし、一つ問題になるのが「解像度対応」です。
スマートフォンは解像度が非常に多岐に渡ります。そして、「解像度」と言いましたが、特に重要なのは「縦横比(アスペクト比)」です。
ざっと
- 4:3 (iPad等)
- 16:9 (ちょっと前のiPhone系)
- 19.5:9(iPhoneX~)
- 21:9(XPERIA等)
などなど(もちろんもっともっとあります)
これだけあると、ターゲットをどれか一つに絞って出すわけには行きません。
どうしたものでしょうか・・・。
固定解像度で作る方法 機種変更に対応できない
一番簡単なのは「論理的に解像度を固定」してしまうことです。
例えば、今回はゲームのサイズを480×800で作り始めています。
これはあくまでもUnityEditor上での解像度なので、ここで480×800を指定したからと言ってスマートフォンなどの実機上で480×800になるわけではありません。
では、このドロップダウンから違う解像度のStandalone(1024×768)を選択してみましょう。
どうでしょうか。あっさりと画面構成が崩れてしまいましたね。
画面外で生成しているはずの羊は見えてしまっていますし、UI関係は横に広がってしまっていますね。
あと(ちょっと分かりづらいですが)カゴのすぐ下にあったはずの売却ボタンもちょと隙間が空いてしまっています。
ゲーム画面の解像度対応 カメラ描画範囲を変更する方法と考え方
ではまずゲーム画面(UI以外)の解像度をあわせていきます。
基本的にはカメラは画面全体を使って描画しようとします。
そのため、実行する環境(スマートフォンの画面サイズ)によって差が生まれてしまいます。
そこで、カメラが描画しようとする範囲そのものを変更してしまいましょう。
「Main Camera」のInspectorにあるViewport Rectが画面に対する描画範囲になります。
今は
X:0 Y:0
W:1 H:1
となっていますね。
この、XとYが画面の描画位置、W(Width:横幅)とH(Height:縦幅)が画面のどれくらいを描画範囲として使うか、を表しています。
なぜ0や1かというと、ViewPortで使用される座標は実ピクセルではなく、0~1に正規化されたものを使用するからです。
実際の画面の横幅が800ピクセルだろうと、3840ピクセルだろうと、画面の左端は0で、右端は1,丁度真ん中は0.5ということです。
試しに「W」の上でマウスドラッグをして、W(横幅)の値を0~1で変化させてみましょう。
1で画面の横幅全てを、0.5で画面の半分を使うことになり、0以下だと描画されないのがわかりますでしょうか。
今回の場合(480×800用に作ったゲーム を 1024×768の画面で描画)は、0.45を指定すると丁度良くなるようです。
ただ、画面表示が左端になってしまっていますね。
それは描画位置を表すX(およびY)が0になっているからです。では「X」の上でマウスドラッグをして、X(描画位置)の値を0~1で変化させてみましょう。
0で左端、1だと右端になってしまうので何も描画されません。
そして0.5で真ん中というわけではなく、0.5の位置を起点として画面が描画されるので、
真ん中に揃えるためには今回の描画横幅W=0.45 の 半分である0.225 を、0.5から引く必要があります。
0.5 – 0.225 = 0.275ですね。
結果、X = 0.275,W = 0.45 にすると、今回の場合(480×800用に作ったゲーム を 1024×768の画面で描画)は余計な所を描画せず、画面の丁度真ん中に描画することが出来ました。
では再生ボタンを押して、実行してみましょう。
ゲーム画面は必要な箇所だけ描画されるようになりましたが、UIがおかしいですね。
入りきっていたはずの羊購入ボタンが下にはみ出してしまっています。
UIの解像度対応 Canvasのfree aspect設定を行う(まだ画面崩れは起きる)
では、次にUIの解像度対応を行いましょう。
Unityの標準UI(uGui)には、最初から解像度対応の機能があるので、UIについてはそちらを使用します。
Hierarchyから「Canvas」を選択し、Inspectorで最初から付いているComponentである「CanvasScaler」のUI Scale Mode を変更します。
最初は Constant Pixel Size (画面の解像度をそのまま使う)となっているので
Scale With Screen Size(画面の解像度に合わせて拡大縮小する)を選択します。
その際の基準となる解像度(Reference Resolution)と、そこで指定した解像度と実機の解像度が異なる場合にどのように差を吸収するか(Screen Match Mode)が指定出来るので
- Reference Resolution : X 480 Y 800
- Screen Match Mode : Expand
にしておきましょう。
では、再生ボタンを押して確認してみましょう。
羊購入ボタンも全て入りきって、今まで通りの画面になりましたね!!
しかし、もちろんこの対応はあくまでも
- 480×800用に作ったゲーム を 1024×768の画面で描画
の専用の対応です。
これはGameビューの解像度ドロップダウンからFree Aspectを選択してみるとよくわかります。
(Free AspectはUnityEditor上でのGameビューのサイズそのものを画面解像度としてくれます)
このFree Aspectの状態でウインドウサイズを変えてみましょう。
画面に入り切らなかったり、また逆に余白が表示されたりしてしまっていますね。
既に書いたように、iPhoneおよびAndroidの解像度は多岐に渡ります。
このようにFree Aspectを指定してウインドウをどのようにサイズ変更したとしても画面構成が崩れないのが理想です。
そのために解像度固定のスクリプトを用意することにしましょう。
解像度固定スクリプトの作成 全スマホの画面サイズに対応させる方法
HierarchyでMain Camera を選択し、Add Component で解像度固定用の 新しいスクリプト AspectKeeper を追加します。
では、早速AspectKeeperスクリプトを修正していきます。
まず、ターゲットとなるカメラ(targetCamera
)と、固定したい解像度(aspectVec
)をInspectorで指定出来るように、メンバ変数に以下の2つを追加します。
7 8 9 10 11 |
[SerializeField] private Camera targetCamera; //対象とするカメラ [SerializeField] private Vector2 aspectVec; //目的解像度 |
次に、Startメソッドは使わないので削除し、Updateメソッドで、現在の解像度(Screen
)と目的とする解像度(aspectVec
)から、カメラのViewport Rect(camera.rect
)を設定していきます(Camera.Rectに関しての公式リファレンスはこちら)。
まず、現在の解像度の縦横比(以後:アスペクト比)と、目的とする解像度のアスペクト比を求めます。 これは、横幅を縦幅で割るだけです。
1 2 3 4 |
void Update() { var screenAspect = Screen.width / (float)Screen.height; //画面のアスペクト比 var targetAspect = aspectVec.x / aspectVec.y; //目的のアスペクト比 |
なお現在の画面の縦解像度・横解像度はScreen.width
及び Screen.height
で取得することが出来ます。
そのため、widthまたはheightの少なくともどちらかを
(float)
でfloat型にキャストする必要があります。次に、このtargetAspect
をscreenAspect
で割って、現在アスペクト比から目的アスペクト比にするための倍率(magRate
)を計算します。
1 2 3 4 5 6 |
void Update() { var screenAspect = Screen.width / (float)Screen.height; //画面のアスペクト比 var targetAspect = aspectVec.x / aspectVec.y; //目的のアスペクト比 var magRate = targetAspect / screenAspect; //目的アスペクト比にするための倍率 |
さて、このscreenAspectとtargetAspect、magRateをわかりやすくするために、
- 480×800用に作ったゲーム を 1024×768の画面で描画
を割り当てて考えていきます。
すると
- screenAspect = 1024 / 768 = 1.33333333333…
- targetAspect = 480 / 800 = 0.6
- magRate = 0.6 / 1.33333333333 = 0.45
になりました。 この0.45という数字。見覚えがありますね。
これは、先程Viewport Rect の W に設定した値でした。
このmagRate
の値をそのままViewport の Wにセットすると良さそうです。
では、Viewport で使われている Rect 型の変数 viewportRect
を作り、W(width)にmagRateの値をセットしてから、実際にターゲットとしているカメラ(targetCamera)のViewport Rect(rect)にセットするようにします。
1 2 3 4 5 6 7 8 9 10 11 |
void Update() { var screenAspect = Screen.width / (float)Screen.height; //画面のアスペクト比 var targetAspect = aspectVec.x / aspectVec.y; //目的のアスペクト比 var magRate = targetAspect / screenAspect; //目的アスペクト比にするための倍率 var viewportRect = new Rect(0, 0, 1, 1); //Viewport初期値でRectを作成 viewportRect.width = magRate; //使用する横幅を変更 targetCamera.rect = viewportRect; //カメラのViewportに適用 } |
さて、この時点で一度スクリプトを保存して、UnityEditorで確認をしてみたいと思います。
Inspectorで、TargetCameraに自身のCameraを、Aspect Vecにはターゲットとする解像度480と800を入れます。
では、再生ボタンを押して確認してみましょう。
どうでしょうか。Gameビューのサイズを変更すると、動的にCameraのViewportRectが変更されているのがわかると思います。
また、縦幅に伸ばしたり縮めたりする分には画面構成が崩れなくなりました!
ただ、横幅を縮めた時に、Wの値が1を超えてしまって画面内に入り切らなくなってしまっているのがわかります。
これはどういう時に起きるかというと、極端に言うと
- 480×800用に作ったゲーム を 240×800の画面で描画
のような、ゲーム画面を縦幅いっぱいに広げた時に横がはみ出てしまうパターンです。
これもまた、具体的に先程の計算式に当てはめていくと
- screenAspect = 240 / 800 = 0.33333333333…
- targetAspect = 480 / 800 = 0.6
- magRate = 0.6 / 0.33333333333 = 1.8
となり、現在アスペクト比から目的アスペクト比にするための倍率(magRate)を求めたら、1を超えてしまっています。
このような場合はどうなって欲しいかと言うと、Viewport の width は 1(すべて使う)としておいて、使用する縦幅であるheightの方を減らす(1/magRate)必要があります。
これをそのままプログラムに落とし込むとこのようになります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
void Update() { var screenAspect = Screen.width / (float)Screen.height; //画面のアスペクト比 var targetAspect = aspectVec.x / aspectVec.y; //目的のアスペクト比 var magRate = targetAspect / screenAspect; //目的アスペクト比にするための倍率 var viewportRect = new Rect(0, 0, 1, 1); //Viewport初期値でRectを作成 if (magRate < 1) { viewportRect.width = magRate; //使用する横幅を変更 } else { viewportRect.height = 1 / magRate; //使用する縦幅を変更 } targetCamera.rect = viewportRect; //カメラのViewportに適用 } |
さて、またスクリプトを保存して、UnityEditorで確認をしてみましょう。
縦幅だけではなくて、横幅を縮めた時も画面構成が崩れなくなりました!
ただ、またViewportのwidthまたheightを変更しているだけなので、横幅を縮めた場合は左端に、縦幅を縮めた場合は下端に画面が表示されています。これを中央にするには、描画横幅(縦幅)の半分 を、0.5から引く必要があるんでしたね
では、スクリプトをそのように修正しましょう。応用的に縦も対応してしまいます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
void Update() { var screenAspect = Screen.width / (float)Screen.height; //画面のアスペクト比 var targetAspect = aspectVec.x / aspectVec.y; //目的のアスペクト比 var magRate = targetAspect / screenAspect; //目的アスペクト比にするための倍率 var viewportRect = new Rect(0, 0, 1, 1); //Viewport初期値でRectを作成 if (magRate < 1) { viewportRect.width = magRate; //使用する横幅を変更 viewportRect.x = 0.5f - viewportRect.width * 0.5f;//中央寄せ } else { viewportRect.height = 1 / magRate; //使用する縦幅を変更 viewportRect.y = 0.5f - viewportRect.height * 0.5f;//中央寄せ } targetCamera.rect = viewportRect; //カメラのViewportに適用 } |
そして最後に、このスクリプトは実行中だけではなく、UnityEditorで操作している間にも動いてくれたほうが都合が良いのでclassの宣言の前に [ExecuteAlways]
という属性(Attribute)を記述しておきます。
AspectKeeperスクリプト全体は以下のようになります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
using System.Collections; using System.Collections.Generic; using UnityEngine; [ExecuteAlways] public class AspectKeeper : MonoBehaviour { [SerializeField] private Camera targetCamera; //対象とするカメラ [SerializeField] private Vector2 aspectVec; //目的解像度 void Update() { var screenAspect = Screen.width / (float)Screen.height; //画面のアスペクト比 var targetAspect = aspectVec.x / aspectVec.y; //目的のアスペクト比 var magRate = targetAspect / screenAspect; //目的アスペクト比にするための倍率 var viewportRect = new Rect(0, 0, 1, 1); //Viewport初期値でRectを作成 if (magRate < 1) { viewportRect.width = magRate; //使用する横幅を変更 viewportRect.x = 0.5f - viewportRect.width * 0.5f;//中央寄せ } else { viewportRect.height = 1 / magRate; //使用する縦幅を変更 viewportRect.y = 0.5f - viewportRect.height * 0.5f;//中央余生 } targetCamera.rect = viewportRect; //カメラのViewportに適用 } } |
スクリプトを保存してUnityEditorで確認をしてみましょう。[ExecuteAlways] が付いているので、再生ボタンを押さない状態でも、動的にViewportが変更されるようになっているのが確認出来ると思います。
描画範囲外の対応 ゲーム画面外を隠す枠の作り方
以上でスクリプトによる対応は全てなのですが、実はまだ問題があります。
カメラのViewportを変更することで、画面の一部だけを使用するようにしたわけですが、では使用していない部分はどうなるでしょうか。
Unityにおいて、本来画面を消す(1回きれいにする)のはカメラの役割です。
しかし、そのカメラはViewportで使用画面範囲を狭めているため、使用されていない部分はクリア処理が走らない場所ということになります。
これは非常によろしくない状態で、UnityEditor上ではキレイに黒で塗りつぶされていますが、実端末で試してみると
- 直前で実行していた画面が表示される
- ノイズのようなゴミが表示される
- 画面内の部分が何故か表示される
といった、謎の不具合が頻発することになります。(たまたまキレイになんらかの色で塗りつぶされることもあります)
この対処法としては、今ある(最初からある)カメラとは別に、画面全体をクリアする用のカメラを別途用意してしまうことです。
画面外を隠すクリア用カメラの追加
Hierarchyを右クリックし、メニューから「Camera」を選択して、もう一つカメラを追加します。
名前はわかりやすいように「ClearCamera」とでもしておきましょう。
TransformもReset(PositionとRotationは全て0 Scaleは全て1)にしておきます。
さて、ゲーム画面は、というとが真っ青(初期の青い色)になってしまいましたね。
これは、カメラの描画順によるものです。
MainCamera→ClearCameraの順番で描画処理が行われてしまっているため、MainCameraでせっかく描画したUIや羊の画像などが、ClearCameraで全て消えてしまっています。
カメラの描画順は Depth という項目の小さい順に描画処理が行われる仕組みになっています。(MainCameraはDepth=-1, ClearCameraは初期値のDepth=0)
なので、ClearCameraのDepthの値をMainCameraよりももっと小さい値にしてあげればOKです。-2にしてしまいましょう。
また、あくまでも「画面のクリア用」で、何かを描画する必要はないため、 Culling Mask(何を描画するためのカメラか)という項目もNothingに変更します。
そして、AudioListenerは2つあるとログに警告が出続けてしまうので、ClearCameraの方のAudioListenerは消してしまいましょう。
これにより、Camera同士の処理順も正しくなり、先程まで真っ暗(不定)だった箇所がClearCameraのBackgroundで指定した色で塗りつぶされるようになりました!
外枠を装飾するレターボックス(ピラーボックス)の作り方
さて、Androidならこの対応だけでも良いのですが、iOSはこの塗りつぶし処理だけではリジェクト(申請却下)されることがあります。
では、どうするかというと、画像によるレターボックス(ピラーボックス)を置きます。
全て設定できましたら、Gameビューの解像度をFree Aspectにして確認してみましょう。
上下左右にそれぞれ違う色の帯を置くことが出来ました!
おさらい
今回はスマホ解像度対応を作り上げることができました。
ここまでくれば、あなたももうスマホゲームを作れるようになっているはず!
今回のクリッカーゲームだけでなく、どんな機種でも対応できるあなたオリジナルのスマホゲーム作りにチャレンジしてみてください^^
講座全体としては全12回に加え、サウンドマネージャー作成も途中で入っているため非常に長い講座となってしまいました。
あなたのUnityゲーム制作のお役に立てていれば幸いです。
ここまでお付き合い頂き誠にありがとうございました。
次回講座の準備も進めているのでお楽しみに!
放置インフレ系クリッカーゲームの作り方講座に戻る↓
コメント
次に、この screenAspect を targetAspect で割って
書いてあるコードと矛盾しています
targetAspectをscreenAspect で割っています
修正お願い致します。
コメントありがとうございます!
こちら修正しておきました!