LoginSignup
41
22

More than 1 year has passed since last update.

UEのPCゲーム対応について

Last updated at Posted at 2022-12-18

PCゲーム対応とは

PCゲーム対応とは、PCで動くように対応することである。
(ネタが古い)
 ここで言うPC対応は基本的にはWindowsでプレイできるものという意味です。MacとかLinuxとかはとりあえず言及しません。
 UEのプロジェクトをWindowsにビルドすればPCビルドができあがるのですが、実際に製品クオリティのPCゲームをリリースするには意外と手間がかかります。そのあたりをちょっと解説していこうかなと言うのがこの記事の主旨です。
 PCでゲームをプレイすることが多い人はゲームによってPC対応が充実しているゲームもあれば物足りないゲームもあると思ってるのでは無いでしょうか。筆者はPCゲームに係わることも多かったので他社のゲームがどれくらい頑張って対応しているかがすごく気になってしまいます。

PCゲーム対応解説編

画面モード

 コンソールの場合は出力先が基本的にテレビなのと、出力先の解像度やアスペクト比、リフレッシュレートなどはコンソールのOSがよろしくやってくれることが多いので、そこまで気にしなくても良かったりしますが、PCの場合は画面モードがディスプレイによって千差万別で、なおかつユーザーが好みに合わせて変更できるという面倒さがあります。
 スマートフォンの場合もデバイスによって画面比率や解像度がまちまちで、PCに劣らず面倒だったりしますのでけっこう共通する部分もあります。
 PCの場合はどんなディスプレイでプレイされるかがまったくわからないので、どこまで対応するかは悩みどころなのですが、基本的には

  • 解像度
  • スクリーンモード
  • リフレッシュレート
  • アスペクト比
  • HDR

 といったものの対応が必用になります。

解像度

 ゲーム画面のピクセルサイズのことですね。一般的なHDディスプレイは1920x1080です。PCの場合は、無数のディスプレイ製品がありますが、変則的な解像度のものも多々あります。複数のディスプレイを繋げて単一のディスプレイとして扱う事も可能なのでWindowsで設定可能な解像度の組み合わせはほぼ無限と言えます。
 解像度が低すぎるとUIが表示しきれなかったり、解像度が高すぎると描画負荷が重すぎたりVRAMが不足するといった問題があります。最近は4Kディスプレイが当たり前になってきましたが、3840x2160とHDディスプレイの縦横2倍面積4倍の解像度になります。つまりは4倍のメモリと描画速度が必要になるので、非力なPCでゲームを4Kで動かすのはなかなかに厳しいものがあります。

スクリーンモード

 Windowsのゲームでは大抵スクリーンモードの選択があります。種類としては

  • フルスクリーン
  • ボーダレス
  • ウィンドウ

 の3種類が一般的です。UEでサポートしているのもこの3種類です。
 フルスクリーンは画面全体をゲームに使用します。ボーダレスはタイトルや枠がない、描画部分だけのウィンドウで、これを画面全体に広げて使用するのが一般的です。ウィンドウはディスプレイの中に移動可能なウィンドウを配置して、その中にゲームを描画します。

 ボーダレスのことをボーダレスフルスクリーンと呼ぶ場合もあります。同じ画面全体を覆うモードなのに違いがあるのかというと、フルスクリーンの場合は指定した解像度にディスプレイのモードを変更して全画面に描画するが、ボーダレスの場合はディスプレイの解像度は変更せずに画面全体を使用するという違いがあります。
 ボーダレスはわりと最近のゲームに入るようになった機能で昔のゲームにはありませんでした。GPUの性能がそれほど高く無く、VRAMも限られていた時代はゲームに描画リソースを集中させるために画面解像度をゲーム用に変更して他のアプリケーションを描画しないためにフルスクリーンにしていた経緯があり、「排他的フルスクリーン」なんて呼ぶ事もありました。
 しかし、ディスプレイの解像度を変更すると、ゲーム以外のウィンドウの位置が再配置されてしまったり、デスクトップアイコンの配置が変わってしまったりと煩雑な副作用があり、最近では嫌われる傾向にあります。なので、UEでもDirectx12ではフルスクリーンはボーダレスフルスクリーンとして扱われていたりします。

 また、DirectX11ではHDR描画はフルスクリーン時にしか設定できないという制約がありましたが、DirectX12ではウィンドウごとに設定できるようになったため、その意味でもフルスクリーンの必要性が下がったというのもあります。

