65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.96/5 (27投票s)

2013 年 9 月 6 日

CPOL

8分钟阅读

viewsIcon

201573

downloadIcon

4200

如何使用 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[^]
这是迷宫生成器生成的迷宫

Generated Maze

定位元素并在 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 方法绘制迷宫图像,我使用 beginPathclosePathrectfill 方法绘制矩形,并使用 beginPathclosePatharcfill 方法绘制圆。

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

currRectXcurrRectY 代表矩形的位置,而 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

Radians

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() 函数。我创建了这个方法来检查蓝色矩形是否可以移动。其背后的逻辑是:

  1. 检查蓝色矩形是否会移动到 canvas 的边界内。
  2. 如果是这样,则获取 x = destinationX、y = destinationY、width = 15、height = 15 的矩形的图像数据。我取 15 作为 widthheight,因为这就是蓝色矩形的大小。
  3. 使用 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 日:第一个版本
© . All rights reserved.