ゲームを作っていくにあたって、画面作りって大事ですよね。
でも、プラットフォームによっては複数の画面に対応しなくちゃいけなくてどういう風に画面を作っていけばいいかわからなくなってしまうことがあります。
特に新しい新規モデルの端末が出てきて、新しいタイプの解像度だとどういう風に対応すればいいのかちょっと混乱してしまいます。
そのような時に混乱しないよう、ちゃんと落とし込めるようにするため、実際にUnityの画面がどのように端末に表示されるのかを検証してみました。
結論だけ見たい人はこちらから飛んでください
<アスペクト比16:9の端末で検証>
とりあえず、この記事を書いている段階では一番端末数が多い16:9の解像度でUnityの画面がどのように表示されるのかを検証していきたいと思います。
解像度1334×750でアスペクト比16:9のiPhone7で検証していきます。
カメラ内の検証(3D)
それではやっていきましょう。まずは普通にカメラで映したものを検証してみます。
↓のようにiPhoneと同じ解像度にゲームビューを設定して、端っこにCubeを設置していきます。
若干雑ですが、とりあえず隅っこがわかればOKです。
これを実機に焼き込んで確認していきます。
↑を実機で見ると↓のようになりました。
iPhoneの縦画面
iPhoneの横画面
横画面は合っているので問題無いように思えます。
しかしながら縦向きにすると画面端に置いていたCubeが中心の二つしか表示されなくなってしまいました。横向きでの表示より大きく表示されている事がみて取れます。
縦画面の時、上下のCubeがしっかりと端っこに映っていることから縦幅に合わせて拡大縮小している事がわかります。
色々いじってみましたが、どーにも縦基準みたいですね。縦は画面に収まるように拡大縮小されます。横は縦と同じ倍率で拡大縮小されて、はみ出した分見切れるみたいです。
これはエディタでの動きを見るとよくわかります。
カメラをヒエラルキーで選択すると↓のようなField of Viewの白いラインが映ると思います。
解像度(ゲームビュー)をがちゃがちゃ変えてもこのField of Viewの白いラインの縦幅は変わりません。その代わり横幅は大きくなったり縮んだりします。
どうやらCameraというのはこのField of View縦幅を基準にして画面に映しているみたいですね。
エディタ上のTransfomの値から幅を計算すると↓のようになりました。
iPhoneが横向きの時は縦幅が750pxなので5.28→750なので
750 ÷ 5.28 = 142.045455
です。これを横幅に掛けると
9.4 × 142.045455 = 1335.22727
となって、なんか1ズレてますが、概ね合っている事がわかります。
1ズレているのは自分が適当に置いたせいですね。(オィ
iPhoneを縦向きにすると今度は縦幅が1334になってしまうので
1334 ÷ 5.28 = 252.651515
となり、横幅750pxまでしか表示されないので
750 ÷ 252.651515 = 2.96851574
になるので、エディタ上のTransfomの値から幅が2.96851574までの範囲までしか表示されないことになるので縦向きにした時上と下の二つのCubeしか表示されなかったのも納得ですね。
UGUIの検証(ScreenOverlay)
今度はカメラとは無関係なScreenOverlayなCanvasで検証していきたいと思います。
ちょうどiPhone7の解像度に合ったような状態で四角いImageが画面端にあります。
キャンバスの設定は↓のような感じです。
UI Scale ModeがConstant Pixel Sizeになっているので伸び縮みしないような状態になっています。
この状態でiPhone7に焼き込みます。
結果は↓のような感じになりました。
iPhoneの縦画面
iPhoneの横画面
Cubeの時と同じように横向きの場合は解像度に合った状態になっています。
縦向きの時はCubeと同じく中心の二つのImageしか映らなくなっていますが、Cubeとは違って画面端ではなくちょっと真ん中よりに表示されています。
2つの画像を重ねると↓みたいな感じになります。
縦向きにした時の二つの四角がちょうど重なるので、拡大縮小せずに設定した距離に忠実に表示されている事がわかります。
というか、Canvasを画面に合わせて拡大縮小する設定があるので、それ以外の設定では拡大縮小を一切しないみたいです。まぁ当然と言えば当然の結果ですね。
数値でみると↓のような感じですね。
既に普通のカメラで映した映像とUGUIでは解像度による画面の映り方が違う事がわかりました。
ではここから検証を続けていきましょう。
<Screen.SetResolutionを使った場合>
次はUnity側で解像度を指定して、本来の解像度と違う解像度になるように指定したらどうなるのか検証していきます。
Screen.SetResolutionでゲームの解像度をスクリプトから指定できます。
画面の縦、横を数値で指定できるのですが、三つ目の引数でフルスクリーンにするのかどうかを設定できるので、その場合どうなるのかも検証していきます。
Screen.SetResolution(幅,高さ,フルスクリーンかどうか);ですね
四つ目の引数としてリフレッシュレートが存在しますが、今回は関係ないのでスルーします。
実機より小さい解像度に設定した場合
↓のようなスクリプトを貼っつけてどうなるのかをみてみます。
using System.Collections; using System.Collections.Generic; using UnityEngine; public class NewBehaviourScript : MonoBehaviour { void Start() { Screen.SetResolution(500, 500, false); } }
500×500なので正方形で画面より小さくなるはずです。
ちなみにエディタで実行すると↓のようになります。
カメラで映した状態でSetResolution(エディタ)
UGUIでSetResolution(エディタ)
これはScreen.SetResolutionはエディタ上では機能しないという仕様なので、エディタで見ても何も変化がない訳ですね。
ちなみにScreen.SetResolutionで画面の解像度を変えたとしてもScreen.widthとheightの値は変わりませんでした。(実機調べ)
では、実機上でどのような見え方になるのかやってみましょう。
・カメラで映した状態で実機より小さくSetResolutionした場合
Cubeをカメラで映している状態で解像度を500×500にしたところ↓のようになりました。
iPhoneの縦画面
iPhoneの横画面
相変わらず縦基準になってますね。
Awakeで呼ぶ必要があるかな?とも思いましたが、Startでもちゃんと反映されているので、描画前に呼び出せば反映されるようです。(おそらく呼び出したフレームから解像度が変化するものと思われます)
↓のようにはならないみたいです。
無理矢理500×500にしてしまったのでみょーんと伸びています。
1334の幅に500が表示され、750の幅に500が表示されているので倍率がデカイ方向にみょーんと伸びています。
Cubeは大きいのも小さいのもX,Y,Zのスケーリングの数値が同一なので立方体です。
1334 ÷ 500 = 2.688
750 ÷ 500 = 1.5
となるので映っている立方体が1334の方向へ2.668倍されて、750の方向へ1.5倍されているのでみょーんと伸びているわけです。(実際のCubeの大きさじゃなくて画面に表示されている「絵」が。という話です)
このことからScreen.SetResolutionというのは画面全体の解像度を指定した数値として扱うものみたいですね。
ちなみにフルスクリーンをtrueにしても同じ結果になりました。
・UGUIを映した状態で実機より小さくSetResolutionした場合
今度はUGUIを映している状態で解像度を500×500にしたところ↓のようになりました。
iPhoneの縦画面
iPhoneの横画面
何も表示されませんでした。
500×500にして小さくしてしまったのでどーやら中心部分の何もないところしか映っていないようです。
ちょっとこれではどうなっているのかわからないので↓のようにしました。
赤色のImageで500×500のラインをわかりやすくしています。この状態で実機上に焼いて見ます。
↓のような結果になりました。
iPhoneの縦画面
iPhoneの横画面
あ、アルェー。
画面の大きさによらなかったUGUIが拡大縮小されています。
1334→500、750→500になっているので、みょーんと伸びていますね。
どーやらUGUIは設定によらずSetResolutionの影響を受けるみたいです。
ただし、白色のImageが表示されていないことから相変わらず設定した距離に忠実に表示されている事がわかります。これはConstant Pixel Sizeの特色ですね。
SetResolutionは画面全体に影響するので、UGUIをどのように画面に映すのかを決定してからSetResolutionで伸ばされているものと思われます。
また、途中でiPhoneを傾けて縦横を変更してもちゃんと対応されていました。
一番最初の時の検証ではズレていて今回の検証でちゃんと対応しているのは正方形で指定しているからです。
こちらもフルスクリーンをtrueにしても同じ結果になりました。
どーもフルスクリーンの引数は解像度の引き伸ばしなどには関係なく、単にウィンドウモードかフルスクリーンモードの切り替えっぽいですね。
なので、iPhone(というかウィンドウモードにできない端末全般)では解像度に一切関係ない引数だとわかります。
フルスクリーンモードにすると端末のステータスバーを消せると言う情報を聞いたことがあるのですが、ProjectSettingでHidden Status Barにチェックができるのでちょっと謎です。昔はフルスクリーンで対応してた?とかですかね?
実機より大きい解像度を設定した場合
↑と同様の条件で2000×2000でやってみようと思います。実機よりオーバーした解像度を指定しています。
・カメラで映した状態で実機より大きくSetResolutionした場合
Cubeでやった場合実機では↓のように表示されました。
iPhoneの縦画面
iPhoneの横画面
500×500と同じ結果になりました。
500×500の時もそうでしたが、長方形を正方形に歪めているのでみょーんと伸びます。縦基準で拡大縮小しているのでそりゃそうです。
しかしながら、ちゃんと歪んでいるので、Screen.SetResolutionは実機の解像度以上の値を指定しても動作するということがわかります。
ただし、どの程度ピクセルが乱れるのかを検証していないので画像が変に表示されることがあるかもしれません。綺麗な画像が表示されるのが命なゲームは痛手になる可能性もあるのでご留意ください。
・UGUIを映した状態で実機より大きくSetResolutionした場合
次にUGUIで2000×2000のテストしていきます。
エディタ上では↓のような感じです。わかりやすいように緑のImageを並べました。
これを実機上で実行すると↓のようになります。
iPhoneの縦画面
iPhoneの横画面
実機より小さくした時と同様に画面がみょーんとなっています。小さくした時は引き伸ばされていましたが、今回の場合は実機より大きくしているので縮められている形になりますね。
カメラで映したものにしろUGUIにしろ実機より大きい値を適用しても絵的に見れる状態にはなってますが、低スペック端末に焼いた時、極端に処理が重くなり、カクカクになった時がありました。
可能ではあるけどもなるべくやらないほうがいいかもしれません。
Screen.SetResolutionの注意点
色々検証していて引っかかったのがScreen.SetResolutionの仕方を間違えると画面が変になりました。
どのようになるかと言うと、スマホの縦横を固定せず反転自由な状態にしている時に、SetResolutionの長い方と起動の縦横が違うと変になるというものです。
↓はSetResolutionの横幅の方が長い状態で縦画面で起動した場合です。真中の数値は設定した解像度の縦、横の数値です。
↑の状態でスマホを横にしたり縦にしたりすると↓の状態になります。
アスペクト比が固定されてしまっているのはちょっと無視してください。(別の検証中にたまたま気づいたので)
1000と500の数値が反対になっているのがわかるでしょうか?
どうやらスマホを傾けて画面を回転させるとSetResolutionで長く指定した方が実機の長い方に合わせてくれるみたいですね。
ただし、傾ける前の初回起動時はおかしかったので、どうやら傾けた時だけ合わせてくれることがわかります。
その為、回転をオフにするか、起動時の縦横をみてSetResolutionしてあげる必要がありそうです。
<Camera.rect(Viewport Rect)を使った場合>
さて、次はカメラ自体の設定を切り替えて行こうと思います。Camera.rect(Viewport Rect)を変更することによって画面の見え方を変える事ができます。
インスペクターで言うと↓のところになります。これを変更することによってアスペクト比を変更することができるようになります。
これをスクリプトから変更する場合は↓のような感じで変更します。
using System.Collections; using System.Collections.Generic; using UnityEngine; public class NewBehaviourScript : MonoBehaviour { void Start() { Camera came = GetComponent(); came.rect = new Rect(0, 0, 1, 1); //x,y,w,h (xとyは位置,wとhは幅と高さ) } }
ちょっとわかりやすくするためにUGUIの方から検証していきます。
UGUI(Overlay)でカメラのViewport Rectを変更した場合
UGUIを表示させた状態でカメラのWとH(WidthとHeight)の値をいじると↓のようになります。
青色の部分がカメラで映している部分になります。
どうやら左下が原点になっているようですね。
画面全体の幅、高さを1として、それからどれくらいの割合でカメラを映すのかを表していますね。
次にXとYを変更すると↓のようになります。これは位置を表しています。
カメラが映っている位置が原点(左下)から移動しているのがわかりますね。
この状態でとりあえず実機に焼いてみましょうか。この時SetResolutionは行なっていません。
結果は↓のようになりました。
iPhoneの縦画面
iPhoneの横画面
SetResolutionを行なっていないので解像度は1334×750になっています。
青色の部分がカメラに写している場所で、黒色の所がカメラの範囲外ですね。
iOSの場合、範囲外が黒色というのは結構大事な事実なので、iOSでリリースを考えている人はここをおさえておきましょう。黒色の部分が多いとAppleの気分次第で(マジで冗談抜きに担当者の気分で決まる)アプリがリジェクトを喰らいます。
青色の大きさが画面に合わせて長方形になっていることから、ちゃんと画面からの割合で表示されていることがわかります。
各種Imageの四角はCanvasがOverlayの設定なので表示されています。
UGUIはオーバーレイである為、カメラとは関係ない状態になっています。まぁ、当然ですよね。
カメラで映した状態でカメラのViewport Rectを変更した場合
今度はCubeの方で同じことをしてみようと思います。UGUIの時と同じようにViewport Rectを(0.2, 0.2, 0.7, 0.7)にします。
結果↓のようになりました。
iPhoneの縦画面
iPhoneの横画面
青色の範囲はUGUIで映した時と同じようになっています。
ですが、青色の範囲にCubeが追従してズレているし、縮小されています。
んでもってやっぱり縦基準みたいです。(どちらも上下のCubeが端に表示されているので)
どうやらこれはカメラの画面の占有を示すものであって、カメラが捉える映像は変わらないようです。
CanvasをScreen Space – Cameraにした場合
さて、CanvasがOverlayの時はUGUIがカメラと関係なかったのであのような結果でしたが、CanvasをScreen Space Cameraにしたらどうなるのかを検証してみようと思います。
Viewport Rectは(0.2, 0.2, 0.7, 0.7)です。
結果↓になりました。
iPhoneの縦画面
iPhoneの横画面
んー???
どうもスケーリングはかかっていないが、表示位置が中心からズレてはいます。
ちょっとよくわからない状態だったので、スケーリングに関わる設定を色々といじり倒したのですが、Canvas Scalerの設定を変えても見え方は変わりませんでした。
Canvas Scalerは設定がScreen Space – Cameraであっても、カメラに合わせるわけではなく、画面の大きさに合わせるようです。
逆に言うと計算して動的にReference Resolutionを変えてあげれば表示位置と範囲を変えつつUGUIを表示できそうです。
<UIの解像度を考える(Canvas Scaler)>
Screen.SetResolutionは画面全体の解像度、Camera.rect(Viewport Rect)はカメラのアスペクト比をコントロールするものでした。
UIはUIでまた別に考える必要があります。UGUIを使用していた場合Canvasの設定によります。
CanvasのUI Scale Modeの設定がWorld以外だった場合、UIの解像度をどうすればいいのかというのはCanvasScalerというコンポーネントの設定を考える必要があります。
詳しい使い方は↓の記事を見ていただければと思います。
これはReference ResolutionでCanvas自体の大きさを決定して、その大きさに合わせてUGUIを拡大縮小して画面に合わせてくれます。
しかしながら、もし画面に合わせたい場合、プレイする側がどんな解像度の端末を使用するのかわからないので、Reference Resolutionを変更した場合どのようになるのかを検証していきます。
ちなみに全部のImageのAnchorsとPivotは全て0.5になってます。
まずはちゃんと合っている状態にします。
とりあえず、解像度とぴったりの状態に合わせてみると白の四角がちょうど枠にハマったので正しい状態のようです。
この状態からReference Resolutionを500×500に変更してみます。
すると↓のようになりました。
MatchがWidth全振りになっているため500の横幅は合ってますが縦幅は合わなくなってしまいました。
↑ではちょっとわかりづらいですが、中のImageの大きさは自体は変わっているのですが、画面に合わせて変わっているわけではなく単純にX,Y全て同じ値で変更されています。
このことから、CanvasScalerは各種UGUIの中身を画面の大きさに合わせてX,Y,Zを各々計算して拡大縮小するわけではなく、単純な(X,Y,Z同一の)拡大縮小するものだとわかります。
簡単にいうとみょーんとはならないみたいです。
灰色になって動かせないですが、これはCanvas自体のScaleを見る事でもわかります。
X,Y,Zが同じ値になっている事がわかりますね。
どうやらCanvas内のUGUIを拡大縮小するわけではなく、Canvasそのものを拡大縮小する事で子オブジェクト全てがそれに影響を受けているようです。
今度は一旦元の状態に戻してから
今度はAnchorsをそれぞれに合わせた位置にしてみたいと思います。
要するに右上にあるImageは↓みたいな設定にします。
これと同じように上下左右それぞれに合ったAnchorsにしてみます。
この状態でReference Resolutionを500×500に変更してみます。
すると↓のようになりました。
各種Imageの大きさが変わっていて、横にみょーんと伸びているわけではないので、こちらも単純にXとY同一の値で拡大縮小されていることがわかります。
あと、画面見え方は大きく変わっていますが、拡大縮小率は変わらないみたいですね。
Canvas Scalerの設定が一緒の場合↑数値が変わっていない事がわかります。
Anchorsを変えた事によって基準としている位置が変わっているみたいです。例えば右上を基準としたImageは右上を基準にしてImageの位置を決定しています。右上はCanvasScalerによって拡大縮小されて位置が変わるのでそれに合わせて動いているのがわかります。
以上よりわかることをまとめると
なんかUGUI周りを触っているとがちゃがちゃ大きさが変わったり、位置が変わったりややこしかったですが、ちゃんと分解してみるとやっていることは単純だったということがわかりました。
<UGUIのRectTransformを考える>
今度はUGUIのRectTransformの設定で見え方がどのように変わっていくのかを検証していきたいと思います。
Anchorsを端に合わせたRectTransform
Anchorsをそれぞれに合わせた位置にしたらどうなるのかをみていきます。
要するに右上にあるImageは↓みたいな設定にします。
これと同じように上下左右それぞれに合ったAnchorsにしてみます。
この状態で実機に焼くと↓のようになりました。
iPhoneの縦画面
iPhoneの横画面
まぁ、思っていた通りになりましたね。
Anchorsを変えた事によって基準としている位置が変わっているみたいです。例えば右上を基準としたImageは右上を基準にしてImageの位置を決定しています。
stretch設定のRectTransform
次にRect Transformをstretchにして検証してみます。
AnchorsのMinとMaxを0、1にするとこのようなアイコンになりますが、MinとMaxが同じ値ではない時で01ではない場合、何も矢印がないアイコンになります。
何も矢印がないアイコンも伸び縮みするので同様のものとして考えます。
これを実機でテストすると↓のようになりました。
iPhoneの縦画面
iPhoneの横画面
予想外の結果になりました。横はちゃんと表示されていますが、縦は表示されていません。
ちょっとどうなっているのかわからないので、エディタ上でみてみると↓のようになっていました。
どうやらstretchが拡大縮小できていないようです。赤いバツマークが出ています。
stretchがどのように拡大縮小しているのかは↓の記事を参考にしてください
バツになっている理由はLeftがRightより右にあり、BottomがTopより上にあるせいです。
Left,Right,Bottom,Topはアンカーからの距離で考えているので、これらの大きさより表示できる領域が小さくなってしまうとバツになってしまうようです。
その為、CanvasScalerの設定で距離が固定値になってしまうConstant Pixel SizeやConstant Physical Sizeにしてしまうとstretch機能は使えないと考えて良さそうです。
設定上使えますが、破綻する可能性が非常に高いです。
まぁ、値を固定する設定なのにstretchにする事はまずないと思いますが、例えば綺麗に見せたいグラフィックをConstant Pixel Sizeで表示して、めんどくさがって同じCanvasでstretch設定のUIを突っ込んでしまったりするマズイです。この場合Canvasを2つ作った方がいいかと思います。
何か理由がない限り大人しくCanvasScalerをScale With Screen Sizeにした方がいいですね。
が、単にScale With Screen Sizeにするだけではダメでアスペクト比が変わると↓のようになりました。
まぁ、そうと言えばそうなのですが、みょーんと伸びています。
このことから、RectTransformでStretchの設定にしている場合、Scale With Screen Sizeにして且つアスペクト比も固定する必要があると言えます。
さて、ここで問題があります。Screen Space – Overlayにしろ、Screen Space – Cameraにしろカメラのアスペクト比を変えてもCanvas Scalerは画面の大きさを参照するので、Scale With Screen Sizeの設定だけではどうにもなりません。
Screen Space – OverlayでのStretch
まず対策として、Screen Space – Overlayの場合はstretch設定のUGUIを一番上に置かない事です。
このstretchは親のアンカーを見ます。Screen Space – Overlayの場合Canvasのアンカーが画面の大きさになるので、これはコントロールできません。
その為、親に1つカラのオブジェクトを噛ませてあげてアンカーをコントロールした方が無難です。
もしくは、stretchを、縦もしくは横のみにするようにして全方位での拡大縮小はしないようにする設定にするといいかなと思います。
カメラが縦基準だったように、UGUIも基準を設けて、それに合わせるといった方法と取るのがいい気がします。
それか、みょーんと伸びてしまっても大丈夫なテクスチャをにするかです。
Screen Space – CameraでのStretch
色々調査していた結果、このScreen Space – Cameraでの設定は非常に曲者だということがわかりました。
例えScreen Space – CameraであってもCanvas Scalerは画面の大きさを参照しますが、アンカーはカメラの端を参照するみたいです。
その為、Canvas ScalerのScale With Screen Sizeは画面の大きさで調整され、RectTransformのstretchはカメラの端から調整されます。
Viewport Rectでの調査の際にはUGUIのアンカーが中心にあったので、「CanvasはScreen Space – Cameraの場合、スケーリングはされないが、位置は調整される」という結果になりましたが、stretch設定やアンカーをカメラの端に置いた場合は結果が変わるようです。
と、いうわけでアスペクト比を固定した場合で実機に焼いてみたら↓のようになりました。
iPhoneの縦画面
iPhoneの縦画面
いい感じになっています。
アンカーが中心になっていた場合はこうはならないので、Screen Space – Cameraの場合、Scale With Screen Sizeにしてアスペクト比を固定し、アンカーを画面端に持ってくる必要があるということがわかります。
アスペクト比を固定する場合はかなり有用な設定だと言えると思います。ただし、アンカーを中心に持ってこない方がいいということになります。
アンカーを中心にするのは本当に中心に映っていて欲しいUGUIのみにする形になります。
この設定で↓のアイコンのRectTransformをなるべくCanvasの子に持ってこない方がいいということです。(Canvasから見て孫以下の階層にいるオブジェクトは大丈夫です)
<Camera.Physical Camera>
CameraのインスペクターでPhysical Cameraにチェックを入れるとカメラの見え方を変えることができます。
これは2018.2から追加された項目みたいです。
これの中身については↓の記事を参考にしてください
これを実機に焼いてみるとどうなるのか検証していきます。
Viewport Rectは(0.2, 0.2, 0.7, 0.7)で検証してみます。
Gate FitがHorizontalの時↓のようになりました。
iPhoneの縦画面
iPhoneの横画面
Gate FitがHorizontalなので横基準になっていますね。設定によって基準や見え方を色々変えられるみたいです。
Physical Cameraはリアルなカメラを使いたい場合や見え方を工夫したい場合に使えそうです。
また、本来縦基準のカメラの拡大縮小を横基準にすることができるのでゲームによっては重宝するかもしれません。
<セーフエリアについて考える>
さて、iPhoneXとかいう四角でおいておけばいいものをわざわざ端末めいいっぱいまで画面を広げやがったとんだお騒がせ野郎のせいで、セーフエリアを考慮した対応を余儀なくされてしまいました。
雑な絵で申し訳ないですが概ね↓みたいな感じです。
画面がただの四角ではなく、スマホの形に合わせて広がっているのがわかるかと思います。つまりただの工夫の無い四角い画面構成ではいかん状態になってしまいました。
横だと↑のような感じですが、縦にするとホームバーが絵の右側のところに移動します。
Androidでもこのノッチブームは到来しており、ノッチを搭載している端末が続々と出る始末で、なおかつノッチの形が端末によるとかいう悪夢まで発生しています。
そのため、↓の情報から計算して画面の配置を考えないといけないわけです。
が、Screen.safeAreaは機能しない端末があるみたいです。
Huawei P20でテストしてみたのですが、Screen.widthとScreen.safeArea.widthが同一の値になってしまいました。もちろんheightも一緒です。
これはマニフェストの書き換えで対応できるらしいのですが、どうもこの対応で直るのはHuawei P20であって、他の機種で直るとは限らないみたいです。他の機種では他の機種なりの対応をしないといけないみたいですね。
Unity2019.1から直るという情報もあったのですが、Unity2019.2.12f1でテストしてダメでした
いろんな種類や形のノッチもありますし、機種毎に対応方法を考えなければいけないとするとAndroidはセーフエリア対応しないのが安定っぽいです。
例え、既存の機種に対応したとしても新しい端末が出てきてはイタチごっこになりそうですしね。
上部メニューのEdit>ProjectSettingのPlayerタブのAndroidの項目から
Render outside safe areaのチェックは外しましょう。
こうすることで、画面はただの四角になり、変な形の部分は黒色になります。
こうなってくるとiPhoneの為だけに対応する必要が出てきます。ノッチは廃止になるとの噂もありますが、まだ噂レベルです。
iPadは今の所ノッチは無いっぽいです。今後出てきたら対応しなければいけません。
Appleはセーフエリア外を黒色にしてしまうとアプリをリジェクトされてしまうので対応せざるをえません。
Appleの毎度の理不尽なやつかーと思ったら、iPhoneXで範囲外を何もせずに真っ黒な状態にすると、なんか黒い部分がチラチラ明滅し始めたので、あー確かにコレはリジェクトになるわと納得しました。(黒にしたい場合でもちゃんと黒色を渡してあげないと変になるみたいです)
さて、ではAndroidは対応しないとした場合、iPhoneの為だけに対応するのはもはや馬鹿らしいので、適当に帯とか枠でも置いておくのが正解かもしれません。それか単に画角を広くするか。
真面目に対応しても振り回されて終わりな気がします。
UGUI以外の部分はカメラをセーフエリア内に移動させてしまえばいいと思います。Viewport Rectを計算してセーフエリアに合わせましょう。カメラを2台用意して帯や枠を全画面に映して、その上にゲーム画面を乗っけるのが一番手っ取り早いかもしれませんね。
× Camera.main.rect = Screen.safeArea;
こうできそうな気がしますが、Cameraのrectはノーマライズされた値で、Screen.safeAreaは解像度の実数なので計算する必要があります。
さらに言うとScreen.safeAreaの値は縦と横で違うみたいなので、縦横変えることができるゲームではUpdateで変わったかどうかをチェックする必要があるっぽいです。
Screen.SetResolutionしてもScreen.safeAreaは変わらなかったので、これは考慮に入れる必要はありません。
その為
Rect safeAreaRect = new Rect();
safeAreaRect.width = Screen.safeArea.width / Screen.width;
safeAreaRect.height = Screen.safeArea.height / Screen.height;
safeAreaRect.x = Screen.safeArea.x / Screen.width;
safeAreaRect.y = Screen.safeArea.y / Screen.height;
Camera.main.rect = safeAreaRect;
こうすれば、カメラがセーフエリア内に収まります。
縦横変更されてしまうと更新してあげる必要があるので、変更可能なゲームは変更を検知して↑をもう一回走らせる必要があります。
UGUIはCanvasの設定をScreen Space – Cameraにしてしまって、UGUIをカメラ内で映すようにして、カメラをセーフエリアの場所へ移動させてしまうのが最も楽かもしれないと考えたんですが、ちょっと一工夫が必要でした。
セーフエリアがあるとアスペクト比が実際のディスプレイから変わってしまうという酷い有様だったのでUGUIを設置するべきアスペクト比がわかりません。
その為、カメラのアスペクト比を固定してしまうか、UGUIのRectTransformで対応するといいかもしれません。
↑こんな感じで右下にあるUGUIは右下にアンカーが来るようにしたら、カメラの大きさ(すなわちセーフエリアの大きさ)に合わせて移動してくれます。
ただし、これだけでは拡大縮小はされないので、UIの比率が若干デカめになってしまいますが、セーフエリア外の少ない領域分の差なので許容してもいいかなと思います。
iPhoneXの解像度に合わせて黄色のImageでやってみた結果が↓です。
当たり前っちゃ当たり前ですが、スクショ撮るとノッチで隠れている事は考慮されないんですね。ノッチの形に合わせたUIとかは作らない方が吉かもしれません。
セーフエリア対応なし
セーフエリア対応あり
ちゃんとできてますね。
黄色以外はちゃんと設定してないのでズレてますが気にしないでください
Device Simulator
Unity2019.3からまだpreview版ではあるものの、エディタ上で実機での見え方が確認できる大変便利な機能がPackageManagerにて登場しました。
が、プレビュー版だけあって動きませんでした。
↓こんな感じです。
本来ならセーフエリアが緑の枠線で表示され、画面の周囲にスマホの枠っぽいのが表示されるはずなんですが、ありません。(ちゃんとHighlight Safe Areaにチェックが入ってます。SwitchPlatformeでプラットフォーム合わせてRender outside safe areaにしても無理でした。)
他の人は映っているっぽいので、たまたま変なバージョンを引いたっぽいですね。
Windows、およびMacでテストしましたが両方動かなかったので、ネット上に転がっている情報では足りない何かが存在するのか、たまたま運悪く今は動かないか。色々バージョンを変えてみましたが無理でした。
まぁ、2019/11/14現在Unity2019.3はまだbeta版ですし、これもpreview版なのでこうなってしまうのは仕方ないといった感じです。将来に期待しましょう。
<複数解像度に対応するには>
さて、↑の検証結果からどのようにして複数解像度に対応するのかを考えてみます。
複数解像度の一例としてiOS/Andrioidについて考えてみましょう。
・2688 × 1242(アスペクト比 19 : 9)
・2436 ×1125(アスペクト比 2 : 1)
・1920 × 1080(アスペクト比 16 : 9)
・2048 × 1536(アスペクト比 4 : 3)
etc…etc…..
これ、挙げだしたらキリがないので代表的なアスペクト比のみでおいておきます。
ここから、アスペクト比の大きい値から小さい値で割った計算をします。
19 : 9 → 19 ÷ 9 = 2.1111…..
2 : 1 → 2 ÷ 1 = 2
16 : 9 → 16 ÷ 9 = 1.7777……
4 : 3 → 4 ÷ 3 = 1.33333……
とりあえずこの値は参考値だと思ってください。↓で度々出てきます。
複数解像度での必須対応
さて、まず画面全体に影響するSetResolutionについて考えていきたいと思います。
まず、近年の端末は解像度が高すぎるので、ゲームが重くなりがちです。
UnityではデフォルトでCPUがGPUの処理を待つ仕組み(VSync)がオンになっているので、解像度が高過ぎるとゲームがカクカクになる恐れがあります。これを切ることもできますが描画ズレが発生するので基本はオンにした方がいいです。
その為、解像度の最大値を決めてしまい、それ以上になったら計算して解像度を下げる処理をしてあげる必要があります。
固定の解像度を渡してしまうと↑の検証のようにみょーんと伸びる恐れがあるので、ちゃんと計算して元々のアスペクト比を保ったまま下げてあげるのがベストかと思います。
解像度の最大値を設定などで変更できるようにすると端末によって画面のクオリティを変えれるのでいいかもしれません。
というわけで↓のスクリプトはほぼ必須で入ると思いますので入れましょう。
//上限を決める
(これは適宜設定を外部に出して読み込むなどして対応してください) //オプションなどでこの値を変更できるようにするとユーザーフレンドリーかもしれませんfloat maxWidth = 1080.0f;
float maxHeight = 1080.0f;
//それぞれのオーバーしている倍率を求めるfloat scaleWidth = (float)Screen.width / maxWidth;
float scaleHeight = (float)Screen.height / maxHeight;
//オーバーし過ぎている方から縮小率を得るfloat rate;
if(scaleWidth > scaleHeight)
{
rate = scaleWidth;
}
else
{
rate = scaleHeight;
}
//上限よりオーバーしていたら元々のアスペクト比を保ったまま解像度を縮小するif (rate > 1.0f)
{
//切り上げで計算(1ドット欠けを防ぐ)
int setWidth = Mathf.CeilToInt((float)Screen.width / rate);
int setHeight = Mathf.CeilToInt((float)Screen.height / rate);
Screen.SetResolution(setWidth, setHeight, false);
}
↑の縦横の最大値は参考値が1にもっとも近いものと、最も大きいものから算出するといいと思います。
例えば横に長いゲームであるなら、縦の大きさを参考値1に近いもので考え、参考値が最も大きいもので横の大きさを考えます。それらの端末がどの程度の解像度におさまって欲しいかで値を決めればいいかなと思います。
よくわからなかったらmaxWidthもmaxHeightも同じ値にしてもいいかもしれません
大きい解像度だった場合、SetResolutionで比率をそのままに解像度を下げる処理を入れよう
ゲーム部分のカメラ対応
さて、次はゲーム部分のカメラについて考えていきます。
ここで、ゲームでのカメラについてどうするのか2種類に分類分けができると思います。
それは、カメラの画角がゲーム内容に関わるかどうかです。
・カメラの画角がゲーム内容に関わるゲーム
例えば、シューティングゲームやアクションゲームの場合、端末によって画角が広さが変わってしまうと、先がどれくらい見えるかが端末によって変わってしまいます。
ということは、持っている機種で有利不利が出てきてしまいます。
また、画面をタッチする事でゲーム内のオブジェクトに触れるといった機能を持ったゲームもこちらに入ります。(UIは除く)
例え、有利不利に関係なくても画角によって触れられる範囲が変わってしまうのでこちらに分類する必要があります。
これらはアスペクト比を固定して見える範囲を端末に寄らないようにした方がいいかと思います。
・カメラの画角がゲーム内容に関わらないゲーム
逆に普通のRPGやノベル系のゲームではカメラの見える範囲でゲームの有利不利は変わらないので、画面を広くとった方が映像として豪華に見えそうです。
ただし、ゲームが画面内におさまるかどうかを考えないといけないので、画角内にゲームで見えなければいけない部分が収まる必要があります。
近年では色々な機種が出てきていて、いつどんな解像度の機種が出てくるかわからない状態にあります。(2:1が限界かと思っていたらそれ以上のものが登場しています)
そのため、ゲームが画面内におさまるかどうかという基準は安全をとって1:1の画面でゲーム内容が伝わるのかを考えた方が良さそうです。
現状参考値が1に最も近いのは4:3の画面ですので、それで合わせてもいいですが、いつ新しい解像度の端末が登場するのかがわからないので1で考えます。
例えば↓のようなゲームがあったとします。(適当です)
ゲームとして必要な画面が↓のような範囲内に入っていればアスペクト比を固定しなくても大丈夫です。
長さ計らずに適当に斜線引いたので、真ん中が1:1になっているものとしてみてください。
真ん中がゲーム部分で、赤斜線の部分は見えなくても問題ないけど見えると豪華に見えるよねという部分になります。
1:1の範囲にゲームに必要なものを全て表示させることができれば赤斜線の部分をフルに画面に映せます。(UIは除く)
もし1:1の範囲に映せない場合、アスペクト比の最低値を決めておいて、それ以下では決めておいたアスペクト比に固定するやり方がいいかなと思います。
アスペクト比を固定すると↓のようになります。
画面にカメラが映せない部分が出てきてしまい、黒になってしまいます。
この場合Appleでリジェクトを食らうので何か帯のようなものを用意してあげる必要があります
黒の部分を何か適当な背景で埋めてしまおうという事です。
なので、なるべく1:1の範囲内に収めたいところではあります。
また、逆に画角が広すぎるものにも対応しないといけません。2:1が限界だろうと思っていたらそれ以上のものが出たので3:1くらいの画角に対応できればいいかなと思います。
スマホ業界はどれほど画角を広げるのか・・・これも安全をとって3:1にします。
↓ぐらい横が見えてしまっても問題無いような作りにする必要があります。
まとめると↓のような感じになります。
なるべく全解像度フルスクリーンを目指したいところではありますが、妥協が必要な場合は妥協しましょう。
ただし、無駄な機能を乗っける事で必要な画角が広がってしまうような仕様はそもそも組まない事が重要だと思います。
また、アスペクト比固定にしろ、違うにしろ、画面のセーフエリア外になりそうな部分に判定をつけるのは、やめましょう。(UIを除く)
セーフエリアを取得できない端末が存在するので、はっきり言ってiPhoneの為だけに対応することになります。それだけの為にセーフエリア用に画角を狭くして、さらにアスペクト比固定で画角を狭くしてはゲームとしてかなり見辛くなります。
判定がセーフエリア外にあっても、移動する事で判定を中央に持ってこれるなどできるならいいですが、必ずセーフエリア外に判定が存在するものはやめた方がいいです。
帯の部分が多いとちょっとゲームとして安っぽくなりますしね。
どうしても、セーフエリア外の部分をゲーム上でタップしたい場合はもういっそ処理をUIに逃してしまってもいいかもしれません。
必要の無い機能を省く事で、ワンランク上の解像度を目指せるのであれば、仕様を省くのも検討に入れる必要があるかと思います。
↓のスクリプトで最大、最小を指定してアスペクト比を固定化できます。最大と最小の値を同じにすればそのアスペクト比で固定化できます。
using UnityEngine; using System.Collections; public class AspectCamera : MonoBehaviour { public Vector2 maxAspect = new Vector2(); public Vector2 minAspect = new Vector2(); private Camera came; private void Awake() { came = GetComponent<Camera>(); //maxAspectとminAspectをどこかから読んでくる等してください //同じ値にするとアスペクト比は固定されます。 Vector2 targetAspect = GetTargetAspect();//未設定だったり、変更する必要がなければスルー
if (targetAspect != Vector2.zero)
{
Rect rect = CalcAspect(targetAspect);
came.rect = rect;
}
}
private Vector2 GetTargetAspect()
{
if (Screen.width > Screen.height)
{
float screenAspect = (float)Screen.width / (float)Screen.height;
if (screenAspect < minAspect.x / minAspect.y)
{
return minAspect;
}
else if (screenAspect > maxAspect.x / maxAspect.y)
{
return maxAspect;
}
}
else
{
float screenAspect = (float)Screen.height / (float)Screen.width;
if (screenAspect < minAspect.y / minAspect.x)
{
return minAspect;
}
else if (screenAspect > maxAspect.y / maxAspect.x)
{
return maxAspect;
}
}
//ここを通った場合調整の必要なしreturn Vector2.zero;
}
private Rect CalcAspect(Vector2 targetAspect)
{
float targetRate = targetAspect.x/ targetAspect.y;
float screenRate = (float)Screen.width / (float)Screen.height;
float scaleHeight = screenRate / targetRate;
Rect rect = new Rect(0.0f, 0.0f, 1.0f, 1.0f);
if (1.0f > scaleHeight)
{
rect.x = 0;
rect.y = (1.0f - scaleHeight) / 2.0f;
rect.width = 1.0f;
rect.height = scaleHeight;
}
else
{
float scale_width = 1.0f / scaleHeight;
rect.x = (1.0f - scale_width) / 2.0f;
rect.y = 0.0f;
rect.width = scale_width;
rect.height = 1.0f;
}
return rect;
}
}
ゲーム性によってアスペクト比をどのように適用するか考えよう
グラフィックの阻害になるような仕様は本当に必要か考えよう
UGUI対応
・通常のUIの場合
UGUIはiPhoneのセーフエリア対応がある以上、Screen Space – Cameraほぼ一択な気がします。
Screen Space – Cameraであれば、CameraのViewport RectにセーフエリアのRectを渡してあげれば一発で済むからです。
- CanvasはScreen Space – Camera
- Canvas ScalerはScale With Screen SizeでMatch Width or Heightを「UIが詰まりそうな方」に合わせる(後述する方法ならどっちでもいい)
- 各種UGUIのアンカーを画面端に持ってくる
Canvas ScalerはScale With Screen SizeでMatch Width or Heightを 「UIが詰まりそうな方」 に合わせるというのは縦に長く、縦幅が狭まると他の何かに重なりそうなUIを使用している場合、Height(つまり1にする)にして、横に長く、横幅が狭まると他の何かに重なりそうなUIを使用している場合Width(つまり0にする)とうまくいくと思います。
そして、UGUIを映しているカメラをセーフエリアに入れてあげる必要があるのですが、やり方が2つあります。
1つは普通にUGUIのカメラをセーフエリア内に入れてあげる方法。↓のスクリプトを使えばいけます。
Rect safeAreaRect = new Rect();
safeAreaRect.width = Screen.safeArea.width / Screen.width;
safeAreaRect.height = Screen.safeArea.height / Screen.height;
safeAreaRect.x = Screen.safeArea.x / Screen.width;
safeAreaRect.y = Screen.safeArea.y / Screen.height;
UGUIを映しているカメラ.rect = safeAreaRect;
ただしこの場合、アンカーがセーフエリアなので、もしアスペクト比を固定していた場合、UIがカメラが映していない部分に被る可能性があります。
つまり↓こうなります。
絵が雑なのは置いてもらって
カメラが映していない部分とカメラが映している部分にまたがってUIが存在する感じです。
黒帯が大きければUIが黒帯の中にすっぽり入って、まぁアリな見た目になるのですが、解像度によって黒帯の大きさは変わるので、↑のようにUIが半端な位置に来たりします。
これをよしとするかどうかです。
もし、これをよしとしない場合、↓を使うことでUGUIをゲーム画面に入れることができるようになります。
using System.Collections; using System.Collections.Generic; using UnityEngine; public class UGUICamera : MonoBehaviour { public Camera gameCamera; //ゲーム画面を映すカメラ public Camera uguiCamera; //UGUIを映すカメラ public CanvasScaler scaler; //キャンバススケイラー private Vector2 defaultRes;// Start is called before the first frame update
void Start()
{
Rect gameRect = gameCamera.rect;
Rect safeAreaRect = new Rect();
safeAreaRect.width = Screen.safeArea.width / Screen.width;
safeAreaRect.height = Screen.safeArea.height / Screen.height;
safeAreaRect.x = Screen.safeArea.x / Screen.width;
safeAreaRect.y = Screen.safeArea.y / Screen.height;
Rect uguiRect = new Rect();
uguiRect.width = (gameRect.width < safeAreaRect.width) ? gameRect.width : safeAreaRect.width;
uguiRect.height = (gameRect.height < safeAreaRect.height) ? gameRect.height : safeAreaRect.height;
uguiRect.x = (gameRect.x > safeAreaRect.x) ? gameRect.x : safeAreaRect.x;
uguiRect.y = (gameRect.y > safeAreaRect.y) ? gameRect.y : safeAreaRect.y;
uguiCamera.rect = uguiRect;
//UGUIの大きさをカメラの大きさに合わせる //いちいちdefaultResに入れているのはこのスクリプトをUpdateに書いた場合に移行しやすくする為です //適宜書き換えてください defaultRes = scaler.referenceResolution;float scaleX = defaultRes.x / (uguiRect.width * Screen.width);
float scaleY = defaultRes.y / (uguiRect.height * Screen.height);
scaler.referenceResolution = new Vector2(defaultRes.x * scaleX, defaultRes.y * scaleY);
//CanvasScalerはカメラに合わせないので、カメラが画面に近い方に合わせる scaler.matchWidthOrHeight = (uguiRect.width > uguiRect.height) ? 0 : 1;}
}
↑を使うことで、黒帯の中にUGUIが入ることはなくなりますが、表示領域が狭まる分、UIそのものが小さくなってしまうというデメリットもある為、どちらにするのかは選択といったところでしょうか
また、当たり前ですがゲーム内でタップしなければいけない場合、UIがそこに被ってしまうような設計にしてはダメです。セーフエリア内にUIが寄ってくるせいで範囲が狭まることを考慮に入れる必要があります。
・綺麗に見せたいグラフィックの場合
UGUIなんだけども、綺麗に見せたいグラフィックもあると思います。例えば2Dキャラクターの立ち絵なんかですね。
これはもう他のUIとは別にCanvasを作り、Constant Pixel Sizeにしてしまった方がいいかと思います。
Constant Pixel Sizeにした場合は大きさが画面に合わなくなってしまうので、解像度から計算して、Canvas ScalerのScale Factorをスクリプトから調節するようにしてください。
ただこの時注意しなければいけないのはピクセル数が固定になってしまうので、市場に出回っている端末をよく見て、はみ出したり、極端に小さくなりすぎないか調べる必要があると思います。
・通常のUIはScreen Space – Camera一択
・UIの大きさを担保するか、見栄えをとるか選択しよう
・綺麗なグラフィックはUIとは別Canvasにしよう(別CanvasはScale Factorで調整)
カメラ外対応について
さて、↑の対応で一つでもアスペクト比を固定する対応を行った場合、カメラが映さない部分ができてしまい、黒帯になってしまいます。
このままではAppleにリジェクトをくらってしまうので、対策をする必要があります。(Appleに出さない人はしなくていいです)
せっかくなので何か映えるモノを入れたい気持ちはわかりますが、如何せん解像度によってどこまでの範囲が見えるのかがわからないので、大人しく背景っぽいシームレス画像を用意するのが無難かと思います。
Spriteの設定を↓のようにします。
次にImageの設定を↓のようにします
そして、Canvasの設定をScreen Space – Cameraにして、帯用のカメラを用意して、デプスを一番奥にして設定してあげればOKとなります。
<まとめ>
さて、いかがだったでしょうか?
画面構成にゲームの仕様がかなり深く関わってくるので、何も考えずとりあえず作ろうというのはやめて、よーく考えてから作り始めた方がいいかもしれませんね。
UIについても、仕様によってUIの範囲が狭まってしまって押しにくくなったりすることもあると思います。
なるべくギュウギュウに押し込めるのではなく、ゆとりを持った画面作りをしましょう。
そして、特に誰も求めてはいないけど体裁的に置いておく機能や特に必要のない機能は、省いてしまうのがベストだと思います。