はじめに
2010 年の夏に、Sand Trap というゲームを作成しました。このゲームは、スマートフォン向けの HTML5 ゲームの大会に出品しました。しかし、ほとんどのスマートフォンでは、ゲームの一部しか表示されないか、ゲームが小さすぎてまったくプレイできませんでした。そこで、どんな解像度にも対応できるよう、ゲームをスムーズに調整できるようにしました。少しプログラムを書き換え、この記事で説明したアイデアを取り入れることで、デスクトップ ブラウザでもモバイル デバイスでも、あらゆる最新ブラウザでスケーリングできるゲームが完成しました。


このアプローチは Sand Trap でうまく機能したため、最新のゲームである 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 ピクセルにします。キャンバスはゲーム領域全体を覆い、半透明の統計パネルが下部に 24 ピクセルの高さで表示されます(図 1 を参照)。

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

ゲーム領域とその子要素のサイズを決定したので、次のように 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 のように、ゲームを縦向きまたは横向きで画面全体に表示するかどうかを決定できます。

希望するゲームエリアの形状がウィンドウの形状よりも幅が広く(高さが短い場合)、ウィンドウを横方向にいっぱいにして、上下に余白を残す必要があります。同様に、希望するゲーム領域の形状がウィンドウの形状よりも高く(幅が狭い場合)、ウィンドウを縦方向にいっぱいにして、左右に余白を残す必要があります。
そのためには、希望する幅と高さの比率を現在のウィンドウの幅と高さの比率でテストし、次のように適切に調整します。
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;
}
ウィンドウのサイズ変更時や、モバイル デバイスの場合は画面の向きが変更されたときに、これらの調整が自動的に行われるようにします。これらのイベントを次のように処理して、resizeGame() 関数を呼び出します。
window.addEventListener('resize', resizeGame, false);
window.addEventListener('orientationchange', resizeGame, false);
ウィンドウのサイズが大きくすぎる場合や、画面の向きが縦向きの場合は、幅をウィンドウの 100% にします。ウィンドウのサイズが広すぎる場合や、画面の向きが横向きの場合は、高さをウィンドウの 100% にします。残りのディメンションは、事前に決められた幅と高さのアスペクト比に従ってサイズが設定されます。
概要
Gopherwood Studios では、すべての HTML5 ゲームでこの構造のバージョンを使用しており、複数の画面解像度とさまざまなモバイル デバイスに対応するのに非常に役立っています。また、ブラウザを全画面表示することで、多くのブラウザベースのゲームよりも従来のパソコンゲームに近い没入感のあるエクスペリエンスをウェブゲームで実現できます。HTML5 とウェブ技術の進化に伴い、ウェブゲームのさらなるイノベーションが期待されます。