リフレッシュレート

 ゲーム画面を更新する頻度です。30Hzとか60Hzとか表記されます。
 30fpsとか60fpsとか表記される場合もありますが厳密には同じものではありません。Hzはディスプレイがハードウェア的に画面を更新する周期、fpsはゲーム画面を更新する周期でとするのが一般的かと思います。つまりディスプレイのリフレッシュレートとゲームのリフレッシュレートがあります。
 ディスプレイの周期とゲームの周期を同期することで、滑らかに変化するゲーム画面を実現できます。ディスプレイの画面更新中にゲーム画面が更新されると画面の途中からズレて次のフレームが描画されて画面が割れた様に見えてしまいます。これをティアリングと呼びます。これを避けるためには、ディスプレイの垂直同期(VSync)を待ってからゲーム画面を更新するのですが、この待つという動作がゲームのfpsを低下させたり入力の遅延を発生させたりするので、それを避けるために描画バッファを3枚にするトリプルバッファや、ディスプレイが対応していればFreesyncなどの技術を使うこともあります。この辺の話は脱線気味になるので各自調べてください。
 例えば144HzのディスプレイでVSync同期に設定したゲームが60fpsで画面を更新した場合、1/144秒ごとにゲームの画面更新の準備ができているか確認して、できていれば更新という流れになり更新間隔が一定にならなかったりします。こういった問題もあるのでPCの場合は可変フレームレートで設計し、同期方法やフレームレートはプレイヤーの設定に委ねることが多いです。PCの性能とディスプレイの仕様の組み合わせになるので開発側で最適な状態を選べないというのが理由です。

アスペクト比

 アスペクト比はディスプレイの縦横比です。一般的なディスプレイの場合は16:9が多いのですが、アーティスト向けのディスプレイなどは縦が少し広く16:10だったり、最近はウルトラワイドと言われる21:9とか32:9といった横が広いディスプレイが流行りだったりします。
 FPSなどのシューター系のゲームの場合横が広いイコール視界が広いのでプレイが有利になるというのと、実際横が広いとゲームの迫力が増すようにも感じられるので、ゲーマーに好まれる傾向があります。コンソールゲームの場合は16:9だけ対応すれば良いのですが、複数のアスペクト比に対応するときは、画角がアスペクト比で変わったり、UIの位置が変わったり、ムービーを全画面で流せないなどの問題があります。対応をあきらめてレターボックス対応で16:9以外の領域を黒で塗りつぶしてしまうゲームも多いです。が、UEのレターボックスはあくまでもゲーム画面のみの対応で、UMGによるUIはレターボックス関係なく画面の端からのアンカー処理になってしまうという問題があります。

 21:9と記載されているディスプレイですが、3440x1440といった実際には21.5:9というか43:18の製品が多かったりもします。

 最近の欧米のアクションゲームではウルトラワイド対応がほぼ必須といっても過言では無い状況になっています。対応していないとユーザーのウケが悪いのでなるべく対応したいところですが、これがなかなかに大変なので、その話は後ほど。

HDR

 HDRについてはこの記事では詳しく触れません。長くなるし僕自身まだまだ勉強不足で(HDR警察怖いし)。UEのHDRに関しては過去の記事
UE4のHDR対応はどうなってるの?Win10編
UE4のHDR対応その後
や、UNREAL FEST WEST'22の講演でも触れているのでそちらをごらんください。
 Windows PCでのHDR対応は前述の理由もあり、DirectX12での対応をおすすめします。
 UE5のHDR対応はこれから充実していくという話も聞いていますので、期待したいところです。

