Suporte a redimensionamento de telas grandes

A expansão de smartphones para diferentes formatos de tela grande introduz considerações sobre como o jogo lida com o gerenciamento de janelas. No ChromeOS e no Google Play Games no PC, o jogo pode ser executado em modo de janela em uma interface principal de computador. Em novos tablets e dispositivos dobráveis Android com o Android 12L (nível 32 da API) ou mais recente e largura de tela maior que 600 dp, seu jogo pode ser executado lado a lado no modo de tela dividida com outros aplicativos, ser redimensionado e até mesmo movido entre a tela interna e externa em dispositivos dobráveis, resultando em uma mudança de configuração para o tamanho da janela e, em alguns dispositivos, a orientação.

Redimensionamento com jogos do Unity

Configuração básica de tela grande

Declare se o jogo é capaz de lidar com redimensionamento:

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

Se não for possível oferecer suporte à redimensionabilidade, verifique se o manifesto do jogo define explicitamente as proporções mínima e máxima compatíveis:

<!-- 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 Games no PC

No Google Play Games no PC, a plataforma processa a redimensionabilidade da janela respeitando a proporção especificada. O tamanho da janela é bloqueado automaticamente nas dimensões ideais. É necessário oferecer suporte a pelo menos 16:9 se a orientação principal for paisagem e 9:16 se o jogo estiver no modo retrato. Para ter a melhor experiência, ofereça suporte explícito às proporções 21:9, 16:10 e 3:2 para um jogo no modo paisagem. A redimensionabilidade da janela não é necessária aqui, mas ainda é bom ter para outros formatos de compatibilidade.

Para mais informações e práticas recomendadas, consulte Configurar gráficos para o Google Play Games no PC.

ChromeOS e telas grandes do Android

Para maximizar a área visível do jogo em tela cheia no ChromeOS e em dispositivos Android de tela grande, ofereça suporte ao modo imersivo de tela cheia e oculte as barras do sistema definindo flags em decorView, na visibilidade da interface do sistema ou pela API WindowInsetsCompat. Você também vai precisar processar eventos de configuração de rotação e redimensionamento ou impedir que eles aconteçam em dispositivos ChromeOS.

Em dispositivos Android de tela grande, seu jogo pode ser executado em configurações que você talvez ainda não consiga processar. Se o jogo não oferecer suporte a todas as configurações de tamanho e orientação da janela, a plataforma vai usar o modo de compatibilidade e, se necessário, vai pedir permissão ao jogador antes de mudar para uma configuração sem suporte.

Figura 1. Caixa de diálogo de compatibilidade da configuração.

Em alguns dispositivos, quando um jogador muda para uma configuração sem suporte, ele pode receber a opção de recarregar o jogo e recriar a atividade para se ajustar melhor ao novo layout da janela, o que interrompe a experiência de jogo. Teste seu jogo em várias configurações do modo de várias janelas (tamanho de janela 2/3, 1/2, 1/3) e verifique se nenhum elemento de jogabilidade ou da interface fica cortado ou inacessível. Além disso, teste como o jogo responde à continuidade dobrável ao se mover entre a tela interna e externa em dispositivos dobráveis. Se você encontrar problemas, processe explicitamente esses eventos de configuração e adicione suporte avançado de redimensionamento de tela grande.

Redimensionamento avançado para telas grandes

Figura 2. Interfaces diferentes em computadores e dispositivos dobráveis na posição de mesa.

Para sair do modo de compatibilidade e evitar a recriação de atividades, faça o seguinte:

  1. Declare sua atividade principal como redimensionável:

    <android:resizeableActivity="true" />
    
  2. Declare suporte explícito para "orientation", "screenSize", "smallestScreenSize", "screenLayout" e "density" no atributo android:configChanges do elemento <activity> do manifesto do jogo para receber todos os eventos de configuração de tela grande:

    <android:configChanges="screenSize | smallestScreenSize | screenLayout | orientation | keyboard |
                            keyboardHidden | density" />
    
  3. Substitua onConfigurationChanged() e processe o evento de configuração, incluindo a orientação atual, o tamanho da janela, a largura e a altura:

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

