https://thoughtbot.com/blog/html5-canvas-snake-game

保罗·詹森 

2009 年 12 月 31 日 更新于 20193 月 9 日

这篇文章最初发表在 New Bamboo 博客上,之前New Bamboo 在伦敦加入了thoughtbot。


介绍

HTML5 规范目前处于草案阶段,但已经被 Apple 的 Safari、Mozilla Firefox 和 Opera 等网络浏览器支持。该规范的一个很棒的新功能是 canvas 标签,这是一个与 javascript api 结合的 html 元素,允许您绘制图形并为其设置动画。我将通过使用 canvas 标签重新制作 Snake 手机游戏来演示您可以使用 canvas 标签做的许多很酷的事情之一。您可以查看蛇源 并玩蛇游戏。

入门

首先,您需要使用以下标记创建一个 html 文件:

<html><head><title>Snake</title><link href='/reset.css' rel='stylesheet'><link href='/master.css' rel='stylesheet'><script src='snake.js' type='text/javascript'></script></head><body><canvas id="canvas" width="400" height="300"></canvas></body>
</html>

对于 reset.css 样式表:

html, body div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a abbr, acronym, address, big, cite, code, del, dfn, em, font, img, ins, kdb, q, s, samp, small, strike, strong, sub, sup, tt, var, b, u, i, center, dl, dt, dd, ol, ul, li fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td {margin: 0;padding: 0;font-size: 100%;vertical-align: baseline;border: 0;outline: 0;background: transparent;
}ol, ul {list-style: none;
}blockquote, q {quotes: none;
}a, a:hover {text-decoration: none;
}table {border-collapse: collapse;border-spacing: none;
}

对于 master.css 样式表:

body {background: #111;
}canvas {border: solid 1px red;
}

这将设置一个黑色网页,在 canvas 标签周围有一个红色边框。

检测 Canvas 支持

不幸的是,并不是所有的网络浏览器都支持画布(咳咳IE),所以我们应该做的第一件事是检查浏览器是否支持它。我们将在Snake.js 文件中放置用于检查画布支持的代码。