スケーラビリティ

 性能が決まっているコンソールと違い、PCの場合はスペックも千差万別です。PCによって、CPU、GPU、搭載メモリ、ストレージなどが違うのでどこまで対応するかを考慮する必用があります。PCゲームの場合はたいていのゲームでは最低スペックと推奨スペックのふたつのスペックが提示されています。最低スペックは文字通りそのゲームが遊べる最低限のスペック。推奨スペックはそのゲームが快適に遊べると想定するスペックですが、あくまでも目安だったりします。
 Windowsの場合同時に開かれているアプリケーションや選択している画面モード、インストールされているウィルス対策ソフトといった要因で想定よりも性能が出ない、使えるメモリが少ないといったこともよくあるので、スペックを満たしているからといって快適に遊べるとは限らなかったりします。
 特にパフォーマンスに影響を及ぼすのがグラフィックスなので、多くのPCゲームにはグラフィックスの細かい設定ができるようになっています。コンソールゲームの場合は性能が一定なのでそういった設定は無い場合が殆どです。ゲームによっては描画重視かフレームレート重視かの選択ができるものもありますが。
 まあ、ある程度描画設定を下げるとゲームの描画は軽くなり、低いスペックのPCでも遊べるようになるのですが、闇雲に下げてしまうと影が全くなくなったり、本来フォグで隠したかったものが見えてしまったりと様々な問題が起きたりもするのでその辺の調整が必用です。

セーブデータ

 Windowsゲームで問題になりがちなのはセーブデータの保存に関することです。セーブデータをどこに保存するかを考慮する必用があります。これにはWindowsの作法が関係してきます。
 昔のゲームは自分のプログラムと同じフォルダにセーブデータを保存することが多かったのですが、WindowsのProgram Filesフォルダに書き込むには管理者権限が必用だったり、同じPCを複数のユーザーが使用する場合にセーブデータが上書きされてしまうといった問題があり、現在ではユーザーごとのドキュメントフォルダやアプリケーションデータフォルダに保存することが一般的です。ユーザーフォルダを取得するWindows APIがあるので、それらを使用して保存先のフォルダを決めるのがよいです。
 もうひとつの問題は、今どきのゲームはクラウドによるセーブデータの共有が当たり前です。ゲーム進行にかかわるデータはそれで良いのですが、PCごとのスケーラビリティ設定や画面モード設定などを共有してしまうと、全く別のスペックのPCで同じ設定が使用されてしまい不都合です。場合によってはゲームが起動不能になることさえあります。高スペックや高解像度ディスプレイで保存した設定で低スペックPCや低解像度ディスプレイのPCで起動しようとすると性能を超えた設定で起動しようとしてクラッシュしたりといった事が起きます。なので、PC固有の設定に関するセーブデータはゲーム進行とは別のファイルに保存し、クラウド同期の対象外にすることが必用になります。

コントローラー対応・マウス・キーボード対応

 UEは基本的にXInputコントローラーにしか対応していません。PS4コントローラーなどのDirect Inputコントローラーに対応するには別途プラグインを入れたり自分で実装したりする必要があるので注意が必要です。またPCゲームユーザーはアクションゲームであってもマウスとキーボードでのプレイを好むので、その対応もけっこう面倒です。特にキーボードは国によってキー配置が違うので、「移動はWASD」という常識が必ずしも通用しません。画面のボタン表示がキーボードを押すとキーボード仕様に、XBOXパッドならXBOX仕様にみたいな事をちゃんとやろうとするとなかなかな手間です。

難読化・チート対策

 PCゲームの場合、ゲームの関連ファイルが全てユーザーの目につくところにあります。なので解析したり改造したり、オンラインゲームの場合は不正行為を防ぐために様々な手段を講じる場合があります。
 本格的な対策には市販のミドルウェアなどを導入することが多いのでその辺は詳しく語りませんが、UEの場合はEpic Gamesが傘下に収めたEasy Anti-Cheatなどが有名です。
 そういったものを使用せずにUEでできる難読化としては、パッケージの暗号化やiniファイルのバイナリ化などがあります。この辺は簡単に設定できるので公式ドキュメントを御覧ください。
 その他にもゲーム内のムービーを簡単に見られたくない場合はムービーファイルをパッケージ内に入れる方法などがあります。
 また、デフォルトの状態では一部設定がiniファイルに書き出されてしまうため、そこも隠蔽したい場合は設定したりエンジンを修正したりしてiniファイルへの出力を抑制したりもします。

