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

Na telefonach można korzystać z różnych urządzeń o dużych ekranach, dlatego warto wziąć pod uwagę sposób zarządzania oknami w grze. Na ChromeOS i Grach Google Play na PC możesz uruchomić grę w trybie okna przez główny interfejs komputera. Na nowych tabletach z Androidem i urządzeniach składanych z Androidem 12L (poziom interfejsu API 32) lub nowszym przy szerokości ekranu >600 dp gra może działać obok siebie w trybie podzielonego ekranu z innymi aplikacjami, zmieniać rozmiar, a nawet przenosić ją między wyświetlaczem wewnętrznym a zewnętrznym (na urządzeniach składanych) co skutkuje zmianą konfiguracji okna i orientacji (na niektórych urządzeniach).

Podstawowa konfiguracja dużego ekranu

Określ, czy Twoja gra obsługuje zmianę rozmiaru:

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

Jeśli nie można zmienić rozmiaru, upewnij się, że plik manifestu gry wyraźnie określa 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 obsługuje zmianę rozmiaru okna z uwzględnieniem określonego współczynnika proporcji. Rozmiar okna jest automatycznie ustawiany na optymalne wymiary. Jeśli gra jest w orientacji poziomej, obraz musi mieć format co najmniej 16:9, a jeśli gra jest w orientacji pionowej, współczynnik proporcji 9:16. Aby uzyskać najlepsze rezultaty, wyraź zgodę na proporcje obrazu 21:9, 16:10 i 3:2 w grze w orientacji poziomej. Możliwość zmiany rozmiaru okna nie jest w tym przypadku wymagana, ale warto to zrobić, aby zachować zgodność z innymi formatami.

Więcej informacji i sprawdzone metody znajdziesz w artykule Konfigurowanie grafiki na potrzeby Gier 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 urządzeniach z dużym ekranem, włącz pełny ekran w trybie pojemnym i ukryj paski systemu, ustawiając flagi decorView, widoczność interfejsu systemu lub interfejs API WindowInsetsCompat. Warto też płynnie obsługiwać rotację i zmienianie rozmiaru zdarzeń konfiguracji lub zapobiegać ich powtarzaniu na urządzeniach z ChromeOS.

Pamiętaj, że na urządzeniach z Androidem z dużym ekranem gra może działać w konfiguracjach, których być może jeszcze nie obsługujesz. Jeśli gra nie obsługuje wszystkich konfiguracji rozmiaru okna i orientacji ekranu, platforma oznacza grę w trybie zgodności, a w razie potrzeby wyświetla komunikat z prośbą o przejście na nieobsługiwaną konfigurację.

Rysunek 1. Okno zgodności konfiguracji.

Na niektórych urządzeniach, gdy użytkownik przejdzie na nieobsługiwaną konfigurację, może pojawić się prośba o ponowne wczytanie gry i odtworzenie aktywności w celu dopasowania jej do nowego układu okna, co utrudnia rozgrywkę. Przetestuj grę w różnych konfiguracjach trybu wielu okien (2/3, 1/2, 1/3 okna) i sprawdź, czy rozgrywka ani elementy interfejsu nie są ucięte lub niedostępne. Dodatkowo sprawdź, jak gra reaguje na składanie elementów ciągłości podczas poruszania się między wewnętrznym i zewnętrznym ekranem na urządzeniach składanych. Jeśli zauważysz problemy, musisz bezpośrednio obsłużyć te zdarzenia konfiguracji i dodać zaawansowaną obsługę zmiany rozmiaru dużego ekranu.

Zaawansowana możliwość zmiany rozmiaru dużego ekranu

Rysunek 2. Różne interfejsy użytkownika na komputerze i urządzenia składane w pozycji stołowej.

Aby wyłączyć tryb zgodności i uniknąć odtwarzania aktywności:

  1. Deklarowanie głównej aktywności jako możliwości zmiany rozmiaru:

    <android:resizeableActivity="true" />
    
  2. Zadeklaruj wyraźną obsługę właściwości „orientation”, „screenSize”, „smallestScreenSize”, „screenUkład” 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 element onConfigurationChanged() i obsługuj zdarzenie konfiguracji, w tym aktualną 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ć aktualną rotację urządzenia. Korzystając z tych metadanych, sprawdź nowe wymiary okna i wyrenderuj go w pełnym rozmiarze okna. W niektórych przypadkach może to nie zadziałać z powodu różnic w formacie obrazu, więc możesz zakotwiczyć interfejs gry w nowym rozmiarze okna i umieścić w nim podstawowe treści związane z rozgrywką. Jeśli zastosowanie ma ograniczenia techniczne lub projektowe, które uniemożliwiają stosowanie obu metod, możesz zastosować własne czarne pasy w wyszukiwarce, aby zachować współczynnik proporcji, i skalować obraz do najlepszych możliwych wymiarów, zadeklarując atrybut resizeableActivity = false i unikając trybu konfiguracji.

