Obsługa dużego ekranu z możliwością zmiany rozmiaru

Przejście z telefonów na różne duże ekrany może wymagać przemyślenia, jak gra obsługuje zarządzanie oknami. W ChromeOS i Grach Google Play na PC gra może działać w trybie okna z głównym interfejsem pulpitu. Na nowych tabletach z Androidem i urządzeniach składanych z Androidem 12L (poziom interfejsu API 32) lub nowszym o szerokości ekranu > 600 dp, gra może działać równolegle w trybie podzielonego ekranu z innymi aplikacjami, zmieniać jej rozmiar, a nawet przenosić się między wewnętrznym a zewnętrznym ekranem na urządzeniach składanych, co prowadzi do zmiany konfiguracji rozmiaru okna, a na niektórych urządzeniach również orientacji.

Możliwość zmiany rozmiaru w grach na Unity

Podstawowa konfiguracja dużego ekranu

Określ, czy gra obsługuje możliwość zmiany rozmiaru:

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

Jeśli nie możesz zmienić rozmiaru, sprawdź, czy w pliku manifestu gry są określone minimalne i maksymalne obsługiwane formaty obrazu:

<!-- 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">

Gry Google Play na PC

W przypadku Gier Google Play na PC platforma umożliwia zmianę rozmiaru okna z zachowaniem określonego współczynnika proporcji. Rozmiar okna jest automatycznie blokowany pod kątem optymalnych wymiarów. W przypadku gry w orientacji poziomej format obrazu musi wynosić co najmniej 16:9, a w trybie pionowym – 9:16. Aby uzyskać najlepsze wrażenia, musisz wyraźnie obsługiwać formaty 21:9, 16:10 i 3:2 w grach w orientacji poziomej. Zmiana rozmiaru okna nie jest w tym przypadku wymagana, ale warto ją włączyć w przypadku zgodności z innymi formatami.

Więcej informacji i sprawdzone metody znajdziesz w artykule Konfigurowanie grafiki w Grach Google Play na PC.

Duże ekrany ChromeOS i Androida

Aby zmaksymalizować widoczny obszar gry na pełnym ekranie na urządzeniach z ChromeOS i dużym ekranie, zapewnij obsługę trybu pełnoekranowego pełnoekranowego i ukryj paski systemowe. Aby to zrobić, ustaw flagi na decorView, widoczność interfejsu systemu lub interfejs API WindowInsetsCompat. Warto też bezproblemowo obsługiwać zdarzenia konfiguracji i zmieniać ich rozmiar oraz zapobiegać ich występowaniu na urządzeniach z ChromeOS.

Pamiętaj, że na dużych urządzeniach z Androidem gra może działać w konfiguracjach, z których być może jeszcze nie korzystasz. Jeśli gra nie obsługuje wszystkich konfiguracji rozmiaru i orientacji okna, platformy wyświetlają w niej tryb zgodności i w razie potrzeby poprosi o wprowadzenie nieobsługiwanej konfiguracji.

Rysunek 1. Okno zgodności konfiguracji.

Na niektórych urządzeniach, gdy gracz przejdzie na nieobsługiwaną konfigurację, może zobaczyć prośbę o ponowne wczytanie gry i odtworzenie aktywności, aby jak najlepiej dopasować ją do nowego układu okna, co zakłóca rozgrywkę. Przetestuj grę w różnych konfiguracjach trybu wielu okien (2/3, 1/2 i 1/3 okna) i upewnij się, że żadna rozgrywka ani elementy interfejsu nie zostały ucięte lub niedostępne. Możesz też sprawdzić, jak Twoja gra reaguje na funkcję składanej ciągłości, gdy przechodzisz między ekranem wewnętrznym i zewnętrznym na urządzeniach składanych. Jeśli zauważysz problemy, musisz obsługiwać te zdarzenia konfiguracji i dodać zaawansowaną obsługę zmiany rozmiaru dużego ekranu.

Zaawansowana zmiana rozmiaru dużego ekranu

.
Rysunek 2. Różne interfejsy użytkownika na komputerze i w przypadku urządzeń składanych w stanie stołu.

Aby wyjść z trybu zgodności i uniknąć odtwarzania aktywności, wykonaj te czynności:

  1. Zadeklaruj główną aktywność jako z możliwością zmiany rozmiaru:

    <android:resizeableActivity="true" />
    
  2. Zadeklaruj wyraźną obsługę pól „orientation”, „screenSize”, „smallestScreenSize”, „screenLayout” i „gęstość” w atrybucie android:configChanges elementu <activity> pliku manifestu gry, aby otrzymywać wszystkie zdarzenia konfiguracji dużego ekranu:

    <android:configChanges="screenSize | smallestScreenSize | screenLayout | orientation | keyboard |
                            keyboardHidden | density" />
    
  3. Zastąp onConfigurationChanged() i obsługuj zdarzenie konfiguracji, w tym bieżącą orientację, rozmiar okna, szerokość i wysokość:

    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();
    }
    