function checkSupported() {canvas = document.getElementById('canvas');if (canvas.getContext){ctx = canvas.getContext('2d');// Canvas is supported} else {// Canvas is not supportedalert("We're sorry, but your browser does not support the canvas tag. Please use any web browser other than Internet Explorer.");}
}

为了让这段代码在网页加载时执行,调整 body 标签,让它看起来像这样:

<body onload="checkSupported();">

画画

现在,我对在画布上绘制对象感兴趣,即蛇。从我的第一部手机(诺基亚 3210)中我记得,蛇是一串正方形,你可以在四个方向上移动,所以让我们从画一个正方形开始:

// This sets the fill color to red
ctx.fillStyle = "rgb(200,0,0)";// This sets some variables for demonstration purposes
var x = 50;
var y = 50;
var width = 10;
var height = 10;// This draws a square with the parameters from the variables set above
ctx.fillRect(x, y, width, height);

如果您将此 javascript 代码放在 IF 块的 canvas-supported 部分,那么您应该会看到以下结果:

画布左上角某处的一个漂亮的小红色方块。这向您展示了在画布上绘制矩形对象是多么容易。还有一些选项可以绘制矩形的笔触,以及清除矩形:

strokeRect(x,y,w,h);
clearRect(x,y,w,h);

移动

现在我知道如何绘制方块了,我想移动那个方块,或者至少给人一种移动方块的印象。蛇可以上下左右移动。在 javascript 中,我们想要捕获被击中的键盘键,并使用这些键与画布进行交互。

为了捕获击键,我使用以下 javascript 代码:

document.onkeydown = function(event) {var keyCode;if(event == null){keyCode = window.event.keyCode;}else{keyCode = event.keyCode;}switch(keyCode){// leftcase 37:// action when pressing left keybreak;// upcase 38:// action when pressing up keybreak;// rightcase 39:// action when pressing right keybreak;// downcase 40:// action when pressing down keybreak;default:break;}
}

此代码设置了捕获用户按下键盘上的向上、向下、向左和向右键的能力,提供在画布上移动 Snake 所需的控件。

当我们按下一个键时,我们想在那个方向上绘制一个新的正方形。为了匹配方向,我们需要知道以下几点:

  • 蛇头现在的位置是什么
  • 我们要往哪个方向走,这是由按键给出的

所以,我们需要一个变量来记录蛇头的位置,当我们按下一个键时,我们绘制一个新的正方形,它的位置是方向与蛇头当前位置的偏移量。

在IF块的canvas-supported部分,填写如下代码:

// The current position of the Snake's head, as xy coordinates
this.currentPosition = [50, 50];// Sets the grid dimensions as one value
this.gridSize = 10;

我们将使用这些全局变量来设置蛇的起点,以及网格大小,它定义了它们在一个方向上移动的距离,以及每个方块的大小。

现在,我们要调整 keydown switch 语句以生成新的 xy 坐标,然后我们将其提供给 fillRect 参数以创建一个新正方形:

switch(keyCode)
{// leftcase 37:// set new position, and draw square at that position.currentPosition['x'] = currentPosition['x'] - gridSize;ctx.fillRect(currentPosition['x'], currentPosition['y'], gridSize, gridSize);break;// upcase 38:currentPosition['y'] = currentPosition['y'] - gridSize;ctx.fillRect(currentPosition['x'], currentPosition['y'], gridSize, gridSize);break;// rightcase 39:currentPosition['x'] = currentPosition['x'] + gridSize;ctx.fillRect(currentPosition['x'], currentPosition['y'], gridSize, gridSize);break;// downcase 40:currentPosition['y'] = currentPosition['y'] + gridSize;ctx.fillRect(currentPosition['x'], currentPosition['y'], gridSize, gridSize);break;default:break;
}

您应该会看到如下所示的内容:

如果你成功了,你应该能够画一条可以在所有四个方向上走的线。您还可以更改在 IF 块中绘制初始方块的代码,使其看起来像这样:

ctx.fillRect(currentPosition['x'], currentPosition['y'], gridSize, gridSize);

您会注意到该行通常在代码中的几个地方重复。为了方便起见,我将创建一个函数,这对于 1 行方法来说似乎有点多,但会使代码更易于阅读:

function drawSnake() {ctx.fillRect(currentPosition['x'], currentPosition['y'], gridSize, gridSize);
}

您可以浏览代码并根据需要替换这些行。

蛇一直在移动

玩贪吃蛇手机游戏时,您可以控制蛇的方向,但不是速度,保持恒定,这意味着您必须专注于引导蛇绕过该区域,以免撞到自己。这有点像澳大利亚的那个人,当巡航控制系统锁定时,他不得不驾驶他的车。

为了提供这个功能,我们需要做两件事:

  • 有一个新的变量记录蛇行进的方向
  • 制作一个定时循环,该循环将执行一个将蛇向该方向移动的函数

所以,首先,我们创建一个新的全局变量,称为方向:

direction = 'right';

现在,我们需要执行一个让 Snake 朝那个方向移动的动画,把它放在画布支持的 IF 块中:

setInterval(moveSnake,100);

现在,将此函数添加到代码底部的某处:

function moveSnake(){switch(direction){case 'up':currentPosition['y'] = currentPosition['y'] - gridSize;drawSnake();break;case 'down':currentPosition['y'] = currentPosition['y'] + gridSize;drawSnake();break;case 'left':currentPosition['x'] = currentPosition['x'] - gridSize;drawSnake();break;case 'right':currentPosition['x'] = currentPosition['x'] + gridSize;drawSnake();break;}
}

你应该看到 Snake 向右移动,这很好,但似乎尽管我们坚持,Snake 还是决定向右走,实际上是从画布中出来的。这突出了 a) 我们没有将击键与方向的变化相关联,并且 b) 蛇可以自由地逃离画布,这是不应该的。后一个问题我们稍后会处理,但首先,让我们将击键与方向联系起来。

只需使用按键设置方向变量:

switch(keyCode)
{// leftcase 37:// set new position, and draw square at that position.direction = 'left';currentPosition['x'] = currentPosition['x'] - gridSize;drawSnake();break;
}

现在,让我们设置 Snake 移动的边界。

设置移动边界

我们希望我们的 Snake 的运动被限制在 canvas 标签的边界内,因此我们需要执行以下操作:

  • 找出画布边界是什么
  • 检测snake的位置是否会超出边界
  • 如果可以,将其重定向到另一个方向

第一步很简单:

canvas.width;
canvas.height;

我们为 canvas 标签定义的 html 属性在 javascript 中可用,因此我们将以这种方式引用它们。当蛇向下或向右移动时,我们要检查新位置的 x 或 y 值是否大于画布的宽度或高度。如果是,那么我们不绘制新正方形。相反,我们选择朝着不同的方向前进,以保持势头。如果蛇向上或向左移动时当前位置的 x 或 y 值小于 0,则相同的原理生效。

我不得不承认,此时我借此机会重构了我的代码,以便将逻辑保持在一个地方。我首先将计算的上、下、左、右位置转换为函数:

function leftPosition(){return currentPosition['x'] - gridSize;
}function rightPosition(){return currentPosition['x'] + gridSize;
}function upPosition(){return currentPosition['y'] - gridSize;
}function downPosition(){return currentPosition['y'] + gridSize;
}

然后将计算出的位置函数用于逻辑以确定蛇是否可以向某个方向移动,以及设置要移动到的当前位置。