実行ファイルに関するもの

 たいていのゲームでは実行ファイルのアイコンを設定しますが、アイコンファイルにはicoフォーマットの特殊なファイルを指定します。フリーソフトなどでpngやbmpから変換できますので、そういったツールを使うのが良いでしょう。
 また、実行ファイルにデジタル署名をしておかないと、安全な実行ファイルとみなされないため、実行時にWindowsが警告を出す場合があります。Steamで配信する場合はSteam側でDRM処理と同時に署名もしてくれているようですので特に必用は無さそうです。デジタル署名は法人でなくても取得設定することが可能なのでその辺は検索してみてください。

解説編まとめ

 このようにPCゲーム対応は意外と面倒なことが多いです。コンソールゲームの開発に携わった経験があるひとは「いやいや、コンソールゲームのレギュレーション対応に比べれば楽なもんじゃん」と言うかもしれません。それはまあその通りなのですが、PCゲーム対応にはまた違った苦労があるので甘く見ているとけっこうしんどい思いをするかもしれません。
 コンソールの場合は各ベンダーがどの様に対応すれば良いか細かい資料を提供してくれています。非常に項目が多く対応は大変なのですが、逆に言えばその通りにしておけば大丈夫という安心があります。PCの場合はそういったものが全く無く、Windowsの作法や世間一般の暗黙の常識といったものを考慮して対応する必用があります。
 ではどのように対応すれば良いかというと、Windowsゲームをいっぱい買っていっぱい遊ぶのが一番だと思っています。僕自身、職業柄UEで制作されたゲームのPC版はかなりの数を購入してどういう対応しているかを調査して参考にしたりしています。

PCゲーム対応実践編

 それでは実際にどういう対応をするか紹介していこうと思います。
 UEのバージョンは5.1、DirectX12でビルドした場合として解説します。

画面モード関連

起動時の解像度と画面モード

 まず画面モードですが、PCビルドの場合初回起動時はプライマリディスプレイ(マルチディスプレイの場合はメインに指定されているディスプレイ)の現在の解像度でボーダレスフルスクリーンで起動します。
 ここで注意が必用なのは例えば8Kディスプレイが接続されている場合はいきなり8Kで起動してしまいます。その結果解像度設定画面にたどり着くまえにクラッシュしてゲームプレイ不可能なんて事も有り得ます。
 まずは実証しましょう。ThirdPersonShootingのプロジェクトをビルドしてパッケージを作成して実行します。ディスプレイは4Kです。結果は。
image.png
「4Kじゃないやん、嘘つき!」
 はい、すいません。これは画面設定で「テキスト、アプリ、その他の項目のサイズを変更する」を150%にしているのが理由です。この項目をDPIスケールと呼称します。
image.png

 起動時の解像度設定は最大画面サイズで、DPIスケールを考慮したものが設定されるということになります。DPIスケールを125%にすると
image.png
 このようになります。

 デフォルトの設定では文字サイズでスケーリングされることを考慮した解像度になります。この場合は2160を150%で割った1440になっています。wfはWindowedFullscreenの略で先程述べたボーダレスフルスクリーンのことです。
 文字サイズに関係無く実際の解像度で描画したい場合はプロジェクト設定のエンジン - ユーザーインターフェース - ゲームモードでの高DPIを許可をチェックします。
image.png
 これをチェックした状態で解像度を設定するとDPIスケールを無視した解像度に設定が可能になります。
 チェックした状態でビルドして実行してみます。
image.png

 あれ?変わりませんね?
 しかし、RenderDocでキャプチャしてみると
image.png
 フル4Kで描画されています。この辺の挙動がちょっと不可解ですが、r.setresなどで改めて設定した解像度はちゃんとDPIスケールを無視して設定されるみたいです。

 さて、起動時の解像度はどのように設定されるのでしょうか。