Niezależnie od stosowanej metody przetestuj grę w różnych konfiguracjach (składanie i rozkładanie, różne zmiany rotacji, tryb podzielonego ekranu) i upewnij się, że nie ma w niej elementów interfejsu, które się przycięły lub nakładały się, nie występują w nich żadne problemy z dostępnością docelowych elementów dotykowych ani proporcje obrazu, przez co gra jest rozciągnięta, ściśnięta lub zniekształcona w inny sposób.

Poza tym większe ekrany oznaczają zwykle większe piksele, ponieważ te same piksele oznaczają znacznie większy obszar. Może to spowodować pikselizację w przypadku zmniejszonych buforów renderowania lub zasobów o niższej rozdzielczości. Na urządzeniach z dużymi ekranami używaj zasobów najwyższej jakości i profiluj swoją grę, aby uniknąć problemów. Jeśli gra obsługuje kilka poziomów jakości, upewnij się, że jest odpowiednia dla urządzeń z dużym ekranem.

Tryb wielu okien

Tryb wielu okien umożliwia jednoczesne udostępnianie tego samego ekranu przez wiele aplikacji. Tryb wielu okien nie zmienia cyklu aktywności. Jednak wznowienie działania 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 wielu okien).

Gdy odtwarzacz przełączy aplikację lub grę w tryb wielu okien, system powiadamia o zmianie konfiguracji zgodnie z opisem w sekcji Zaawansowane możliwości zmiany rozmiaru dużego ekranu. Zmiana konfiguracji ma miejsce też wtedy, gdy gracz zmieni rozmiar gry lub włączy tryb pełnoekranowy.

Nie ma gwarancji, że po włączeniu trybu wielu okien aplikacja ponownie go aktywuje. Dlatego, jeśli do wstrzymywania gry używasz dowolnego zdarzenia stanu aplikacji, nie wznawiaj jej przez zdarzenie skupienia (onWindowFocusChanged() z wartością fokusu ustawioną na „prawda”). Zamiast tego używaj innych modułów obsługi zdarzeń lub modułów obsługi zmiany stanu, np. onConfigurationChanged() lub onResume(). Pamiętaj, że zawsze możesz użyć metody isInMultiWindowMode(), by wykryć, czy bieżąca aktywność działa w trybie wielu okien.

W trybie wielu okien w ChromeOS początkowe wymiary okna są istotnym czynnikiem. Gra nie może być działająca na pełnym ekranie. Musisz też zadeklarować rozmiar okna w takim przypadku. Możesz to zrobić na 2 sposoby.

Pierwsza opcja działa dzięki wykorzystaniu określonych atrybutów w tagu <layout> w pliku manifestu Androida. Atrybuty defaultHeight i defaultWidth sterują wymiarami początkowymi. Zapoznaj się też z atrybutami minHeight i minWidth, aby uniemożliwić graczom zmianę rozmiaru okna gry na nieobsługiwane przez Ciebie wymiary. 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 korzysta z tych atrybutów:

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

Druga opcja ustawiania rozmiaru okna polega na stosowaniu dynamicznych granic uruchamiania. Korzystając z setLaunchBounds(Rect)⁠⁠, możesz zdefiniować początkowe wymiary okna. Jeśli określisz pusty prostokąt, aktywność zostanie uruchomiona w stanie zmaksymalizowanym.

Jeśli używasz silników gier Unity lub Unreal, upewnij się, że masz najnowszą wersję (Unity 2019.4.40 i Unreal 5.3 lub nowszą), która dobrze obsługuje tryb wielu okien.

Składane podparcie postawy

Użyj biblioteki układu WindowManagera w Jetpacku, aby obsługiwać składane pozycje, takie jak na stole, i w ten sposób zwiększyć zaangażowanie graczy i ich zaangażowanie:

Rysunek 3. Gra w sposobie na stole z widokiem głównym w pionowej części wyświetlacza, elementami sterującymi w poziomie.

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