下一步是在命名函数中包装边界逻辑、方向设置器、当前位置设置器和 drawShape 函数调用:

function moveUp(){if (upPosition() >= 0) {executeMove('up', 'y', upPosition());}
}function moveDown(){if (downPosition() < canvas.height) {executeMove('down', 'y', downPosition());}
}function moveLeft(){if (leftPosition() >= 0) {executeMove('left', 'x', leftPosition());}
}function moveRight(){if (rightPosition() < canvas.width) {executeMove('right', 'x', rightPosition());}
}function executeMove(dirValue, axisType, axisValue) {direction = dirValue;currentPosition[axisType] = axisValue;drawSnake();
}

前四种方法的设置类似。第一行调用 IF 语句来检查在该方向上的移动是否会设置画布区域内的当前位置。如果是,则执行第二行,将 3 个参数传递给 executeMove 函数。

executeMove 函数是对封装在 moveUp/moveDown/moveLeft/moveRight 函数中的相同代码的重构。它设置方向,然后根据轴类型和传入的轴值设置currentPosition,然后绘制代表蛇头的正方形。

最后,我们现在可以清理很多常见的代码,所以现在我们的按键调用和自动蛇形运动看起来像这样:

document.onkeydown = function(event) {var keyCode;if(event == null){keyCode = window.event.keyCode;}else{keyCode = event.keyCode;}switch(keyCode){// leftcase 37:moveLeft();break;// upcase 38:moveUp();break;// rightcase 39:moveRight();break;// downcase 40:moveDown();break;default:break;}
}function moveSnake(){switch(direction){case 'up':moveUp();break;case 'down':moveDown();break;case 'left':moveLeft();break;case 'right':moveRight();break;}
}

漂亮干净。您现在应该看到的是,Snake 会自己移动,当它到达画布边缘时,它实际上会停在那个点。您可以通过在蛇到达边缘后等待几秒钟来测试这一点,然后向蛇的任一侧按一个键,您会注意到蛇然后立即从该点向给定方向移动,它没有从画布中逃脱。

到目前为止,这很好,但是我们想要发生的是,蛇一旦碰到边缘就会自动移动到任一侧,并且不能走得更远。我们通过使用以下代码来做到这一点:

function whichWayToGo(axisType){if (axisType=='x') {a = (currentPosition['x'] > canvas.width / 2) ? moveLeft() : moveRight();} else {a = (currentPosition['y'] > canvas.height / 2) ? moveUp() : moveDown();}
}

此代码采用轴类型,然后根据当前位置计算出要走的路。因此,例如,如果蛇碰到画布的右边缘,则该函数会被传递一个y参数,告诉该函数它需要向上或向下移动。然后此函数将检查当前位置是在画布中间上方还是下方。如果它在上面,它将向下移动,反之亦然。示例原则适用于水平情况。

因此,使用该功能,调整 moveUp/moveDown/moveLeft/moveRight 功能,如下所示:

function moveUp(){if (upPosition() >= 0) {executeMove('up', 'y', upPosition());} else {whichWayToGo('x');}
}function moveDown(){if (downPosition() < canvas.height) {executeMove('down', 'y', downPosition());} else {whichWayToGo('x');}
}function moveLeft(){if (leftPosition() >= 0) {executeMove('left', 'x', leftPosition());} else {whichWayToGo('y');}
}function moveRight(){if (rightPosition() < canvas.width) {executeMove('right', 'x', rightPosition());} else {whichWayToGo('y');}
}

如果您现在重新加载页面,您将看到 Snake 在碰到边缘时会继续移动,并巧妙地选择向离边缘最远的方向前进。

拉着蛇的身体

它开始到达那里,蛇看起来很棒,甚至有点聪明,因为他或她看到了优势。一切都很好,但现在蛇的背面不动,或者引用荷马辛普森的话(我没有骗你,这是早期的一集中),“玛吉,你的屁股不会动”不要退出”。它需要拖动它的身体,而不是画一条无限长的线,这就是我们要做的。

您会注意到,我在想出 drawSnake() 函数名称时已经考虑过这一点。我想要一个占位符,我可以在其中绘制 Snake 的身体。还记得我说蛇看起来像“一串正方形”吗?那正是蛇的样子,一串正方形。

这个想法是有一个 xy 坐标数组,代表 Snake 身体占据的所有方形点:

snakeBody = [];

并调整 drawSnake 函数,使其看起来像这样:

function drawSnake() {snakeBody.push([currentPosition['x'], currentPosition['y']]);ctx.fillRect(currentPosition['x'], currentPosition['y'], gridSize, gridSize);if (snakeBody.length > 3) {var itemToRemove = snakeBody.shift();ctx.clearRect(itemToRemove[0], itemToRemove[1], gridSize, gridSize);}
}

这里发生的事情是,每次调用 drawSnake 时,该函数都会将新位置添加到 snakeBody 数组中,并将其保存在内存中。然后,当 SnakeBody 数组的大小增长到 3 以上时,将删除 SnakeBody 数组的第一个元素,并删除矩形。你现在应该看到的是一条小蛇四处走动,它的身体很小,而不是一条无限长的线。

吃东西,长得更久

现在我们需要向游戏中添加 1 个当前未以任何方式实现的元素;我们需要食物。在 Snake 游戏中,小方块食物会在该区域周围弹出,当 Snake 越过它时,它会变长,因此被称为“食物”。要将此元素添加到游戏中,我们需要执行以下操作:

  • 创建一个在画布上绘制食物方块的方法,随机插入
  • 没有被蛇的身体占据的位置当蛇经过时
  • 那个食物项目,增加蛇的身体长度,并放置一个食物方块
  • 别的地方

我们已经知道如何绘制正方形,但我们如何将它们放置在一个随机位置,一个不被蛇占据的位置?前一个问题通过使用Math库解决,后一个问题通过检测snakeBody数组是否包含建议用于定位食物方块的随机点数组来解决。这是代码:

function makeFoodItem(){suggestedPoint = [Math.floor(Math.random()*(canvas.width/gridSize))*gridSize, Math.floor(Math.random()*(canvas.height/gridSize))*gridSize];if (snakeBody.some(hasPoint)) {makeFoodItem();} else {ctx.fillStyle = "rgb(10,100,0)";ctx.fillRect(suggestedPoint[0], suggestedPoint[1], gridSize, gridSize);};
}function hasPoint(element, index, array) {return (element[0] == suggestedPoint[0] &amp;&amp; element[1] == suggestedPoint[1]);
}

它看起来很丑,可能很丑,所以我必须承认,我不是 Javascript 大师(这就是我时不时缠着 Ismael 的原因),但我会解释逻辑。首先,在我们做任何事情之前,我们需要在画布中生成一个随机点,一个由两个数字组成的数组,在画布的宽度和高度范围内,但也要四舍五入,以便随机数可以被 gridSize 整除,这恰好是 10。

其次,我们需要检查SuggestedPoint是否已经被Snake的身体占据了。不幸的是,在javascript 中比较数组数组并不是一个简单的过程,所以我必须创建一个名为hasPoint 的函数,它使用Array.some,如果任何snakeBody 数组的数组与suggestPoint 数组直接匹配,它将返回true。

如果snakeBody 的集合中已经有suggestPoint,那么我们再次运行整个方法,并重复这个过程,直到发生另一种情况,在这种情况下,将填充颜色设置为绿色,然后绘制该颜色的正方形,使用建议点的坐标。

特别注意,确保将红色 fillcolor 线移动到 drawSnake 函数,如下所示:

function drawSnake() {snakeBody.push([currentPosition['x'], currentPosition['y']]);ctx.fillStyle = "rgb(200,0,0)";
}

如果你不这样做,你会挠头想知道为什么你心爱的蛇刚刚变绿了。把makeFoodItem();canvas支持的初始启动代码放在IF块中,你应该看到的是地图上的一个小绿色方块,真棒。为了获得额外的奖励,移动你的 Snake 让它吃掉食物......

没了!但是等等,我们没有编写任何代码来删除我们吃完后的绿色方块?

有意,不,无意,是。通过删除尾随方块来帮助给人以蛇移动的印象的小代码也清除了蛇所在的区域,包括绿色方块。那有多好?

我们已经接近了,现在我们需要让吃过零食的食物重新出现在不同的地方,我们需要让 Snake 变长。首先,首先将此代码放在画布支持的 IF 语句块中,准确地按以下顺序:

snakeLength = 3;
makeFoodItem();drawSnake();

makeFoodItem()函数必须出现在 drawSnake 函数之前,否则代码会因为缺少建议点变量而中断。您还会注意到我们已经定义了一个 snakeLength 变量,其值为 3,该值用于确定何时开始碰撞 snakeBody 数组中的最后一个点?你可以猜出这将走向何方,答案如下:

function drawSnake() {snakeBody.push([currentPosition['x'], currentPosition['y']]);ctx.fillStyle = "rgb(200,0,0)";ctx.fillRect(currentPosition['x'], currentPosition['y'], gridSize, gridSize);if (snakeBody.length > snakeLength) {var itemToRemove = snakeBody.shift();ctx.clearRect(itemToRemove[0], itemToRemove[1], gridSize, gridSize);}if (currentPosition['x'] == suggestedPoint[0] &amp;&amp; currentPosition['y'] == suggestedPoint[1]) {makeFoodItem();snakeLength += 1;}
}

您会注意到 drawSnake 方法有 2 处更改。首先,它现在引用了snakeLength变量而不是3,重要的是,currentPosition变量与suggestPoint变量的IF语句比较允许我们捕获Snake何时吃食物,然后我们生成下一个食物项目画布,我们增加snakeLength变量,这使得Snake变长。刷新浏览器并查看它的运行情况。

游戏结束

我们已经非常接近完成游戏了,除了一件小事,我们还没有考虑到 Snake 撞到自己的情况,以及最终的游戏案例。现在,如果 Snake 越过自己,它只会继续它的路径。我们在这里需要做的是:

  • 跟踪蛇何时穿过自己
  • 一旦发生就结束游戏

值得庆幸的是,为了解决第一个问题,我们已经有了一个解决方案:

function drawSnake() {if (snakeBody.some(hasEatenItself)) {gameOver();return false;}
}function hasEatenItself(element, index, array) {return (element[0] == currentPosition['x'] &amp;&amp; element[1] == currentPosition['y']);
}

hasEatenItself函数检查该currentPosition点是否已被 Snake 的身体占据(即snakeBody数组包含 currentPosition),如果是,它将调用一个名为 的新函数gameOver

function gameOver() {var score = (snakeLength - 3)*10;clearInterval(interval);snakeBody = [];snakeLength = 3;allowPressKeys = false;alert("Game Over. Your score was " + score);ctx.clearRect(0, 0, canvas.width, canvas.height);
}

gameOver 函数做了几件事:

  • 计算分数并显示
  • 告诉 Snake 停止动画(移动)
  • 重置蛇
  • 将 allowPressKeys 变量轻弹为 false(用法稍后解释)
  • 清除画布

allowPressKeys 变量在 IF 语句 canvas-supported 块中定义,如下所示:

allowPressKeys = true;

并用于禁用按键功能,如下所示:

document.onkeydown = function(event) {if (!allowPressKeys){return null;}
}

这样,一旦游戏结束,用户就不能移动 Snake。至此,我们已经有效地复制了贪吃蛇游戏。就是这样。菲尼托。

一点点吐痰和抛光

不完全的。是的,我们可以发布它并且它可以工作,但是用户如何启动新游戏?或暂停它?那么实时显示比分呢?在我们称这个游戏完成之前,我们将添加更多功能。

在我们添加重启和暂停功能之前,我们在游戏中有一个小的可用性缺陷。如果你按下一个与 Snake 方向相反的键,游戏就会结束,因为它实际上是在试图超越自己。从逻辑上讲,这是 Snake 的正确结果,但是当您尝试执行急转弯时不小心倒退或按错误顺序击键时,就会变得很烦人。

这里的解决方案是忽略任何与 Snake 方向相反的击键,如下所示:

switch(keyCode)
{case 37:if (direction != "right"){moveLeft();}break;case 38:if (direction != "down"){moveUp();}break;case 39:if (direction != "left"){moveRight();}break;case 40:if (direction != "up"){moveDown();}break;
}

如果 Snake 朝相反的方向移动,则您根本不执行移动。现在尝试一下,如果您试图让它向后移动,您会注意到 Snake 会继续沿其当前方向前进。

接下来,我们希望能够暂停游戏。

function pause(){clearInterval(interval);allowPressKeys = false;
}

这将停止移动蛇的动画,并禁用按键动作,有效地为用户暂停游戏。要恢复游戏,会发生相反的情况:

function play(){interval = setInterval(moveSnake,100);allowPressKeys = true;
}

隔离此功能后,您现在可以干掉其他地方的一些代码:

function gameOver(){var score = (snakeLength - 3)*10;pause();alert("Game Over. Your score was "+ score);ctx.clearRect(0,0, canvas.width, canvas.height);
}

我们现在可以创建一个可用于初始化新游戏的开始函数。

if (canvas.getContext){ctx = canvas.getContext('2d');this.gridSize = 10;start();
}function start(){ctx.clearRect(0,0, canvas.width, canvas.height);this.currentPosition = {'x':50, 'y':50};snakeBody = [];snakeLength = 3;makeFoodItem();drawSnake();direction = 'right';play();
}

start 函数封装了设置新游戏所需的所有方法。但是,在另一个游戏正在进行时执行游戏会导致错误;游戏将重新开始,但结果蛇将开始越来越快地移动。我们需要的是一种删除现有区间变量的方法,然后开始游戏:

function restart(){pause();start();
}

您可以使用 Safari 中的错误控制台或使用 Firebug 的 Firefox javascript 控制台来测试此方法。

现在是向网页添加一些按钮以向用户提供对这些功能的访问的好时机。就上下文而言,默认情况下隐藏重新启动和恢复按钮并默认显示暂停按钮是有意义的。一旦按下暂停按钮,它就会被隐藏,而其他 2 个按钮就会显示出来。

<body onload='checkSupported();'><span id='logo'>SNAKE</span><div id='play_menu'><button onclick="pause();document.getElementById('play_menu').style.display='none';document.getElementById('pause_menu').style.display='block';">Pause</button></div><div id='pause_menu'><button onclick="restart();document.getElementById('play_menu').style.display='block';document.getElementById('pause_menu').style.display='none';">Restart</button><button onclick="play();document.getElementById('play_menu').style.display='block';document.getElementById('pause_menu').style.display='none';">Resume</button></div>

还要添加此 css 规则以默认显示徽标和隐藏暂停菜单按钮:

#logo {font-family: helvetica;font-size: 1.4em;
}#pause_menu {display: none;
}

