Расширение с телефонов на различные форм-факторы больших экранов вносит соображения о том, как ваша игра обрабатывает управление окнами. В ChromeOS и Google Play Games на ПК ваша игра может работать в оконном режиме поверх основного интерфейса рабочего стола. На новых планшетах Android и складных устройствах под управлением Android 12L (уровень API 32) или выше с шириной экрана > 600dp ваша игра может работать бок о бок в режиме разделенного экрана с другими приложениями, изменять размер и даже перемещаться между внутренним и внешним дисплеями на складных устройствах, что приводит к изменению конфигурации размера окна и, на некоторых устройствах, ориентации.
Возможность изменения размера в играх 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">
Google Play Игры на ПК
Для Google Play Games на ПК платформа обрабатывает изменение размера окна, соблюдая указанное соотношение сторон. Размер окна автоматически фиксируется на оптимальных размерах. Вам необходимо поддерживать соотношение сторон не менее 16:9, если ваша основная ориентация — альбомная, и соотношение сторон 9:16, если ваша игра находится в портретном режиме. Для наилучшего опыта явно поддерживайте соотношения сторон 21:9, 16:10 и 3:2 для альбомной игры. Изменение размера окна здесь не требуется, но все равно полезно для совместимости с другими форм-факторами.
Дополнительную информацию и рекомендации см. в статье Настройка графики для игр Google Play на ПК.
Большие экраны ChromeOS и Android
Чтобы максимально увеличить область просмотра для вашей игры на весь экран на ChromeOS и устройствах Android с большим экраном, поддерживайте полноэкранный иммерсивный режим и скрывайте системные панели, установив флаги на decorView
, видимость системного пользовательского интерфейса или через API WindowInsetsCompat
. Вам также захочется изящно обрабатывать события конфигурации поворота и изменения размера или предотвращать их возникновение на устройствах ChromeOS.
Обратите внимание, что на устройствах Android с большим экраном ваша игра может работать в конфигурациях, которые вы, возможно, еще не обрабатывали. Если ваша игра не поддерживает все конфигурации размеров и ориентации окон, платформа переведет вашу игру в режим совместимости и, при необходимости, предложит игроку перейти на неподдерживаемую конфигурацию.

