简介
2010 年夏天,我们制作了《Sand Trap》这款游戏,并将其提交到针对移动设备的 HTML5 游戏竞赛中。但大多数手机要么只显示游戏的一部分,要么游戏画面太小,完全无法玩。因此,我们决定让游戏能够流畅地调整以适应任何分辨率。经过一些重新编程并运用本文中介绍的想法,我们制作了一款可在任何新型浏览器(无论是在桌面设备还是移动设备上运行)中扩缩的游戏。


这种方法对《Sand Trap》非常有效,因此我们在最新的游戏《Thwack!》中也采用了相同的方法。游戏会自动调整屏幕分辨率,以适应全屏和自定义大小的窗口,如下面的屏幕截图所示。
实现此功能需要同时利用 CSS 和 JavaScript。使用 CSS 填充整个屏幕很简单,但 CSS 不允许您保持相同的宽高比,以防止画布和游戏区域被拉伸。这时,JavaScript 就派上用场了。您可以使用 JavaScript 重新调整文档元素的大小,并在窗口事件中触发大小调整。
准备页面
第一步是指定网页上将用于游戏的区域。如果您将其作为 div 块添加,则可以在其中放置其他标记或画布元素。正确设置后,这些子元素将继承父 div 块的缩放比例。
如果您的游戏区域分为两个部分(游戏区域和计分区域),则可能如下所示:
<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 的宽度和高度,而不是窗口的宽度和高度)。将 24 除以 300 可得出统计信息面板的高度为 8%,由于它将覆盖游戏区域的底部,因此其宽度也将为 100%,如图 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 游戏都使用了此结构的不同版本,事实证明,它非常适合适应多种屏幕分辨率和各种移动设备。此外,借助全屏浏览器,我们的 Web 游戏可提供身临其境的体验,这种体验与传统的桌面游戏更相似,而不是像许多基于浏览器的游戏那样。随着 HTML5 和 Web 技术的不断发展,我们期待 Web 游戏领域有更多创新。