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

Rozszerzenie gry z telefonów na różne formaty dużych ekranów wymaga przemyślenia sposobu zarządzania oknami. W ChromeOSGrach Google Play na PC gra może działać w trybie pełnoekranowym lub w ramce na głównym interfejsie komputera. Na nowych tabletach i urządzeniach składanych z Androidem w wersji 12L (poziom interfejsu API 32) lub nowszej o szerokości ekranu > 600 dp, gra może być uruchamiana obok innych aplikacji w trybie podzielonego ekranu, może być zmieniana w rozmiarach, a na urządzeniach składanych może być przenoszona między ekranem wewnętrznym i zewnętrznym, co powoduje zmianę konfiguracji rozmiaru okna i (na niektórych urządzeniach) orientacji.

Zmiana rozmiaru w przypadku gier w Unity

Podstawowa konfiguracja dużego ekranu

Podaj, czy Twoja gra obsługuje skalowanie:

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

Jeśli nie można obsługiwać zmiany rozmiaru, upewnij się, że w pliku z informacjami o grze wyraźnie określono minimalne i maksymalne obsługiwane proporcje:

<!-- 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 możliwość zmiany rozmiaru okna z zachowaniem określonego formatu obrazu. Rozmiar okna jest automatycznie blokowany na optymalnych wymiarach. Jeśli Twoja główna orientacja to orientacja pozioma, musisz obsługiwać co najmniej format 16:9, a jeśli gra jest w orientacji pionowej, musisz obsługiwać format 9:16. Aby zapewnić najlepsze wrażenia, w przypadku gry w orientacji poziomej wyraźnie obsługiwaj współczynniki proporcji 21:9, 16:10 i 3:2. Zmiana rozmiaru okna nie jest wymagana, ale jest przydatna ze względu na zgodność z innymi formatami.

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

ChromeOS i duże ekrany z Androidem

Aby zmaksymalizować widoczny obszar gry na pełnym ekranie w ChromeOS i na urządzeniach z Androidem o dużym ekranie, obsłuż tryb pełnoekranowy i ukryj paski systemowe, ustawiając flagi decorView, widoczność interfejsu użytkownika systemu lub interfejs API WindowInsetsCompat. Warto też odpowiednio obsługiwać zdarzenia konfiguracji związane z obrotem i zmianą rozmiaru lub zapobiegać ich wystąpieniu na urządzeniach z ChromeOS.

Pamiętaj, że na urządzeniach z Androidem o dużym ekranie gra może działać w konfiguracjach, które mogą nie być obsługiwane. Jeśli gra nie obsługuje wszystkich konfiguracji rozmiaru i orientacji okna, platforma wyświetli grę w trybie zgodności, a w razie potrzeby wyświetli graczowi komunikat przed przejściem do nieobsługiwanej konfiguracji.

Rysunek 1. Okno zgodności konfiguracji

Na niektórych urządzeniach, gdy gracz przejdzie do nieobsługiwanej konfiguracji, może pojawić się opcja ponownego załadowania gry i powtórnego utworzenia aktywności, aby jak najlepiej dopasować ją do nowej aranżacji okna, co zakłóca rozgrywkę. Przetestuj grę w różnych konfiguracjach trybu wielu okien (2/3, 1/2, 1/3 rozmiaru okna) i sprawdź, czy żadne elementy rozgrywki lub interfejsu nie są przycięte ani niedostępne. Dodatkowo sprawdź, jak gra reaguje na ciągłość na urządzeniach składanych podczas przełączania się między ekranem wewnętrznym a zewnętrznym. Jeśli zauważysz problemy, wyraźnie obsłuż te zdarzenia konfiguracji i dodaj obsługę funkcji zmiany rozmiaru na dużych ekranach.

Zaawansowane możliwości dostosowywania do dużych ekranów

Rysunek 2. Różne interfejsy na komputerze i w trybie tabletu.

Aby wyjść z trybunu zgodności i uniknąć ponownego tworzenia aktywności:

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

    <android:resizeableActivity="true" />
    
  2. Zadeklaruj obsługę parametrów „orientation”, „screenSize”, „smallestScreenSize”, „screenLayout” i „density” w atrybucie android:configChanges elementu <activity> w pliku manifestu gry, aby otrzymywać wszystkie zdarzenia konfiguracji dużego ekranu:

    <android:configChanges="screenSize | smallestScreenSize | screenLayout | orientation | keyboard |
                            keyboardHidden | density" />
    
  3. Zastąpić onConfigurationChanged() i obsługiwać 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ź nowe wymiary okna i wyrenderuj je w pełnej wielkości. Może się to nie udać w każdym przypadku z powodu różnic w formatach obrazu. Możesz wtedy zablokować interfejs gry w nowym rozmiarze okna i ustawić tryb letterbox dla głównej zawartości rozgrywki. Jeśli istnieją ograniczenia techniczne lub związane z projektowaniem, które uniemożliwiają zastosowanie któregokolwiek z tych rozwiązań, użyj letterboxa w silniku, aby zachować format obrazu, i zastosuj najlepsze możliwe wymiary, deklarując resizeableActivity = false i unikaj trybu konfiguracji.