我们现在可以在用户玩游戏时向他们显示与上下文相关的按钮,哦,以及游戏结束时:

<div id='restart_menu'><button onclick="restart();document.getElementById('play_menu').style.display='block';document.getElementById('restart_menu').style.display='none';">Restart</button>
</div>

和CSS:

#pause_menu, #restart_menu {display: none;
}

和蛇js:

function gameOver(){var score = (snakeLength - 3)*10;pause();alert("Game Over. Your score was "+ score);ctx.clearRect(0,0, canvas.width, canvas.height);document.getElementById('play_menu').style.display='none';document.getElementById('restart_menu').style.display='block';
}

现在,当游戏结束时,您可以轻松地重新开始游戏。我不会说这是执行此操作的最有效方法,但它确实有效。最后要做的是实时显示分数。

<div id="score_container">Score:<span id="score">0</span>
</div>

css:

#score_container {float: right;display: inline;
}

蛇js代码:

function updateScore(){var score = (snakeLength - 3)*10document.getElementById('score').innerText = score;
}

我们有一个更新页面分数的新方法,我们在两个地方使用它,第一个是 drawSnake 函数,当 Snake 吃食物时:

function drawSnake() {if (snakeBody.some(hasEatenItself)) {gameOver();return false;}snakeBody.push([currentPosition['x'], currentPosition['y']]);ctx.fillStyle = "rgb(200,0,0)";ctx.fillRect(currentPosition['x'], currentPosition['y'], gridSize, gridSize);if (snakeBody.length > snakeLength) {var itemToRemove = snakeBody.shift();ctx.clearRect(itemToRemove[0], itemToRemove[1], gridSize, gridSize);}if (currentPosition['x'] == suggestedPoint[0] &amp;&amp; currentPosition['y'] == suggestedPoint[1]) {makeFoodItem();snakeLength += 1;updateScore();}
}