Możesz też wysłać zapytanie do WindowManager, aby sprawdzić bieżącą rotację urządzeń. Korzystając z tych metadanych, sprawdź wymiary nowego okna i wyrenderuj je do pełnego rozmiaru. Ze względu na różnice w formacie obrazu może to nie zadziałać we wszystkich przypadkach, dlatego możesz też zakotwiczyć interfejs gry w nowym rozmiarze okna i w czarnych pasach. Jeśli istnieją ograniczenia techniczne lub projektowe, które uniemożliwiają wprowadzenie którejś z tych opcji, utwórz własne czarne pasy w wyszukiwarce, aby zachować współczynnik proporcji, i przeskaluj do optymalnych wymiarów, zadeklarując resizeableActivity = false i unikając trybu konfiguracji.

Niezależnie od wybranej metody przetestuj grę w różnych konfiguracjach (rozłożenie, różne zmiany obrotu, tryb podzielonego ekranu) i upewnij się, że nie ma żadnych przeciętych lub nakładających się elementów interfejsu w grze, problemów z dostępnością dotykiem lub z formatem obrazu, które powodują rozciągnięcie, zwarcie lub zniekształcenia w inny sposób.

Poza tym większe ekrany zwykle oznaczają większe piksele, bo ta sama liczba pikseli jest dużo większa. Może to powodować pikselizację w przypadku mniejszych buforów renderowania lub zasobów o niższej rozdzielczości. Na urządzeniach z dużym ekranem używaj zasobów najwyższej jakości i profiluj wydajność gry, aby uniknąć problemów. Jeśli gra obsługuje wiele poziomów jakości, sprawdź, czy obsługuje ona urządzenia z dużym ekranem.

Tryb wielu okien

Tryb wielu okien umożliwia jednoczesne korzystanie z tego samego ekranu wielu aplikacjom. Tryb wielu okien nie zmienia cyklu życia aktywności. jednak wznawianie aplikacji w wielu oknach różni się w zależności od wersji Androida (zobacz Cykl życia aktywności w trybie wielu okien w artykule Obsługa trybu wielu okien).

Gdy gracz przełącza aplikację lub grę w tryb wielu okien, system powiadamia o zmianie konfiguracji zgodnie z opisem w sekcji Zaawansowane zmiany rozmiaru dużego ekranu. Zmiana konfiguracji następuje też wtedy, gdy gracz zmienia rozmiar gry lub włącza ją z powrotem w tryb pełnego ekranu.

Nie ma gwarancji, że aplikacja ponownie się aktywuje po przejściu w tryb wielu okien. Dlatego jeśli do wstrzymania gry użyjesz dowolnego zdarzenia stanu aplikacji, nie korzystaj z zdarzenia polegającego na pozyskiwaniu ostrości (onWindowFocusChanged() z wartością fokusem ustawionym na wartość true), aby wznowić grę. Zamiast nich używaj innych modułów obsługi zdarzeń lub modułów obsługi zmiany stanu, takich jak onConfigurationChanged() lub onResume(). Pamiętaj, że zawsze możesz użyć metody isInMultiWindowMode(), aby wykryć, czy bieżąca aktywność działa w trybie wielu okien.

W trybie wielu okien w ChromeOS ważne są początkowe wymiary okien. Gra nie musi wyświetlać całego ekranu – w takim przypadku zadeklaruj wielkość okna. Możesz to zrobić na 2 sposoby.

Pierwsza opcja działa na podstawie określonych atrybutów w tagu <layout> w pliku manifestu Androida. Atrybuty defaultHeight i defaultWidth określają wymiary początkowe. Pamiętaj też o atrybutach minHeight i minWidth, aby uniemożliwić graczom zmianę rozmiaru okna gry do wymiarów, których nie obsługujesz. Dostępny jest też atrybut gravity, który określa, w którym miejscu na ekranie pojawi się okno po uruchomieniu. Oto przykładowy tag układu, który wykorzystuje te atrybuty:

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

Druga opcja ustawiania rozmiaru okna działa przy użyciu dynamicznych granic uruchamiania. Za pomocą setLaunchBounds(Rect)⁠⁠ możesz zdefiniować wymiary okna początkowego. Jeśli wskażesz pusty prostokąt, aktywność będzie rozpoczęta w stanie zmaksymalizowanym.

Jeśli używasz silników gier Unity lub Unreal, sprawdź, czy używasz najnowszej wersji (Unity 2019.4.40 i Unreal 5.3 lub nowszej), która zapewnia prawidłową obsługę trybu wielu okien.

Obsługa składanego stanu

Użyj biblioteki układów WindowManager Jetpack do obsługi stanów składanych, na przykład stołu, aby zwiększyć zaangażowanie i zaangażowanie graczy.

Rysunek 3. Gra w stanie stołu, z widokiem głównym w pionowej części wyświetlacza, elementy sterujące w części poziomej.

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);
}