大画面のサイズ変更をサポートする

スマートフォンからさまざまな大画面フォーム ファクタに拡大する場合、ゲームがウィンドウ管理をどのように処理するかについて考慮する必要があります。ChromeOSPC 版 Google Play Games では、ゲームをメインのデスクトップ インターフェースでウィンドウ モードで実行できます。Android 12L(API レベル 32)以降を搭載し、画面幅が 600 dp を超える新しい Android タブレットと折りたたみ式デバイスでは、ゲームを他のアプリと並べて分割画面モードで実行したり、サイズを変更したり、折りたたみ式デバイスの内側と外側のディスプレイ間で移動したりできます。これにより、ウィンドウ サイズと、一部のデバイスでは向きの構成が変更されます。

Unity ゲームでのサイズ変更

大画面の基本的な構成

ゲームがサイズ変更に対応できるかどうかを宣言します。

<android:resizeableActivity="true" or "false" />

サイズ変更をサポートできない場合は、ゲーム マニフェストでサポートされる最小アスペクト比と最大アスペクト比を明示的に定義してください。

<!-- Render full screen between 3:2 and 21:9 aspect ratio -->
<!-- Let the platform letterbox otherwise -->
<activity android:minAspectRatio="1.5">
<activity android:maxAspectRatio="2.33">

PC 版 Google Play Games

PC 版 Google Play Games では、指定されたアスペクト比を維持しながら、ウィンドウのサイズ変更が可能です。ウィンドウのサイズは、最適なサイズに自動的にロックされます。メインの向きが横向きの場合は 16:9 以上のアスペクト比をサポートする必要があります。ゲームが縦向きモードの場合は 9:16 のアスペクト比をサポートする必要があります。最適なエクスペリエンスを実現するには、横向きのゲームで 21:9、16:10、3:2 のアスペクト比を明示的にサポートしてください。ウィンドウのサイズ変更は必須ではありませんが、他のフォーム ファクタとの互換性を確保するために用意しておくことをおすすめします。

詳細とベスト プラクティスについては、PC 版 Google Play Games のグラフィックを設定するをご覧ください。

ChromeOS と Android の大画面

ChromeOS と大画面の Android デバイスでゲームの全画面表示の表示領域を最大化するには、全画面没入モードをサポートし、decorView またはシステム UI の可視性にフラグを設定するか、WindowInsetsCompat API を使用してシステムバーを非表示にします。また、ChromeOS デバイスで回転とサイズ変更の構成イベントを適切に処理するか、発生しないようにする必要があります。

大画面の Android デバイスでは、まだ対応していない構成でゲームが実行される可能性があります。ゲームがすべてのウィンドウ サイズと画面の向きの構成をサポートしていない場合、プラットフォームは互換モードでゲームをレターボックス表示し、必要に応じて、サポートされていない構成に変更する前にプレーヤーにプロンプトを表示します。

図 1. 構成の互換性ダイアログ。

一部のデバイスでは、サポートされていない構成に移動すると、ゲームを再読み込みして新しいウィンドウ レイアウトに最適なアクティビティを再作成するオプションが表示され、ゲームプレイが中断されることがあります。さまざまなマルチウィンドウ モードの設定(2/3、1/2、1/3 のウィンドウ サイズ)でゲームをテストし、ゲームプレイや UI 要素が切り捨てられたり、アクセスできなくなったりしていないことを確認します。また、折りたたみ式デバイスのインナー ディスプレイとアウター ディスプレイを切り替えるときに、ゲームが折りたたみ式デバイスの継続性にどのように対応するかをテストします。問題が発生した場合は、これらの構成イベントを明示的に処理し、大画面の高度なサイズ変更サポートを追加します。

大画面の高度なサイズ変更

図 2.パソコンと折りたたみ式デバイスのテーブルトップ形状では UI が異なります。

互換モードを終了してアクティビティの再作成を回避するには、次の操作を行います。

  1. メイン アクティビティをサイズ変更可能として宣言します。

    <android:resizeableActivity="true" />
    
  2. ゲーム マニフェストの <activity> 要素の android:configChanges 属性で「orientation」、「screenSize」、「smallestScreenSize」、「screenLayout」、「density」の明示的なサポートを宣言して、すべての大画面構成イベントを受信します。

    <android:configChanges="screenSize | smallestScreenSize | screenLayout | orientation | keyboard |
                            keyboardHidden | density" />
    
  3. onConfigurationChanged() をオーバーライドし、現在の画面の向き、ウィンドウ サイズ、幅、高さなどの構成イベントを処理します。

    Kotlin

    override fun onConfigurationChanged(newConfig: Configuration) {
       super.onConfigurationChanged(newConfig)
       val density: Float = resources.displayMetrics.density
       val newScreenWidthPixels =
    (newConfig.screenWidthDp * density).toInt()
       val newScreenHeightPixels =
    (newConfig.screenHeightDp * density).toInt()
    
       // Configuration.ORIENTATION_PORTRAIT or ORIENTATION_LANDSCAPE
       val newScreenOrientation: Int = newConfig.orientation
    
       // ROTATION_0, ROTATION_90, ROTATION_180, or ROTATION_270
       val newScreenRotation: Int =
    windowManager.defaultDisplay.rotation
    }

    Java

    @Override
    public void onConfigurationChanged(Configuration newConfig) {
       super.onConfigurationChanged(newConfig);
       float density = getResources().getDisplayMetrics().density;
       int newScreenWidthPixels = (int) (newConfig.screenWidthDp * density);
       int newScreenHeightPixels = (int) (newConfig.screenHeightDp * density);
    
       // Configuration.ORIENTATION_PORTRAIT or ORIENTATION_LANDSCAPE
       int newScreenOrientation = newConfig.orientation;
    
       // ROTATION_0, ROTATION_90, ROTATION_180, or ROTATION_270
       int newScreenRotation = getWindowManager().getDefaultDisplay()
               .getRotation();
    }