Também é possível consultar o WindowManager para verificar a rotação atual do dispositivo. Usando esses metadados, verifique as novas dimensões da janela e renderize para o tamanho total da janela. Isso pode não funcionar em todos os casos devido a diferenças de proporção. Como alternativa, ancore a interface do jogo ao novo tamanho da janela e use o letterbox no conteúdo principal do jogo. Se houver limitações técnicas ou de design que impeçam qualquer uma das abordagens, crie seu próprio letterboxing no mecanismo para preservar a proporção e dimensione para as melhores dimensões possíveis ao declarar resizeableActivity = false e evitar o modo de configuração.

Independentemente da abordagem, teste o jogo em várias configurações (dobra e desdobra, diferentes mudanças de rotação, modo de tela dividida) e verifique se não há elementos de IU cortados ou sobrepostos, problemas de acessibilidade de destino de toque ou problemas de proporção que causem alongamento, compressão ou distorção do jogo.

Além disso, telas maiores geralmente significam pixels maiores, porque você tem o mesmo número de pixels para uma área muito maior. Isso pode causar pixelização em buffers de renderização reduzidos ou recursos de resolução mais baixa. Use os recursos de maior qualidade em dispositivos de tela grande e faça um perfil de desempenho do jogo para garantir que não haja problemas. Se o jogo tiver suporte a vários níveis de qualidade, verifique se ele é compatível com dispositivos de tela grande.

Modo de várias janelas

O modo de várias janelas permite que vários apps compartilhem a mesma tela simultaneamente. O modo de várias janelas não muda o ciclo de vida da atividade. No entanto, o estado retomado dos apps em várias janelas varia de acordo com as diferentes versões do Android. Consulte Ciclo de vida da atividade no modo de várias janelas em Suporte ao modo de várias janelas.

Quando o jogador coloca um app ou jogo no modo de várias janelas, o sistema notifica a atividade sobre uma mudança na configuração, como especificado na seção Redimensionamento avançado de telas grandes. Uma mudança de configuração também acontece quando o jogador redimensiona o jogo ou o coloca de volta no modo de tela cheia.

Não há garantia de que o aplicativo vai recuperar o foco quando for colocado no modo de várias janelas. Portanto, se você usar qualquer um dos eventos de estado do app para pausar o jogo, não dependa do evento de aquisição de foco (onWindowFocusChanged() com o valor de foco como "true") para retomar o jogo. Em vez disso, use outros manipuladores de eventos ou de mudança de estado, como onConfigurationChanged() ou onResume(). É possível usar o método isInMultiWindowMode() para detectar se a atividade atual está em execução no modo de várias janelas.

Com o modo de várias janelas no ChromeOS, as dimensões iniciais da janela se tornam uma consideração importante. Um jogo não precisa ser em tela cheia, e você vai precisar declarar o tamanho da janela nesse caso. Há duas maneiras recomendadas de fazer isso.

A primeira opção usa atributos específicos na tag <layout> no manifesto do Android. Os atributos defaultHeight e defaultWidth controlam as dimensões iniciais. Também é importante considerar os atributos minHeight e minWidth para evitar que os jogadores redimensionem a janela do jogo para dimensões que não oferecem suporte. Por fim, há o atributo gravity, que determina onde a janela aparece na tela quando é iniciada. Confira um exemplo de tag de layout que usa esses atributos:

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

A segunda opção para definir o tamanho da janela funciona usando limites de inicialização dinâmicos. Usando setLaunchBounds(Rect)⁠⁠, você pode definir as dimensões iniciais da janela. Se um retângulo vazio for especificado, a atividade será iniciada em um estado maximizado.

Além disso, se você estiver usando os mecanismos de jogo Unity ou Unreal, verifique se está usando uma versão recente (Unity 2019.4.40 e Unreal 5.3 ou mais recente) que ofereça bom suporte ao modo de várias janelas.

Suporte para posição dobrável

Use a biblioteca de layout WindowManager do Jetpack para oferecer suporte a posições dobráveis, como mesa, para aumentar a imersão e o engajamento do jogador:

Figura 3. Jogo em posição de mesa com a visualização principal na parte vertical da tela e os controles na parte horizontal.

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