使用 HTML5(和 JavaScript)创建一个带有计时器的迷宫游戏






4.96/5 (27投票s)
如何使用 HTML5 和 JavaScript 创建一个带数字计时器的迷宫,无需使用 Flash
引言
本文讲解如何使用 HTML5(canvas
元素和 JavaScript)创建一个带数字计时器的迷宫游戏,无需使用 Flash。
重要提示:如果您的浏览器不支持 JavaScript 或 HTML5(特别是 canvas
元素),此迷宫将无法工作。如果您下载了迷宫但无法正常工作,请尝试在线演示:https://programfox.github.io/HTML5-Maze/[^]。
另一重要提示:在我测试游戏时,它在 Firefox、Opera 和 Safari 中离线和在线运行良好。然而,在 Chrome 和 Internet Explorer 中,如果文件未托管,我无法移动蓝色矩形。这是出于安全原因:文件系统上的每个文件都有不同的来源,您可以从不同来源加载文件到您的 canvas
(这样会得到一个被污染的 canvas[^]),但是如果您尝试在被污染的 canvas 上使用 getImageData()
,Chrome 和 Internet Explorer 会报错。对于这些浏览器,您应该访问在线演示来尝试迷宫,或者将下载的文件托管在 localhost
服务器上。
下载迷宫后,请务必解压所有文件(HTML 文件和图片),否则迷宫将不会显示。
生成迷宫
创建迷宫的第一步是获取迷宫的图像。要创建迷宫图像,我使用了这个在线迷宫生成器:http://www.hereandabove.com/maze/mazeorig.form.html[^]
这是迷宫生成器生成的迷宫
定位元素并在 Canvas 元素上绘制迷宫
定位与绘制
下一步是定位您可以使用箭头键或 WASD 移动的矩形,以及定位终点(我迷宫中的一个圆圈)。在我的迷宫中,矩形的位置是 425, 3px
,大小是 15px x 15px
,圆的中心点是 524px, 122px
,半径是 7 px
。首先,您需要创建 canvas
元素。
<canvas width="616" height="556" id="mazecanvas">
Can't load the maze game, because your browser doesn't support HTML5.</canvas>
<noscript>JavaScript is not enabled. To play the game, you should enable it.</noscript>
如果浏览器不支持 HTML5,浏览器将显示“无法加载迷宫游戏...
”。迷宫的大小是 556 x 556,但我将其宽度设置为 616,因为我还添加了一个(数字)计时器。下一步是使用 JavaScript 将迷宫绘制到 canvas
元素上。我使用 drawImage
方法绘制迷宫图像,我使用 beginPath
、closePath
、rect
和 fill
方法绘制矩形,并使用 beginPath
、closePath
、arc
和 fill
方法绘制圆。
var canvas = document.getElementById("mazecanvas");
var context = canvas.getContext("2d");
var currRectX = 425;
var currRectY = 3;
var mazeWidth = 556;
var mazeHeight = 556;
var intervalVar;
function drawMazeAndRectangle(rectX, rectY) {
makeWhite(0, 0, canvas.width, canvas.height);
var mazeImg = new Image();
mazeImg.onload = function () { // when the image is loaded, draw the image,
// the rectangle and the circle
context.drawImage(mazeImg, 0, 0);
drawRectangle(rectX, rectY, "#0000FF", false, true);
context.beginPath();
context.arc(542, 122, 7, 0, 2 * Math.PI, false);
context.closePath();
context.fillStyle = '#00FF00';
context.fill();
};
mazeImg.src = "577080/maze.gif";
}
function drawRectangle(x, y, style) {
makeWhite(currRectX, currRectY, 15, 15);
currRectX = x;
currRectY = y;
context.beginPath();
context.rect(x, y, 15, 15);
context.closePath();
context.fillStyle = style;
context.fill();
}
要在您的 canvas
上绘制迷宫,请将此代码添加到脚本的底部。
drawMazeAndRectangle(425, 3); // { 425, 3 } is the position
// of the blue rectangle on the canvas
currRectX
和 currRectY
代表矩形的位置,而 intervalVar
是计时器的变量,我们将在稍后创建它。
rect()、fill() 和 stroke() 方法
在开始一个路径(使用 beginPath()
方法)之后,您可以使用 rect()
方法创建一个矩形。但是,如果您只使用 rect()
方法,则不会绘制任何矩形。要将创建的矩形绘制到您的 canvas
元素上,您应该使用 fill()
或 stroke()
方法来实际在 canvas
上绘制矩形。fill()
方法填充路径,stroke()
方法绘制路径。
rect() 方法的参数
名称 | 类型 | 描述 |
x | 数字 | 矩形左上角 x 坐标(像素) |
是 | 数字 | 矩形左上角 y 坐标(像素) |
w | 数字 | 矩形的宽度(像素) |
h | 数字 | 矩形的高度(像素) |
stroke() 和 fill() 方法没有参数。 |
arc() 方法
arc()
方法在 canvas
上绘制一个圆弧。绘制圆弧后,您应该调用 stroke()
或 fill()
方法。您可以使用此方法绘制一个圆或圆的一部分。
arc() 方法的参数
名称 | 类型 | 描述 |
x | 数字 | 圆弧中心点 x 坐标(像素) |
是 | 数字 | 圆弧中心点 y 坐标(像素) |
radius | 数字 | 圆弧的半径(像素) |
startAngle | 数字 | 起始角度(弧度)。0 位于圆弧圆的 3 点钟位置(见图)。 |
endAngle | 数字 | 结束角度(弧度) |
antiClockwise | 布尔值 | 如果圆弧应以逆时针方向(从开始到结束)绘制,则为 true 。如果圆弧应以顺时针方向(从开始到结束)绘制,则为 false 。 |
Image
makeWhite() 函数
在前一个代码片段中,您看到了我使用了一个 makeWhite()
函数。我创建了这个函数。它在 canvas
上绘制一个白色的矩形。那么,为什么我不使用 clear
方法呢?因为 clear()
方法会使给定矩形中的所有像素变得透明,而这在迷宫中是不需要的。这是 makeWhite()
函数的代码。
function makeWhite(x, y, w, h) {
context.beginPath();
context.rect(x, y, w, h);
context.closePath();
context.fillStyle = "white";
context.fill();
}
使用箭头键或 WASD 移动矩形
接下来要实现的是能够使用箭头键或 WASD 移动蓝色矩形。第一步是计算蓝色矩形新的 X 和 Y 坐标。第二步是移动矩形(如果可以移动),并在蓝色矩形到达终点(绿色圆圈)时显示“恭喜!
”消息。
function moveRect(e) {
var newX;
var newY;
var canMove;
e = e || window.event;
switch (e.keyCode) {
case 38: // arrow up key
case 87: // W key
newX = currRectX;
newY = currRectY - 3;
break;
case 37: // arrow left key
case 65: // A key
newX = currRectX - 3;
newY = currRectY;
break;
case 40: // arrow down key
case 83: // S key
newX = currRectX;
newY = currRectY + 3;
break;
case 39: // arrow right key
case 68: // D key
newX = currRectX + 3;
newY = currRectY;
break;
default: return;
}
movingAllowed = canMoveTo(newX, newY);
if (movingAllowed === 1) { // 1 means 'the rectangle can move'
drawRectangle(newX, newY, "#0000FF");
currRectX = newX;
currRectY = newY;
}
else if (movingAllowed === 2) { // 2 means 'the rectangle reached the end point'
clearInterval(intervalVar); // we'll set the timer later in this article
makeWhite(0, 0, canvas.width, canvas.height);
context.font = "40px Arial";
context.fillStyle = "blue";
context.textAlign = "center";
context.textBaseline = "middle";
context.fillText("Congratulations!", canvas.width / 2, canvas.height / 2);
window.removeEventListener("keydown", moveRect, true);
}
}
如果矩形到达终点,我将使用 clearInterval()
方法停止计时器(我们将在本文稍后设置)。然后,我在 canvas
上绘制“恭喜!
”,接着,我移除 keydown
事件监听器,因为蓝色矩形不应该再移动了。
在您能够移动蓝色矩形之前,应该添加一个 keydown
事件监听器。
drawMazeAndRectangle(425, 3); // this line is already added
window.addEventListener("keydown", moveRect, true); // add this at the bottom of your script
检查蓝色矩形是否可以移动:canMoveTo() 函数
在前一个代码片段中,您看到了一个 canMoveTo()
函数。我创建了这个方法来检查蓝色矩形是否可以移动。其背后的逻辑是:
- 检查蓝色矩形是否会移动到
canvas
的边界内。 - 如果是这样,则获取 x =
destinationX
、y =destinationY
、width = 15、height = 15 的矩形的图像数据。我取 15 作为width
和height
,因为这就是蓝色矩形的大小。 - 使用
for
循环查看所有像素:如果其中任何一个像素是黑色,则蓝色矩形无法移动。如果其中任何一个像素是亮绿色,则您已到达终点。
这是 canMoveTo()
函数的代码。
function canMoveTo(destX, destY) {
var imgData = context.getImageData(destX, destY, 15, 15);
var data = imgData.data;
var canMove = 1; // 1 means: the rectangle can move
if (destX >= 0 && destX <= mazeWidth - 15 && destY >= 0 &&
destY <= mazeHeight - 15) { // check whether the rectangle would move
// inside the bounds of the canvas
for (var i = 0; i < 4 * 15 * 15; i += 4) { // look at all pixels
if (data[i] === 0 && data[i + 1] === 0 && data[i + 2] === 0) { // black
canMove = 0; // 0 means: the rectangle can't move
break;
}
else if (data[i] === 0 && data[i + 1] === 255 &&
data[i + 2] === 0) { // lime: #00FF00
canMove = 2; // 2 means: the end point is reached
break;
}
}
}
else {
canMove = 0;
}
return canMove;
}
getImageData() 方法
使用 getImageData()
方法,您可以获取 canvas
特定区域的图像数据。此方法返回一个 ImageData
对象,其 data
属性是一个包含 RGBA 像素数据的数组。所以:
data[0]
包含ImageData
中**第一个**像素的 R(红色)值。data[1]
包含第一个像素的 G(绿色)值。data[2]
包含第一个像素的 B(蓝色)值。data[3]
包含第一个像素的 A(Alpha)值。data[4]
包含**第二个**像素的 R(红色)值。data[5]
包含第二个像素的 G(绿色)值。data[6]
包含第二个像素的 B(蓝色)值。data[7]
包含第二个像素的 A(Alpha)值。- 依此类推。
getImageData() 方法的参数
名称 | 类型 | 描述 |
sx | 数字 | 要获取图像数据的矩形左上角 x 坐标(像素)。 |
sy | 数字 | 要获取图像数据的矩形左上角 y 坐标(像素)。 |
sw | 数字 | 要获取图像数据的矩形的宽度(像素)。 |
sh | 数字 | 要获取图像数据的矩形的高度(像素)。 |
实现计时器
下一步是实现计时器。为此,我们使用 setInterval()
方法每秒减少时间。如果时间到了,我们将擦除整个 canvas
并显示红色“时间到!
”消息。
function createTimer(seconds) {
intervalVar = setInterval(function () {
makeWhite(mazeWidth, 0, canvas.width - mazeWidth, canvas.height);
if (seconds === 0) {
clearInterval(intervalVar);
window.removeEventListener("keydown", moveRect, true);
makeWhite(0, 0, canvas.width, canvas.height);
context.font = "40px Arial";
context.fillStyle = "red";
context.textAlign = "center";
context.textBaseline = "middle";
context.fillText("Time's up!", canvas.width / 2, canvas.height / 2);
return;
}
context.font = "20px Arial";
if (seconds <= 10 && seconds > 5) {
context.fillStyle = "orangered";
}
else if (seconds <= 5) {
context.fillStyle = "red";
}
else {
context.fillStyle = "green";
}
context.textAlign = "center";
context.textBaseline = "middle";
var minutes = Math.floor(seconds / 60);
var secondsToShow = (seconds - minutes * 60).toString();
if (secondsToShow.length === 1) {
secondsToShow = "0" + secondsToShow; // if the number of seconds is
// '5' for example, make sure that
// it is shown as '05'
}
context.fillText(minutes.toString() + ":" + secondsToShow,
mazeWidth + 30, canvas.height / 2);
seconds--;
}, 1000);
}
在 createTimer()
函数中,我创建了一个匿名函数,该函数将每秒调用一次。setInterval()
方法返回一个唯一的间隔 ID,如果您不再希望该匿名函数每秒被调用一次,您可以将此 ID 传递给 clearInterval()
方法。在匿名函数的首行,我清除了 canvas
的一个特定区域:我只清除了计时器所在的区域。在这个迷宫中,canvas
分为两部分:一部分显示迷宫,另一部分显示计时器。
如果 seconds
等于零,我调用 clearInterval()
方法,因为我想停止匿名函数每秒的执行。然后,我移除 keydown
事件监听器,因为它不再需要移动蓝色矩形(因为时间到了)。接着,我清除整个 canvas 来在其上绘制“时间到
”。
如果 seconds
还不等于零,我将在 canvas
上显示剩余时间。如果剩余时间超过 10 秒,剩余时间的颜色为绿色。如果剩余时间小于或等于 10 秒但大于 5 秒,则颜色为橙红色,如果剩余时间小于或等于 5 秒,则颜色为红色。
首先,我们计算剩余的分钟数。为此,我们计算 seconds / 60
的下限值,然后计算剩余秒数,即 seconds - minutes * 60
。例如,如果秒数是 5,我们确保它显示为 05
,通过在 5 前面加上一个零(如果需要)。
要实际创建计时器,请将此行添加到脚本末尾。
drawMazeAndRectangle(425, 3); // added already
window.addEventListener("keydown", moveRect, true); // added already
createTimer(120); // add this line
// 120 seconds -> 2 minutes
历史
- 2013 年 10 月 30 日:添加了关于 Chrome 和 Internet Explorer 安全错误的信息
- 2013 年 9 月 6 日:第一个版本