当游戏开始/重新启动时:

function start(){ctx.clearRect(0,0, canvas.width, canvas.height);this.currentPosition = {'x':50, 'y':50};snakeBody = [];snakeLength = 3;updateScore();makeFoodItem();drawSnake();direction = 'right';play();
}

现在,当您玩游戏时,您应该会看到吃饭时更新的分数,并在按下重新启动按钮时重置:

这就对了。我们有一个功能齐全的 Snake 游戏,完全使用 HTML5 的 canvas 标签、一些 CSS 和一些 javascript 编写。想看看它应该是什么样子的吗?[在这里尝试完成的游戏][蛇游戏]。

snake.html

<html><head><title>Snake</title><link href='reset.css' rel='stylesheet'><link href='master.css' rel='stylesheet'></head><body onload='checkSupported();'><span id='logo'>SNAKE</span><div id='play_menu'><button onclick="pause();document.getElementById('play_menu').style.display = 'none';document.getElementById('pause_menu').style.display = 'block';">Pause</button></div><div id='pause_menu'><button onclick="restart();document.getElementById('play_menu').style.display = 'block';document.getElementById('pause_menu').style.display = 'none';">Restart</button><button onclick="play();document.getElementById('play_menu').style.display = 'block';document.getElementById('pause_menu').style.display = 'none';">Resume</button></div><div id='restart_menu'><button onclick="restart();document.getElementById('play_menu').style.display = 'block';document.getElementById('restart_menu').style.display = 'none';">Restart</button></div><div id="score_container">Score:<span id="score">0</span></div><canvas id="canvas" width="400" height="300"></canvas><script src='snake.js' type='text/javascript'></script></body>
</html>