UnrealEngine.cpp
static FAutoConsoleVariable CVarSystemResolution(
	TEXT("r.SetRes"),
	TEXT("1280x720w"),
	TEXT("Set the display resolution for the current game view. Has no effect in the editor.\n")
	TEXT("e.g. 1280x720w for windowed\n")
	TEXT("     1920x1080f for fullscreen\n")
	TEXT("     1920x1080wf for windowed fullscreen\n")
);

r.SetResの初期値は1280x720wです。
どこで初期起動時の画面サイズが設定されるかというと

まずデフォルトのウィンドウモードは

GameUserSettings.cpp
EWindowMode::Type UGameUserSettings::GetDefaultWindowMode()
{
	// WindowedFullscreen should be the general default for games
	return EWindowMode::WindowedFullscreen;
}

でWindowedFullscreenが標準として決められています。

そして

GaneEngine.cpp
void UGameEngine::DetermineGameWindowResolution( int32& ResolutionX, int32& ResolutionY, EWindowMode::Type& WindowMode, bool bUseWorkAreaForWindowed )

で、コマンドラインで解像度が指定されていなければデスクトップサイズが採用されます。
実際のコードは

GameEngine.cpp
	// Find the maximum allowed resolution
	// Use PrimaryDisplayWidth/Height in windowed mode
	int32 MaxResolutionX = bUseWorkAreaForWindowed && WindowMode == EWindowMode::Windowed ? DisplayMetrics.PrimaryDisplayWorkAreaRect.Right - DisplayMetrics.PrimaryDisplayWorkAreaRect.Left : DisplayMetrics.PrimaryDisplayWidth;
	int32 MaxResolutionY = bUseWorkAreaForWindowed && WindowMode == EWindowMode::Windowed ? DisplayMetrics.PrimaryDisplayWorkAreaRect.Bottom - DisplayMetrics.PrimaryDisplayWorkAreaRect.Top : DisplayMetrics.PrimaryDisplayHeight;

 といった感じです。
 デフォルトのWindowedFullscreenの場合はDisplayMetrics.PrimaryDisplayWidth,DisplayMetrics.PrimaryDisplayHeightが解像度として採用されます。これはDPIスケールを加味した数値なので、初期起動時の解像度、ウィンドウモードはプライマリディスプレイのDPIスケールを適用した解像度のWindowedFullscreenになるというわけです。

 初期起動時の解像度をコントロールしたい場合はコマンドラインで指定するしかなさそうです。それはそれでちょっと困ります。実は初期解像度を設定する方法がもうひとつあります。それは、Saved/Config/Windows/GameUserSettings.iniに設定するという方法です。しかし、これも本来は初回起動時に生成されるファイルなのであまりいいやり方では無いですね。セーブデータを消したときに消えてしまいますし。
 僕の場合はこの辺はもう仕方ないのでエンジンに手を入れて初回起動時の解像度を低めにしておいたりという対応をしています。いきなり8Kで起動されたりすると困るので。
 つまり、まずは低い解像度で起動、セーブデータがあればセーブデータに保存された解像度を再設定という流れにしています。エンジン改造が嫌なら、起動時のコマンドラインが必ず設定されるようにして、'-Res=1280x720w' とかを設定しておくのが良いでしょうか。
 まあ、そんなわけでUEのPCでの初期解像度設定にはちょっと不満があります。DefaultGame.iniとかで設定できても良さそうに思います。

 さて、初期起動時の解像度設定に関してはおおよそ判明しましたが、2回め以降の起動時はどうなるのでしょうか。これは先程書いたGameUserrSettings.iniに保存された値が使用されます。実は主にPCでの画面などの設定情報はGameUserSettings.cpp/hで管理されています。クラスで言うとUGameUserSettingsです。このクラスのプロパティがシリアライズされてGameUserSettings.iniに出力されて、次回起動時に読み込まれて設定されます。
 なので、初期解像度を明示的に指定するようにエンジンを改造するなら
 エンジンを改造して対応するなら