Niezależnie od wybranego podejścia przetestuj grę w różnych konfiguracjach (złożona i rozłożona, różne zmiany orientacji, tryb podzielonego ekranu) i upewnij się, że elementy interfejsu w grze nie są przycięte ani nie nakładają się na siebie, że nie ma problemów z dostępnością do obszarów dotykowych ani problemów z formatem obrazu, które powodują rozciągnięcie, ściśnięcie lub inne zniekształcenie gry.

Ponadto większe ekrany zwykle oznaczają większe piksele, ponieważ na znacznie większym obszarze jest taka sama liczba pikseli. Może to powodować pikselizację w przypadku zmniejszonych buforów renderowania lub zasobów o niższej rozdzielczości. Używaj komponentów o najwyższej jakości na urządzeniach z dużym ekranem i sprawdź profil wydajności gry, aby upewnić się, że nie ma żadnych problemów. Jeśli gra obsługuje kilka poziomów jakości, upewnij się, że uwzględnia urządzenia z dużym ekranem.

Tryb wielu okien

Tryb wielu okien umożliwia korzystanie z wielu aplikacji jednocześnie na tym samym ekranie. Tryb wielu okien nie zmienia cyklu życia aktywności, ale stan wznowionych aplikacji w kilku oknach różni się w zależności od wersji Androida (patrz Cykl życia aktywności w trybie wielu okien w sekcji Obsługa trybu wielu okien).

Gdy gracz przełączy aplikację lub grę w tryb wielookienkowy, system powiadomi o zmianie konfiguracji zgodnie z informacjami podanymi w sekcji Zaawansowana możliwość zmiany rozmiaru na dużym ekranie. Zmiana konfiguracji następuje też, gdy gracz zmienia rozmiar gry lub przywraca tryb pełnoekranowy.

Nie ma gwarancji, że aplikacja odzyska fokus po przejściu w tryb wielu okien. Dlatego jeśli używasz któregoś z zdarzeń stanu aplikacji, aby wstrzymać grę, nie polegaj na zdarzeniu acquire focus (onWindowFocusChanged() z wartością focus = true), aby wznowić grę. Zamiast tego użyj innych metod obsługi zdarzeń lub metod 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ść jest uruchamiana w trybie wielookiennym.

W trybie wielu okien w ChromeOS ważne stają się początkowe wymiary okna. Gra nie musi zajmować całego ekranu. W takim przypadku musisz określić rozmiar okna. Możesz to zrobić na 2 sposoby.

Pierwsza opcja polega na użyciu określonych atrybutów w tagu <layout> w pliku manifestu Androida. Atrybuty defaultHeightdefaultWidth kontrolują początkowe wymiary. Pamiętaj też o atributach minHeightminWidth, aby uniemożliwić graczom zmianę rozmiaru okna gry na wymiary, których nie obsługujesz. Ostatnim atrybutem jest gravity, który określa, gdzie na ekranie pojawi się okno po uruchomieniu. Oto przykład tagu układu, który używa tych atrybutów:

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

Druga opcja ustawienia rozmiaru okna polega na użyciu dynamicznych granic uruchamiania. Za pomocą parametru setLaunchBounds(Rect)⁠⁠ możesz zdefiniować wymiary okna początkowego. Jeśli zostanie podany pusty prostokąt, aktywność zostanie uruchomiona w stanie zmaksymalizowanym.

Jeśli używasz silników gier Unity lub Unreal, upewnij się, że korzystasz z ich najnowszych wersji (Unity 2019.4.40 i Unreal 5.3 lub nowsze), które dobrze obsługują tryb wielookienkowy.

Obsługa składania

Aby zwiększyć zaangażowanie i zaangażowanie graczy, możesz użyć biblioteki układów WindowManager z Jetpacka, która obsługuje składane urządzenia, takie jak tablety:

Rysunek 3. Gra w układance na stole z głównym widokiem na pionowej części ekranu i sterowaniem na 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);
}