snake.js

function checkSupported() {canvas = document.getElementById('canvas');if (canvas.getContext) {ctx = canvas.getContext('2d');this.gridSize = 10;start();} else {alert("We're sorry, but your browser does not support the canvas tag. Please use any web browser other than Internet Explorer.");}
}function start() {ctx.clearRect(0, 0, canvas.width, canvas.height);this.currentPosition = {'x': 50, 'y': 50};snakeBody = [];snakeLength = 3;updateScore();makeFoodItem();drawSnake();direction = 'right';play();
}function restart() {pause();start();
}function pause() {clearInterval(interval);allowPressKeys = false;
}function play() {interval = setInterval(moveSnake, 100);allowPressKeys = true;
}function drawSnake() {if (snakeBody.some(hasEatenItself)) {gameOver();return false;}snakeBody.push([currentPosition['x'], currentPosition['y']]);ctx.fillStyle = "rgb(200,0,0)";ctx.fillRect(currentPosition['x'], currentPosition['y'], gridSize, gridSize);if (snakeBody.length > snakeLength) {var itemToRemove = snakeBody.shift();ctx.clearRect(itemToRemove[0], itemToRemove[1], gridSize, gridSize);}if (currentPosition['x'] == suggestedPoint[0] && currentPosition['y'] == suggestedPoint[1]) {makeFoodItem();snakeLength += 1;updateScore();}
}function leftPosition() {return currentPosition['x'] - gridSize;
}function rightPosition() {return currentPosition['x'] + gridSize;
}function upPosition() {return currentPosition['y'] - gridSize;
}function downPosition() {return currentPosition['y'] + gridSize;
}function whichWayToGo(axisType) {if (axisType == 'x') {a = (currentPosition['x'] > canvas.width / 2) ? moveLeft() : moveRight();} else {a = (currentPosition['y'] > canvas.height / 2) ? moveUp() : moveDown();}
}function moveUp() {if (upPosition() >= 0) {executeMove('up', 'y', upPosition());} else {whichWayToGo('x');}
}function moveDown() {if (downPosition() < canvas.height) {executeMove('down', 'y', downPosition());} else {whichWayToGo('x');}
}function moveLeft() {if (leftPosition() >= 0) {executeMove('left', 'x', leftPosition());} else {whichWayToGo('y');}
}function moveRight() {if (rightPosition() < canvas.width) {executeMove('right', 'x', rightPosition());} else {whichWayToGo('y');}
}function executeMove(dirValue, axisType, axisValue) {direction = dirValue;currentPosition[axisType] = axisValue;drawSnake();
}function makeFoodItem() {suggestedPoint = [Math.floor(Math.random() * (canvas.width / gridSize)) * gridSize, Math.floor(Math.random() * (canvas.height / gridSize)) * gridSize];if (snakeBody.some(hasPoint)) {makeFoodItem();} else {ctx.fillStyle = "rgb(10,100,0)";ctx.fillRect(suggestedPoint[0], suggestedPoint[1], gridSize, gridSize);};
}function hasPoint(element, index, array) {return (element[0] == suggestedPoint[0] && element[1] == suggestedPoint[1]);
}function hasEatenItself(element, index, array) {return (element[0] == currentPosition['x'] && element[1] == currentPosition['y']);
}function gameOver() {var score = (snakeLength - 3) * 10;pause();alert("Game Over. Your score was " + score);ctx.clearRect(0, 0, canvas.width, canvas.height);document.getElementById('play_menu').style.display = 'none';document.getElementById('restart_menu').style.display = 'block';
}function updateScore() {var score = (snakeLength - 3) * 10document.getElementById('score').innerText = score;
}document.onkeydown = function (event) {if (!allowPressKeys) {return null;}var keyCode;if (event == null){keyCode = window.event.keyCode;} else{keyCode = event.keyCode;}switch (keyCode){case 37:if (direction != "right") {moveLeft();}break;case 38:if (direction != "down") {moveUp();}break;case 39:if (direction != "left") {moveRight();}break;case 40:if (direction != "up") {moveDown();}break;default:break;}
}function moveSnake() {switch (direction) {case 'up':moveUp();break;case 'down':moveDown();break;case 'left':moveLeft();break;case 'right':moveRight();break;}
}