GameUserSettings.cpp
	/** Loads the resolution settings before is object is available */
	static void PreloadResolutionSettings(bool bAllowCmdLineOverrides = true);

 の中でセーブデータがなければ初期解像度を指定してやるようにすれば良いかと思います。
 GameUserSettingsはユーザークラスにオーバーライドできるのですが、この関数はstaticで直接呼ばれてしまうのでオーバーライドで解決できないのが残念なところ。

解像度変更をトラックしたい

 そんな要望のために OnSystemResolutionChangedというdelegateが用意されています。

	Handle_OnResolutionChanged = FCoreDelegates::OnSystemResolutionChanged.AddStatic(&MyClass::OnResolutionChanged);

 といった感じで設定しておくと解像度変更が実行されたら呼び出してくれます。
 PC版で解像度ごとにスクリーンパーセンテージを設定したい場合などに便利です。

フルスクリーンモードの切り替え

 Windowsのアプリの多くはALT+ENTERのキー入力でWindowsモードとフルスクリーンモードの切り替えができます。F11も同様の機能が割り当てられてる事もあります。UEでもこの両方のキーに対応しています。
 便利ではあるのですが、ゲームアプリではホットキーでの画面切り替えをして欲しくないと考えるひとも居るかと思います。僕も画面切り替えはオプション画面に集約したいのでこの機能を邪魔に思っています。
 ホットキーの機能を切りたい場合はここで設定できます。
image.png
ウィンドウモード時の最大化ボタンやマウスでドラッグしてのリサイズも無効にできます。
image.png

GameUserSettings

 ここで何度か出てきたGameUserSettingsの話をします。
 コンソールの実装ではそれほど気にすることはないのですが、UEの画面関係、スケーラビリティ関連は概ねこのUGameUserSetignsクラスで管理されています。PC版の対応の多くはこのクラスを直接使用するか、自前のクラスにオーバーライドして使うのが良いと思います。
 オーバーライドする際には、UGameUserSettingsの継承クラスを作成し、DefaultEngine.iniに設定します。

DefaultEngine.ini
[/Script/Engine.Engine]
GameUserSettingsClassName=/Script/MyGameSettings.MyGameUserSettings

 画面解像度やモードの変更、垂直同期、デスクトップサイズの取得など様々な機能が用意されているので、基本的なことはほとんどこのクラスを経由して行えます。殆どの機能はBlueprintからも呼び出せます。
 多彩な機能が揃っているので、まずはこのクラスのコードにひととおり目を通し、足りない機能を継承クラスに追加するのがおすすめです。
 例を上げると、このクラスのプロパティをiniファイルに保存する処理を無効にしたり、独自ファイルに保存したい場合はSaveSettingsをオーバーライドしてやれば良いでしょう。
 トピックとしてGameUserSettingsの面白い機能を紹介します。RunHardwareBenchmarkという関数があるのですが、これを呼ぶと、UEのシステムがハードウェアの簡単なベンチマークを取って結果を取得することができます。この関数はBPからも呼び出せますが、結果を直接参照するにはC++コードが必要になります。実行結果はこんな感じです。
image.png
PCごとにグラフィックス設定などを自動設定する目安として使用できます。この結果を一括設定する関数も用意されています。

Scalability.cpp
void SaveState(const FString& IniName)
{
	check(!IniName.IsEmpty());

	// Save the "real" settings if in a temporary state
	FQualityLevels State = GScalabilityUsingTemporaryQualityLevels ? GScalabilityBackupQualityLevels : GetQualityLevels();

	const TCHAR* Section = TEXT("ScalabilityGroups");

	// looks like cvars but here we just use the name for the ini
	GConfig->SetFloat(Section, TEXT("sg.ResolutionQuality"), State.ResolutionQuality, IniName);
	GConfig->SetInt(Section, TEXT("sg.ViewDistanceQuality"), State.ViewDistanceQuality, IniName);
	GConfig->SetInt(Section, TEXT("sg.AntiAliasingQuality"), State.AntiAliasingQuality, IniName);
	GConfig->SetInt(Section, TEXT("sg.ShadowQuality"), State.ShadowQuality, IniName);
	GConfig->SetInt(Section, TEXT("sg.GlobalIlluminationQuality"), State.GlobalIlluminationQuality, IniName);
	GConfig->SetInt(Section, TEXT("sg.ReflectionQuality"), State.ReflectionQuality, IniName);
	GConfig->SetInt(Section, TEXT("sg.PostProcessQuality"), State.PostProcessQuality, IniName);
	GConfig->SetInt(Section, TEXT("sg.TextureQuality"), State.TextureQuality, IniName);
	GConfig->SetInt(Section, TEXT("sg.EffectsQuality"), State.EffectsQuality, IniName);
	GConfig->SetInt(Section, TEXT("sg.FoliageQuality"), State.FoliageQuality, IniName);
	GConfig->SetInt(Section, TEXT("sg.ShadingQuality"), State.ShadingQuality, IniName);
}

 自作ゲームで使用する場合はここもオーバーライドしてカスタマイズした方が良いかもしれません。
 或いはScalability.iniをカスタマイズしても良いかと思います。

