案例研究 - 自动调整 HTML5 游戏大小

Derek Detweiler
Derek Detweiler

简介

2010 年夏天,我们开发了《Sand Trap》,这款游戏参与了针对手机推出的 HTML5 游戏竞赛。但大多数手机要么仅显示游戏的一部分,要么游戏画面太小,导致游戏根本无法玩。因此,我们决定让游戏能够流畅地调整以匹配任何分辨率。经过一番重新编程和运用本文中概述的理念后,我们开发了一款可以扩展到任何现代浏览器的游戏,无论是在桌面设备还是移动设备上运行。

thwack 全屏的屏幕截图
浏览器窗口缩小显示效果的屏幕截图

这种方法在《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 所示。

gameArea 子元素的尺寸(以像素为单位)
图 1:gameArea 子元素的尺寸(以像素为单位)

将这些值转换为百分比会使画布的宽度为 100%,高度为 100%(即 gameArea,而不是窗口)。将 24 乘以 300 得到的统计数据面板的高度为 8%,由于它将覆盖游戏区域的底部,因此宽度也为 100%,如图 2 所示。

gameArea 子元素的尺寸(以百分比表示)
图 2:gameArea 子元素的尺寸(以百分比表示)

现在,您已经确定了游戏区域及其子元素的尺寸,您可以将内两者的 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;
}

现在,您希望每当窗口大小调整或屏幕方向改变(如果使用的是移动设备)时,系统都会自动做出这些调整。处理这些事件,只需让它们调用 sizeGame()函数即可,如下所示:

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

如果窗口的尺寸调整得过高或屏幕方向为竖向,您将采用窗口宽度的 100%;如果窗口大小被调整得过宽或屏幕方向为水平方向,则会将窗口高度调整为 100%。其余尺寸按照预定的宽高比调整。

摘要

Gopherwood Studios 在我们的所有 HTML5 游戏中都使用了这种结构,并且事实证明它对于适应多种屏幕分辨率和各种移动设备非常有用。此外,借助全屏浏览器,我们的网页游戏与许多基于浏览器的游戏相比,更接近传统桌面游戏的沉浸式体验。随着 HTML5 和网络技术的不断发展,我们期待网络游戏领域继续推出更多创新。