事例紹介 - HTML5 ゲームの自動サイズ変更

はじめに

2010 年の夏に、Google は携帯電話向け HTML5 ゲームのコンテストに参加した「サンドトラップ」というゲームを開発しました。しかし、ほとんどのスマートフォンでは、ゲームの一部しか表示されないか、サイズが小さくなりすぎて、完全にプレイできません。そこで Google は、あらゆる解像度に合わせてゲームが滑らかに調整されるようにしました。この記事で概説しているアイデアを少し再プログラミングし、その結果、デスクトップでもモバイル デバイスでも、あらゆる最新のブラウザに対応できるゲームが完成しました。

スワックの全画面表示のスクリーンショット
ブラウザ ウィンドウの thwack の縮小のスクリーンショット

このアプローチはサンドトラップでうまくいったため、最新のゲーム『Thwack!』でも同じ手法を使用しました。以下のスクリーンショットに示すように、ゲームは全画面ウィンドウとカスタムサイズ ウィンドウの両方に合うように画面の解像度を自動的に調整します。

これを実装するには、CSS と JavaScript の両方を利用する必要がありました。CSS を使用して画面全体を埋めるのは簡単ですが、CSS では、キャンバスとゲーム領域が引き伸ばされないように、同じ幅と高さの比率を維持できません。そこで役立つのが JavaScript です。JavaScript を使用してドキュメント要素のサイズを変更し、ウィンドウ イベントでサイズ変更をトリガーできます。

ページの準備

まず、ゲームが行われるページ上の領域を指定します。これを div ブロックとして含めると、その中に他のタグやキャンバス要素を配置できます。正しく設定すると、これらの子要素は親 div ブロックのスケーリングを継承します。

ゲームエリアがプレイエリアとスコアキープエリアの 2 つの部分で構成されている場合、次のようになります。

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

基本的なドキュメント構造を用意したら、これらの要素にいくつかの CSS プロパティを渡して、サイズ変更の準備をします。「gameArea」の CSS プロパティの多くは JavaScript によって直接操作されますが、これらを機能させるには、親の gameArea div ブロックで始まる他の CSS プロパティをいくつか設定します。

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

これにより、キャンバスの左上隅が画面の中央に配置されます。次のセクションで説明する JavaScript の自動サイズ変更関数は、追加の CSS プロパティを操作して、ゲーム領域のサイズを変更し、ゲーム領域をウィンドウの中央揃えにします。

ゲームエリアのサイズはウィンドウの寸法に応じて自動的に変更されるため、gameArea div ブロックの子要素の寸法はピクセル単位ではなく、パーセンテージで指定します。ピクセル値では、親 div 要素の変化に合わせて内部要素を拡大縮小することはできません。ただし、ピクセルから始めて、好みのレイアウトができたらパーセンテージに変換することをおすすめします。

この例では、まず、高さ 300 ピクセル、幅 400 ピクセルのゲーム領域を使用します。キャンバスはゲームエリア全体を覆い、図 1 に示すように、半透明のデータパネルが高さ 24 ピクセルの下部に沿って配置されています。

gameArea 子要素の寸法(ピクセル単位)
図 1: gameArea の子要素の寸法(ピクセル単位)

これらの値をパーセンテージに変換すると、キャンバスの幅が 100%、高さが(ウィンドウではなく gameArea の)100% になります。24 を 300 で割ると、統計情報パネルの高さは 8% になり、ゲームエリアの下部を覆うため、図 2 のように幅も 100% になります。

gameArea の子要素のディメンション(パーセンテージ)
図 2: gameArea の子要素のディメンション(パーセンテージ)

ゲーム領域とその子要素のサイズを決定したら、2 つの内部要素の CSS プロパティを次のようにまとめることができます。

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

ゲームのサイズ変更

これで、サイズ変更されるウィンドウを処理する関数を作成する準備が整いました。まず、親の gameArea ドキュメント要素への参照を取得します。

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

正確な幅と高さにはこだわらないため、次に設定する必要がある情報は、幅と高さの比率です。先ほど、幅 400 ピクセル、高さ 300 ピクセルのゲーム領域の参照を使用して、アスペクト比を幅 4 単位、高さ 3 単位に設定したいことがわかります。

var widthToHeight = 4 / 3;

この関数はウィンドウのサイズが変更されるたびに呼び出されるため、ウィンドウの新しいディメンションを取得して、それに合わせてゲームのディメンションを調整できるようにする必要もあります。これは、ウィンドウの innerWidth プロパティと innerHeight プロパティを使用して探します。

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

必要な幅と高さの比率を決定したら、今度はウィンドウの現在の幅と高さの比率を求めることができます。

var newWidthToHeight = newWidth / newHeight;

これにより、図 3 に示すように、ゲームを縦横に画面全体に表示するか決めることができます。

アスペクト比を維持しながら gameArea 要素をウィンドウに収める
図 3: アスペクト比を維持しながら gameArea 要素をウィンドウに合わせる

目的のゲームエリアのシェイプがウィンドウのシェイプよりも幅が広い(かつ高さが短い)場合は、ウィンドウを水平方向に埋め、上下に余白を残す必要があります。同様に、目的のゲームエリアのシェイプがウィンドウのシェイプよりも高く、幅が狭い場合は、ウィンドウを垂直方向に埋め、左右に余白を残す必要があります。

これを行うには、現在のウィンドウの幅と高さの比率で、希望する幅と高さの比率をテストし、次のように適切な調整を行います。

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

ゲーム領域の幅と高さを調整したので、上部にマイナスのマージン(高さの半分、左側)にマイナスのマージン(幅の半分)を配置して中央揃えにする必要があります。すでに CSS によって gameArea div の左上隅がウィンドウのちょうど中央に配置されているため、ゲームエリアがウィンドウ内の中央に配置されます。

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

フォントサイズも自動的に調整できます。すべての子要素で em を使用している場合は、gameArea div ブロックの fontSize CSS プロパティをサイズで決まる値に設定するだけで済みます。

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

最後に、キャンバスの描画寸法を新しい幅と高さに合わせる必要があります。ゲームコードの残りの部分では、動的なキャンバスの解像度に対応するために、ゲームエンジンの寸法とキャンバスの描画寸法を別々にしておく必要があります。

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

完成したサイズ変更関数は次のようになります。

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

ここで、ウィンドウのサイズが変更されたとき、またはモバイル デバイスの場合は画面の向きが変更されたときに、これらの調整が自動的に行われるようにします。これらのイベントを処理するには、次のようにサイズ変更ゲーム() 関数を呼び出します。

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

ウィンドウのサイズが大きすぎる場合、または画面の向きが縦向きの場合は、幅がウィンドウの 100% になり、ウィンドウのサイズが広すぎる場合、または画面の向きが横向きの場合は、ウィンドウの高さが 100% になります。残りのサイズは、所定の幅と高さのアスペクト比に従ってサイズ設定される。

概要

Gopherwood Studios は、すべての HTML5 ゲームでこの構造のバージョンを使用しており、複数の画面解像度やさまざまなモバイル デバイスに対応するのに非常に有効であることが証明されています。さらに、全画面表示のブラウザを利用することで、多くのブラウザベースのゲームよりも、従来のデスクトップ ゲームに近い没入感をウェブゲームに実現できます。HTML5 とウェブの技術が進化し続けるなか、ウェブゲームにさらなるイノベーションがもたらされることを期待しています。