master.css

body {background: #111;
}canvas {border: solid 1px red;
}
#logo {font-family: helvetica;font-size: 1.4em;
}#pause_menu {display: none;
}

reset.css

html, body div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a abbr, acronym, address, big, cite, code, del, dfn, em, font, img, ins, kdb, q, s, samp, small, strike, strong, sub, sup, tt, var, b, u, i, center, dl, dt, dd, ol, ul, li fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td {margin: 0;padding: 0;font-size: 100%;vertical-align: baseline;border: 0;outline: 0;background: transparent;
}ol, ul {list-style: none;
}blockquote, q {quotes: none;
}a, a:hover {text-decoration: none;
}table {border-collapse: collapse;border-spacing: none;
}
#pause_menu, #restart_menu {display: none;
}
#score_container {float: right;display: inline;color: white;
}

使用 HTML5 Canvas 标签的贪吃蛇游戏相关推荐

  1. 基于HTML5的贪吃蛇游戏的设计与实现

    基于HTML5的贪吃蛇游戏的设计与实现 功能要求: 贪吃蛇游戏是一款经典的单机休闲游戏,玩家通过上下左右按键控制蛇头的移动方向使其向指定方向前进,并吃掉随机位置上产生食物来获得分数.每吃掉一次食物,贪 ...

  2. 如何用html做一个贪吃蛇,如何用HTML5制作贪吃蛇游戏

    如何用HTML5制作贪吃蛇游戏 发布时间:2020-07-09 15:09:59 来源:亿速云 阅读:122 作者:Leah 如何用HTML5制作贪吃蛇游戏?很多新手对此不是很清楚,为了帮助大家解决这 ...

  3. html5+JavaScript实现贪吃蛇游戏(可查看排行榜和个人历史记录)

    先看结果截图(界面很丑,但是主要是为了贪吃蛇游戏算法设计和历史记录.排行榜功能的实现,希望以后不被产品经理打,哈哈) 功能描述: 1.开始游戏页面之前输入玩家姓名 2.历史记录:可以查看之前自己玩过的 ...

  4. java3d酷眩贪吃蛇下载,100行JS实现HTML5的3D贪吃蛇游戏

    js1k.com收集了小于1k的javascript小例子,里面有很多很炫很酷的游戏和特效,今年规则又增加了新花样,传统的classic类型基础上又增加了WebGL类型,以及允许增加到2K的++类型, ...

  5. flash游戏代码html5,Flash贪吃蛇游戏AS代码翻译

    Flash贪吃蛇游戏AS代码翻译 互联网   发布时间:2008-10-06 01:25:13   作者:佚名   我要评论 今天翻译了一段经典的贪吃蛇代码,译后感觉还有很多地方不太妥当,很多不妥的地 ...

  6. Canvas制作经典贪吃蛇

    目录 效果展示 功能描述 然后是我个人开发这个小游戏时的大致步骤或者说思路 源代码 index.html js.js common.css 效果展示 功能描述 "上""下 ...

  7. python写一个游戏多少代码-使用Python写一个贪吃蛇游戏实例代码

    我在程序中加入了分数显示,三种特殊食物,将贪吃蛇的游戏逻辑写到了SnakeGame的类中,而不是在Snake类中. 特殊食物: 1.绿色:普通,吃了增加体型 2.红色:吃了减少体型 3.金色:吃了回到 ...

  8. python游戏脚本实例-使用Python写一个贪吃蛇游戏实例代码

    我在程序中加入了分数显示,三种特殊食物,将贪吃蛇的游戏逻辑写到了SnakeGame的类中,而不是在Snake类中. 特殊食物: 1.绿色:普通,吃了增加体型 2.红色:吃了减少体型 3.金色:吃了回到 ...

  9. python游戏贪吃蛇_Python写的贪吃蛇游戏例子_python

    这篇文章主要介绍了Python写的贪吃蛇游戏例子,练手作品,又好玩又可以学到东西,需要的朋友可以参考下 第一次用Python写这种比较实用且好玩的东西,权当练手吧 游戏说明: * P键控制" ...