На некоторых устройствах, когда игрок переходит на неподдерживаемую конфигурацию, ему может быть предложено перезагрузить игру и заново создать действие, чтобы оно наилучшим образом соответствовало новому макету окна, что нарушает игровой процесс. Протестируйте свою игру в различных конфигурациях многооконного режима (размер окна 2/3, 1/2, 1/3) и убедитесь, что ни один игровой процесс или элементы пользовательского интерфейса не обрезаются или не становятся недоступными. Кроме того, проверьте, как ваша игра реагирует на складную непрерывность при перемещении между внутренним и внешним экраном на складных устройствах. Если вы видите проблемы, явно обработайте эти события конфигурации и добавьте расширенную поддержку изменения размера большого экрана.
Расширенные возможности изменения размера большого экрана
Чтобы выйти из режима совместимости и избежать повторного использования, выполните следующие действия:
Объявите свою основную деятельность как изменяемую по размеру:
<android:resizeableActivity="true" />
Объявите явную поддержку «orientation», «screenSize», «smallestScreenSize», «screenLayout» и «density» в атрибуте
android:configChanges
элемента<activity>
вашего манифеста игры, чтобы получать все события конфигурации большого экрана :<android:configChanges="screenSize | smallestScreenSize | screenLayout | orientation | keyboard | keyboardHidden | density" />
Переопределите
onConfigurationChanged()
и обработайте событие конфигурации, включая текущую ориентацию, размер окна, ширину и высоту:Котлин
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 }
Ява
@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
, чтобы проверить текущий поворот устройства. Используя эти метаданные, проверьте новые размеры окна и отрисуйте его в полный размер окна. Это может не сработать во всех случаях из-за различий в соотношении сторон, поэтому в качестве альтернативы привяжите свой игровой пользовательский интерфейс к новому размеру окна и разместите основной игровой контент в виде почтового ящика. Если технические или конструктивные ограничения не позволяют использовать любой из этих подходов, выполните собственный почтовой ящик в движке, чтобы сохранить соотношение сторон, и масштабируйте до наилучших возможных размеров, объявляя resizeableActivity = false
и избегая режима конфигурации.
Независимо от выбранного вами подхода протестируйте игру в различных конфигурациях (сворачивание и разворачивание, различные повороты, режим разделения экрана) и убедитесь, что нет обрезанных или перекрывающихся элементов игрового пользовательского интерфейса, проблем с доступом к сенсорным объектам или проблем с соотношением сторон, из-за которых игра растягивается, сжимается или иным образом искажается.
Кроме того, большие экраны обычно означают большие пиксели, поскольку у вас то же количество пикселей для гораздо большей площади. Это может вызвать пикселизацию для уменьшенных буферов рендеринга или ресурсов с более низким разрешением. Используйте ресурсы самого высокого качества на устройствах с большим экраном и профилируйте производительность вашей игры , чтобы убедиться в отсутствии проблем. Если ваша игра поддерживает несколько уровней качества, убедитесь, что она учитывает устройства с большим экраном.
Многооконный режим
Многооконный режим позволяет нескольким приложениям одновременно использовать один и тот же экран. Многооконный режим не изменяет жизненный цикл активности ; однако возобновленное состояние приложений в нескольких окнах отличается в разных версиях Android (см. Жизненный цикл активности в многооконном режиме в разделе Поддержка многооконного режима ).
Когда игрок переводит приложение или игру в многооконный режим, система уведомляет об изменении конфигурации, как указано в разделе Расширенное изменение размера большого экрана . Изменение конфигурации также происходит, когда игрок изменяет размер игры или возвращает ее в полноэкранный режим.
Нет гарантии, что приложение восстановит фокус при переводе в многооконный режим. Поэтому, если вы используете любое из событий состояния приложения для приостановки игры, не полагайтесь на событие получения фокуса ( onWindowFocusChanged()
со значением фокуса как true) для возобновления игры. Вместо этого используйте другие обработчики событий или обработчики изменения состояния, такие как onConfigurationChanged()
или onResume()
. Обратите внимание, что вы всегда можете использовать метод isInMultiWindowMode()
чтобы определить, выполняется ли текущая активность в многооконном режиме.
В многооконном режиме на ChromeOS начальные размеры окна становятся важным фактором. Игра не обязательно должна быть полноэкранной, и вам нужно будет указать, каким должен быть размер окна для этого случая. Есть два рекомендуемых способа подойти к этому.
Первый вариант работает с использованием определенных атрибутов в теге <layout>
в вашем манифесте Android. Атрибуты defaultHeight
и defaultWidth
управляют начальными размерами. Также помните об атрибутах minHeight
и minWidth
, чтобы ваши игроки не могли изменять размер игрового окна до размеров, которые вы не поддерживаете. Наконец, есть атрибут gravity
, который определяет, где на экране появляется окно при запуске. Вот пример тега макета с использованием этих атрибутов:
<layout android:defaultHeight="500dp"
android:defaultWidth="600dp"
android:gravity="top|end"
android:minHeight="450dp"
android:minWidth="300dp" />
Второй вариант установки размера окна работает через использование динамических границ запуска. Используя setLaunchBounds(Rect)
, вы можете определить начальные размеры окна. Если указан пустой прямоугольник, активность запускается в развернутом состоянии.
Кроме того, если вы используете игровые движки Unity или Unreal, убедитесь, что вы используете последнюю версию (Unity 2019.4.40 и Unreal 5.3 или новее), которая обеспечивает хорошую поддержку многооконного режима.
Складная поддержка осанки
Используйте библиотеку макетов Jetpack WindowManager для поддержки складных поз, таких как столешница, чтобы повысить погружение и вовлеченность игрока:

Котлин
fun isTableTopPosture(foldFeature : FoldingFeature?) : Boolean { contract { returns(true) implies (foldFeature != null) } return foldFeature?.state == FoldingFeature.State.HALF_OPENED && foldFeature.orientation == FoldingFeature.Orientation.HORIZONTAL }
Ява
boolean isTableTopPosture(FoldingFeature foldFeature) { return (foldFeature != null) && (foldFeature.getState() == FoldingFeature.State.HALF_OPENED) && (foldFeature.getOrientation() == FoldingFeature.Orientation.HORIZONTAL); }