Estudo de caso: redimensionamento automático de jogos HTML5

Derek Detweiler
Derek Detweiler

Introdução

No verão de 2010, criamos o Sand Trap, um jogo que participamos de uma competição sobre jogos em HTML5 para celulares. No entanto, a maioria dos smartphones exibia apenas parte do jogo ou o tornou muito pequeno, o que o torna completamente impossível. Então, decidimos fazer o jogo se ajustar de forma fluida a qualquer resolução. Depois de um pouco de reprogramação e uso das ideias descritas neste artigo, temos um jogo que pode ser dimensionado em qualquer navegador moderno, seja ele executado em um computador ou dispositivo móvel.

Captura de tela cheia
captura de tela de um tamanho menor na janela do navegador

Essa abordagem funcionou bem para o Sand Trap, então usamos o mesmo método no nosso jogo mais recente, o Thwack!. O jogo ajusta automaticamente as resoluções de tela para caber em janelas de tela cheia e de tamanho personalizado, como mostrado nas capturas de tela abaixo.

Para implementar, foi preciso aproveitar tanto o CSS quanto o JavaScript. Usar CSS para preencher toda a tela é trivial, mas o CSS não permite que você mantenha a mesma proporção largura/altura para evitar o alongamento da tela e da área do jogo. É aí que entra o JavaScript. Você pode redimensionar elementos do documento com JavaScript e acionar o redimensionamento em eventos de janela.

Como preparar a página

A primeira etapa é designar a área da página em que o jogo acontecerá. Se você incluí-lo como um bloco div, poderá adicionar outras tags ou um elemento de tela a ele. Ao configurá-lo corretamente, esses elementos filhos herdarão o dimensionamento do bloco div pai.

Se sua área de jogos tiver duas partes, uma área de jogo e uma área de registro de pontuação, ela poderá ficar assim:

<div id="gameArea">
  <canvas id="gameCanvas"></canvas>
  <div id="statsPanel"></div>
</div>

Assim que você tiver uma estrutura básica de documento, será possível dar a esses elementos algumas propriedades CSS para prepará-los para o redimensionamento. Muitas das propriedades CSS de "gameArea" são manipuladas diretamente pelo JavaScript, mas, para que funcionem, configure algumas outras propriedades CSS começando com o bloco div gameArea pai:

#gameArea {
  position: absolute;
  left:     50%;
  top:      50%;
}

Isso coloca o canto superior esquerdo da tela no centro. A função de redimensionamento automático do JavaScript descrita na próxima seção manipula propriedades CSS adicionais para redimensionar a área do jogo e centralizá-la na janela.

Como a área de jogo é redimensionada automaticamente de acordo com as dimensões da janela, você não quer as dimensões em pixels para os elementos filhos do bloco div do gameArea. Em vez disso, use porcentagens. Os valores de pixel não permitem que elementos internos sejam dimensionados com o div principal à medida que ele muda. No entanto, pode ser útil começar com pixels e convertê-los em porcentagens quando você tiver um layout que goste.

Para este exemplo, comece com a área do jogo com 300 pixels de altura e 400 pixels de largura. A tela cobre toda a área do jogo e um painel de estatísticas semitransparente na parte inferior, com 24 pixels de altura, como mostrado na Figura 1.

Dimensões dos elementos filhos de gameArea em pixels
Figura 1: dimensões dos elementos filhos de gameArea em pixels

A conversão desses valores em porcentagens torna a tela 100% em largura e 100% em altura (de gameArea, não da janela). Dividir 24 por 300 resulta na altura do painel de estatísticas em 8% e, como ele cobre a parte inferior da área do jogo, sua largura também será 100%, como mostrado na Figura 2.

Dimensões dos elementos filhos de gameArea em porcentagens
Figura 2: dimensões dos elementos filhos de gameArea em porcentagens

Agora que você determinou as dimensões da área do jogo e seus elementos filhos, monte as propriedades CSS para os dois elementos internos da seguinte maneira:

#gameCanvas {
  width: 100%;
  height: 100%;
}
#statsPanel {
  position: absolute;
  width: 100%;
  height: 8%;
  bottom: 0;
  opacity: 0.8;
}

Redimensionar o jogo

Agora você está pronto para criar uma função para lidar com a janela que está sendo redimensionada. Primeiro, faça uma referência ao elemento pai do documento gameArea.

var gameArea = document.getElementById('gameArea');

Como você não está preocupado com a largura ou altura exata, a próxima informação que precisa definir é a proporção entre largura e altura. Usando sua referência anterior de uma área de jogo de 400 pixels de largura e 300 pixels de altura, você sabe que deseja definir a proporção em 4 unidades de largura e 3 unidades de altura.

var widthToHeight = 4 / 3;

Como essa função é chamada sempre que a janela é redimensionada, é recomendável capturar as novas dimensões da janela para que você possa ajustar as dimensões do seu jogo de acordo com elas. Para isso, use as propriedades innerWidth e innerHeight da janela.