最新文章

  1. benet 3.0的windows服务视频 第二章DNS
  2. golang interface 转 int string slice struct 类型
  3. python扩展库丰富吗_python扩展库
  4. linux——sshd服务及其管理命令
  5. SAP Spartacus OccEndpointsService调用getBaseEndpoint的一些场景
  6. vue设置页面标题title
  7. 【PL/SQL】异常处理
  8. TIPS:java 类的全局变量与静态变量
  9. 基于asp电子商城购物网站设计
  10. 中标麒麟服务器系统安装教程,安装国产Linux中标麒麟操作系统教程
  11. 去哪儿APP设计总结
  12. 02- 在夜神模拟器内部安装App
  13. 敏捷-年金、净现值NPV、IRR、现值PV、终值FV、EAR概念与案例(转)
  14. LeetCode-781-森林中的兔子
  15. 【树莓派】更新树莓派SD卡测速一键脚本,SD卡读写速度测试
  16. 学习型红外遥控器的FPGA设计与实现
  17. ​稳压二极管与TVS二极管
  18. centos查oracle版本命令,Linux操作系统查看系统信息命令
  19. Excel 怎样去掉单元格中的回车符号
  20. google nexus 10 用fastboot 刷机教程 (官方原版rom)

热门文章

  1. js android 通讯录,js通讯录效果,你见过麽
  2. 抓住用户需求的网易有道,能看破教育改革的本质吗?
  3. 《圣殿祭司的ASP.NET4.0专家技术手册》----1-1 ASP.NET平台的三分天下
  4. 看懂2019世界人工智能大会,“预见”未来
  5. 面试官:服务器安装JDK还是JRE?可以只安装JRE吗?
  6. 文科学生思维与理科学生思维对比
  7. 整理的C++面经(较全)
  8. PKCS#1、PKCS#5、PKCS#7、PKCS#8到底是什么?
  9. 3D模型欣赏:美漫风女巫加利CG模型女性作品欣赏
  10. 银弹谷DevSuite零代码开发平台普通窗体控件描述