WindowManager をクエリして、デバイスの現在の回転を確認することもできます。このメタデータを使用して、新しいウィンドウのサイズを確認し、ウィンドウのサイズいっぱいにレンダリングします。アスペクト比が異なるため、この方法がすべてのケースで機能するとは限りません。その場合は、ゲーム UI を新しいウィンドウ サイズに固定し、コア ゲームプレイ コンテンツをレターボックス表示します。どちらのアプローチも技術的または設計上の制限により行えない場合は、独自のエンジン内レターボックスを使用してアスペクト比を維持し、resizeableActivity = false を宣言して構成モードを回避しながら、可能な限り最適なサイズにスケーリングします。

どちらの方法を選択する場合でも、さまざまな構成でゲームをテストします(折りたたみと開き、さまざまな回転の変更、分割画面モード)。ゲーム内の UI 要素が切り捨てられたり重なったりしていないか、タップ対象のアクセス性に問題がないか、アスペクト比の問題でゲームが引き伸ばされたり、圧縮されたり、歪んだりしていないかを確認します。

また、画面が大きいほどピクセルも大きくなります。これは、同じ数のピクセルがはるかに広い領域に配置されるためです。これにより、サイズが小さくなったレンダリング バッファや解像度の低いアセットがモザイク状になる可能性があります。大画面デバイスで高品質のアセットを使用し、ゲームのパフォーマンス プロファイルを作成して問題がないことを確認します。ゲームで複数の品質レベルをサポートしている場合は、大画面デバイスに対応していることを確認してください。

マルチ ウィンドウ モード

マルチ ウィンドウ モードでは、複数のアプリで同じ画面を同時に使用できます。マルチウィンドウ モードはアクティビティのライフサイクルを変更しませんが、複数ウィンドウ内でのアプリの再開状態は Android のバージョンによって異なります(マルチウィンドウ モードをサポートするマルチ ウィンドウ モードでのアクティビティのライフサイクルをご覧ください)。

プレーヤーがアプリまたはゲームをマルチウィンドウ モードに切り替えると、大画面のサイズ変更の詳細で説明されているように、構成の変更がアクティビティに通知されます。構成の変更は、プレーヤーがゲームのサイズを変更したときや、ゲームを全画面モードに戻したときにも発生します。

マルチ ウィンドウ モードに切り替えたときに、アプリがフォーカスを再取得する保証はありません。そのため、アプリの状態イベントを使用してゲームを一時停止する場合は、フォーカス取得イベント(フォーカス値が true の onWindowFocusChanged())に依存してゲームを再開しないでください。代わりに、onConfigurationChanged()onResume() などの他のイベント ハンドラまたは状態変化ハンドラを使用してください。現在のアクティビティがマルチウィンドウ モードで実行されているかどうかは、いつでも isInMultiWindowMode() メソッドを使用して検出できます。

ChromeOS のマルチウィンドウ モードでは、初期ウィンドウのサイズが重要な考慮事項になります。ゲームを全画面表示する必要はありません。その場合は、ウィンドウのサイズを宣言する必要があります。これに対処するには、次の 2 つの方法が推奨されます。

最初の方法では、Android マニフェストの <layout> タグに特定の属性を使用します。defaultHeight 属性と defaultWidth 属性は、初期サイズを制御します。また、minHeight 属性と minWidth 属性にも注意し、プレーヤーがゲーム ウィンドウのサイズをサポートしていないサイズに変更できないようにします。最後に、gravity 属性があります。これは、起動時にウィンドウを画面上のどこに表示するかを決定します。これらの属性を使用したレイアウト タグの例を次に示します。

<layout android:defaultHeight="500dp"
        android:defaultWidth="600dp"
        android:gravity="top|end"
        android:minHeight="450dp"
        android:minWidth="300dp" />

ウィンドウ サイズを設定するための 2 つ目のオプションは、動的起動境界を使用します。setLaunchBounds(Rect)⁠⁠ を使用すると、開始時のウィンドウのサイズを定義できます。空の四角形を指定すると、アクティビティは最大化された状態で開始されます。

また、Unity または Unreal のゲームエンジンを使用している場合は、マルチウィンドウ モードを適切にサポートする最新バージョン(Unity 2019.4.40 および Unreal 5.3 以降)を使用していることを確認してください。

折りたたみ式デバイスの形状のサポート

Jetpack の WindowManager レイアウト ライブラリを使用して、テーブルトップなど、折りたたみ式デバイスの形状をサポートし、プレーヤーの没入感とエンゲージメントを高めます。

図 3. テーブルトップ モードのゲーム。メインビューはディスプレイの縦長部分に、コントロールは横長部分に表示されています。

Kotlin

fun isTableTopPosture(foldFeature : FoldingFeature?) : Boolean {
    contract { returns(true) implies (foldFeature != null) }
    return foldFeature?.state == FoldingFeature.State.HALF_OPENED &&
            foldFeature.orientation == FoldingFeature.Orientation.HORIZONTAL
}

Java

boolean isTableTopPosture(FoldingFeature foldFeature) {
    return (foldFeature != null) &&
           (foldFeature.getState() == FoldingFeature.State.HALF_OPENED) &&
           (foldFeature.getOrientation() == FoldingFeature.Orientation.HORIZONTAL);
}