CPUBenchmarkResultとGPUBenchmarkResultsはCPUやGPUのベンチマークスコアです。筆者のPCは
CPU : Ryzen 9 5950X
GPU : Geforce RTX 3070 Ti
です。

 GameUserSettingsは実に多機能で、最近はHDR関連も追加されたりしているのでよく見ておくと良いです。

スケーラビリティ

 GameUserSettingsから派生してスケーラビリティの話をします。
 UEのスケーラビリティに関しては、基本的にコンソール変数制御で、Engine/Config/BaseScalability.iniに記述されています。これをそのまま適用しても良いのですが、カテゴリの分け方や変更してほしくない項目などをプロジェクトごとにカスタマイズしたほうが良いと考えています。
 例を上げるとTextureQualityでテクスチャのフィルタなどは変更して欲しいが、プールサイズは一定にしておきたいとか、EffectsQualityでSkyaAtomosphereを変更しないで欲しいし、質は下げてもEmitterSpawnRateScaleを下げられるとスカスカになっちゃうから嫌だとか。内容をよく見て不要な項目を削ったり、定義されていないものを追加したり、数値を調整したりとカスタマイズするのが良いでしょう。

 ところでこのカテゴリごとに複数のコンソール変数を設定する仕組み、とても便利ですよね。この仕組を独自に使用する方法があります。

ConfigUtilities.h
namespace UE::ConfigUtilities
{
	/**
	 * Helper function to read the contents of an ini file and a specified group of cvar parameters, where sections in the ini file are marked [InName]
	 * @param InSectionBaseName - The base name of the section to apply cvars from
	 * @param InIniFilename - The ini filename
	 * @param SetBy anything in ECVF_LastSetMask e.g. ECVF_SetByScalability
	 */
	CORE_API void ApplyCVarSettingsFromIni(const TCHAR* InSectionBaseName, const TCHAR* InIniFilename, uint32 SetBy, bool bAllowCheating=false);

 この関数を呼び出せば任意のiniファイルに記述したセクションのコンソール変数をまとめて設定できます。
 筆者の場合はゲームプレイ中、カットシーン中、ポーズメニュー中などでシチュエーションごとにコンソール変数をセットしたい場合などに使用しています。
 具体例としては、ボリュメトリックフォグのクオリティを変更したい場合に、DefaultGame.iniに

DefaultGame.ini
[VFogQuality@0]
r.VolumetricFog=0
r.VolumetricFog.GridPixelSize=16
r.VolumetricFog.GridSizeZ=48
r.VolumetricFog.HistoryMissSupersampleCount=4

[VFogQuality@1]
r.VolumetricFog=1
r.VolumetricFog.GridPixelSize=16
r.VolumetricFog.GridSizeZ=64
r.VolumetricFog.HistoryMissSupersampleCount=4

[VFogQuality@2]
r.VolumetricFog=1
r.VolumetricFog.GridPixelSize=8
r.VolumetricFog.GridSizeZ=128
r.VolumetricFog.HistoryMissSupersampleCount=8

と記述しておき