var newWidth = window.innerWidth;
var newHeight = window.innerHeight;

Assim como você determinou a proporção entre largura e altura desejada, agora é possível determinar a proporção atual entre largura e altura da janela:

var newWidthToHeight = newWidth / newHeight;

Isso permite que você decida se o jogo vai preencher a tela vertical ou horizontalmente, como mostrado na Figura 3.

Como ajustar o elemento gameArea à janela mantendo a proporção
Figura 3: ajuste do elemento gameArea à janela mantendo a proporção.

Se o formato desejado da área de jogo for mais largo que o da janela (e a altura for menor), você vai precisar preencher a janela horizontalmente e deixar margens nas partes de cima e de baixo. Da mesma forma, se o formato desejado da área de jogo for maior que o da janela (e a largura for mais estreita), você vai precisar preencher a janela verticalmente e deixar as margens à esquerda e à direita.

Para fazer isso, teste a proporção desejada de largura para altura com a proporção entre largura e altura da janela atual e faça os ajustes apropriados da seguinte forma:

if (newWidthToHeight > widthToHeight) {
  // window width is too wide relative to desired game width
  newWidth = newHeight * widthToHeight;
  gameArea.style.height = newHeight + 'px';
  gameArea.style.width = newWidth + 'px';
} else { // window height is too high relative to desired game height
  newHeight = newWidth / widthToHeight;
  gameArea.style.width = newWidth + 'px';
  gameArea.style.height = newHeight + 'px';
}

Agora que você ajustou a largura e a altura da área de jogos, é necessário centralizar as coisas, colocando uma margem negativa na parte superior que tenha metade da altura e à esquerda, que seja metade da largura. Lembre-se de que o CSS já está colocando o canto superior esquerdo do div gameArea no centro exato da janela. Isso centraliza a área do jogo na janela:

gameArea.style.marginTop = (-newHeight / 2) + 'px';
gameArea.style.marginLeft = (-newWidth / 2) + 'px';

Você também gostaria de ajustar automaticamente o tamanho da fonte. Se todos os elementos filhos estiverem usando "em", basta definir a propriedade CSS fontSize do bloco div gameArea como um valor determinado pelo tamanho dele.

gameArea.style.fontSize = (newWidth / 400) + 'em';

Por fim, você quer fazer com que as dimensões de desenho da tela correspondam à nova largura e altura. O restante do código do jogo precisa manter as dimensões do mecanismo de jogo separadas das dimensões do desenho da tela para acomodar uma resolução de tela dinâmica.

var gameCanvas = document.getElementById('gameCanvas');
gameCanvas.width = newWidth;
gameCanvas.height = newHeight;

Assim, a função de redimensionamento concluída pode ficar assim:

function resizeGame() {
    var gameArea = document.getElementById('gameArea');
    var widthToHeight = 4 / 3;
    var newWidth = window.innerWidth;
    var newHeight = window.innerHeight;
    var newWidthToHeight = newWidth / newHeight;
    
    if (newWidthToHeight > widthToHeight) {
        newWidth = newHeight * widthToHeight;
        gameArea.style.height = newHeight + 'px';
        gameArea.style.width = newWidth + 'px';
    } else {
        newHeight = newWidth / widthToHeight;
        gameArea.style.width = newWidth + 'px';
        gameArea.style.height = newHeight + 'px';
    }
    
    gameArea.style.marginTop = (-newHeight / 2) + 'px';
    gameArea.style.marginLeft = (-newWidth / 2) + 'px';
    
    var gameCanvas = document.getElementById('gameCanvas');
    gameCanvas.width = newWidth;
    gameCanvas.height = newHeight;
}

Agora, você quer que esses ajustes sejam feitos automaticamente sempre que a janela for redimensionada ou, no caso de dispositivos móveis, a orientação da tela for alterada. Gerencie esses eventos fazendo com que eles chamem sua função resizeGame() da seguinte forma:

window.addEventListener('resize', resizeGame, false);
window.addEventListener('orientationchange', resizeGame, false);

Se a janela for redimensionada para um valor muito alto ou se a orientação da tela for vertical, a largura da janela será de 100%. Se a janela for redimensionada com uma largura muito grande ou a orientação da tela for horizontal, a altura será de 100% da janela. A dimensão restante é dimensionada de acordo com a proporção predeterminada de largura por altura.

Resumo

A Gopherwood Studios usou versões dessa estrutura para todos os nossos jogos HTML5 e se mostrou muito útil para acomodar várias resoluções de tela e vários dispositivos móveis. Além disso, com a ajuda de um navegador de tela cheia, isso proporciona aos nossos jogos da web uma experiência imersiva mais parecida com os jogos para computador tradicionais do que muitos jogos baseados em navegador. À medida que o HTML5 e as tecnologias da Web continuam a evoluir, estamos ansiosos por mais inovações nos jogos para a Web.