	ApplyCVarSettingsFromIni(*FString::Printf(TEXT("VFogQuality@%d"), level), *GGameIni, ECVF_SetByCode);

と呼び出せば、levelに応じた設定を一括で行うことができます。

コードからコンソール変数を変更する際に指定するECVF_SetByCodeのようなENUM変数はコンソール変数のプライオリティとして使用されるのに注意してください。より高いプライオリティで変更すると、低いプライオリティでのリクエストを無視する仕様になっています。
ECVF_SetByConsoleが最も高いプライオリティなので、コンソールから変更された変数はコードから変更できなくなったりします。

ワイド画面対応

 思ったより記事が長くなったのでワイド画面対応はさらっと。
 PC版で需要が高まっているワイド画面対応です。単にワイド画面で表示してみるだけなら、コンソールから
r.setres 2150x900w
 とか入力するととりあえずはワイド表示になります。
1600x900の場合と2150x900を並べてみました。
2022-12-07_18h51_05.png
 何が問題かわかりますか?
 そうです。ワイド画面にするとキャラが大きく表示されてしまいます。本来やりたいのはキャラのサイズはそのままで左右の視界を広くしたいので、これでは期待したものと違うし、UIがキャラにかぶったりする懸念もあります。
 これはUEの画角が縦では無く横を基準に計算されているからだと思います。これを縦を基準に変更すれば良さそうです。どうやって変更すればよいかというと

DefaultEngine.ini
[/Script/Engine.LocalPlayer]
;AspectRatioAxisConstraint=AspectRatio_MaintainXFOV
AspectRatioAxisConstraint=AspectRatio_MaintainYFOV

これでFOVが縦優先になります。
yfov.png
縦の比率はそのまま横が広がりました。

処理的にはこの辺。

CameraStackType.cpp
void FMinimalViewInfo::CalculateProjectionMatrixGivenView(const FMinimalViewInfo& ViewInfo, TEnumAsByte<enum EAspectRatioAxisConstraint> AspectRatioAxisConstraint, FViewport* Viewport, FSceneViewProjectionData& InOutProjectionData)
//
// 中略
//
		// If x is bigger, and we're respecting x or major axis, AND mobile isn't forcing us to be Y axis aligned
		const bool bMaintainXFOV = 
			((SizeX > SizeY) && (AspectRatioAxisConstraint == AspectRatio_MajorAxisFOV)) || 
			(AspectRatioAxisConstraint == AspectRatio_MaintainXFOV) || 
			(ViewInfo.ProjectionMode == ECameraProjectionMode::Orthographic);
		if (bMaintainXFOV)
		{
			// If the viewport is wider than it is tall
			XAxisMultiplier = 1.0f;
			YAxisMultiplier = SizeX / (float)SizeY;
		}
		else
		{
			// If the viewport is taller than it is wide
			XAxisMultiplier = SizeY / (float)SizeX;
			YAxisMultiplier = 1.0f;
		}

 ワイド画面で考えるべきことはいくつかあります。

  • 横が広くなった結果、見えてはいけないものが見えるようになったりしないか
  • ムービー再生の場合、左右足りない部分は黒塗りになるけど問題ないか
  • UIのアンカー位置が横が広がっても大丈夫なつくりになっているか
  • メニューなども横に広げるか、中央の領域だけで表示するか

 などなど。

 カットシーンでカメラ外の出待ちキャラが横に広げると見えてしまったり、背景が画面端までつくられてなかったり、思わぬ問題が起きたリするのでワイド対応するなら早めに対応して確認が大事です。

PC対応まとめ

 他にもレターボックスの話やDLSSやFSRを組み込む話など色々ありますが、思ったより長くなったので今回はこのへんで。
 「エディタで動いてるからPC対応なんてすぐやん」とか思っていると意外と時間を取られたりするので、PC対応も計画的に。
 多くの機能がエンジン側で用意されているにも関わらず知らないでいる事も多いので、結局はソースを見て調べるのが一番です。「こんな機能無いのかな」と思った機能はけっこうすでに実装されていたりするものです。

ということで2022年のアドベントカレンダーの記事でした。今年も無事参加できました。

41
22
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
41
22