recursal/MDN huggingface dataset:
Scaling
=======

* « 上一页
* 下一页 »

这是Gamedev Phaser 教程 (en-US)系列的第二课。在课程完成之后，你可以在Gamedev-Phaser-Content-Kit/demos/lesson02.html找到源码。

缩放是指游戏画布如何在不同的屏幕尺寸上进行显示。我们可以在预加载阶段自动使游戏规模适合任何屏幕尺寸，之后就可以不用再担心屏幕尺寸的问题了。

Phaser 中 scale 对象
-----------------

Phaser 中有一个特殊的对象：`scale`，它包含一些特别的方法和属性。让我们来更改一下上一节中创建的的`preload()`函数：


```js
function preload() {
  game.scale.scaleMode = Phaser.ScaleManager.SHOW\_ALL;
  game.scale.pageAlignHorizontally = true;
  game.scale.pageAlignVertically = true;
}
```


`scaleMode` 有几个不同的选项来指定 Canvas 应该如何缩放：

* `NO_SCALE` — 不进行任何缩放。
* `EXACT_FIT` — 拉伸，填充屏幕，不保留长宽比。
* `SHOW_ALL` — 等比缩放，填充屏幕，保留长宽比，剩余空间用黑色填充。
* `RESIZE` — 动态，每次都会根据屏幕生成画布，所以你需要在游戏运行时动态的放置游戏元素。这是一种进阶的模式。
* `USER_SCALE` — 自定义，允许你自己计算大小和比例。这也是一种进阶的模式。

`preload()`中的其他两行代码负责水平和垂直居中画布，所以它始终以屏幕为中心，无论大小如何。

设置背景颜色
------

我们还可以给画布设置背景颜色来替代磨人的黑色背景。通过更改`stage`对象的`backgroundColor`属性来添加，我们可以使用 CSS 颜色定义语法进行设置。我们在刚才的代码下面添加以下代码：


```js
game.stage.backgroundColor = "#eee";
```

完整的代码
-----

你可以在下面的现场演示中查看本课程的完成代码，并使用它来更好地了解它的工作原理：

下一步
---

现在我们设置了我们游戏的缩放比例，让我们继续第三课，并设计出如何加载资源并将其显示在屏幕上 (en-US)。

* « 上一页
* 下一页 »
Move the ball
=============

* « 上一页 (en-US)
* 下一页 » (en-US)

这是Gamedev Phaser 教程 (en-US) 16 的**第 4 步**。在Gamedev-Phaser-Content-Kit / demos / lesson04.html完成本课后，你可以找到源代码。

我们在屏幕上打印了我们的蓝色球，但它什么都不做，这样做会很酷。本文介绍如何做到这一点。

在每个框架上更新球的位置
------------

记住`update()`功能及其定义？其中的代码在每个框架上执行，所以它是一个完美的地方，将代码更新球的位置在屏幕上。在里面添加以下新行代码`update()`，如下所示：


```js
function update() {
  ball.x += 1;
  ball.y += 1;
}
```


上面的代码在每个框架上为表示画布上的球坐标的属性`x`和`y`属性添加了一个。重新加载 index.html，你应该看到球在屏幕上滚动。

比较你的代码
------

你可以在下面的现场演示中查看本课程的完成代码，并使用它来更好地了解它的工作原理：

下一步
---

下一步是添加一些基本的碰撞检测，所以我们的球可以从墙壁反弹。这将需要几行代码 - 一个比我们迄今为止看到的更复杂的步骤，特别是如果我们也想添加桨和砖碰撞 - 但是幸运的是 Phaser 使我们比我们想要使用纯粹的方法更容易做到这一点 JavaScript 的。

无论如何，在我们做所有的事情之前，我们将首先介绍 Phaser 的物理 (en-US)引擎，并做一些设置工作。

* « 上一页 (en-US)
* 下一页 » (en-US)
反弹的墙壁
=====

* « 上一页 (en-US)
* 下一页 » (en-US)

本篇是 Gamedev Canvas tutorial (en-US) 10 节教程中的第三节。如果你完成了本篇教程之后，你可以从 Gamedev-Canvas-workshop/lesson3.html 看到源码。

看到我们的球动起来很惊讶吧，但是它很快就从屏幕上消失了，当然我们是可以控制它的。我们会实现一些非常简单的碰撞检测 (详细后面解释),使球在画布的四周反弹回来。

简单的碰撞
-----

我们将检查球体是否与边缘接触，如果有接触我们将相应的改变它的运动方向。

为了运算方便我们定义一个名为 ballRadius 的变量，来存储球的半径。向代码中添加一下内容：


```js
var ballRadius = 10;
```


现在更新绘制球的 drawBall() 函数：


```js
ctx.arc(x, y, ballRadius, 0, Math.PI \* 2);
```

### 从顶部和底部弹起

有四面墙壁可以让它反弹回来，我们先来看上面的那面墙。我们需要判断球运动的每一帧，球体是否与画布的顶部边缘接触。如果有接触，我们将会改变球体的运动方向，使它向相反的方向移动，并保证它在画布的可见范围之内。记住坐标系统的左上角，让我们开始并加以下代码：


```js
if (y + dy < 0) {
  dy = -dy;
}
```


如果球的纵坐标（y 轴）值小于零，我们将在球体原有的运动方向上逆转。如果球体向上移动的速度是 2 像素/帧，现在就是向上移动速度是 -2 像素。这相当于此时向下移动的速度是 2 像素/帧。

上面的代码将处理球与画布顶部边缘的反射，现在让我们思考一下底部边缘如何处理：


```js
if (y + dy > canvas.height) {
  dy = -dy;
}
```


如果球的 y 位置大于`canvas`的高度（记住，我们从左上角计算 y 值，所以顶部边缘从 0 开始，底部边缘在 480 像素），然后通过像以前那样反转 y 轴运动而离开底部边缘。

我们可以将这两句冗长的代码合二为一：


```js
if (y + dy > canvas.height || y + dy < 0) {
  dy = -dy;
}
```


如果其中一个判断为 `true`, 则反转球的运动。

### 从左边和右边反弹

我们有顶部和底部的边缘，所以我们来考虑一下左边和右边的边缘。实际上非常相似，你所要做的就是颠倒`x`而不是`y`:


```js
if (x + dx > canvas.width || x + dx < 0) {
  dx = -dx;
}

if (y + dy > canvas.height || y + dy < 0) {
  dy = -dy;
}
```


你应该把上面的代码块插入到 draw（）函数中，就在大括号之前。

### 球部分消失在墙上！

测试你的代码，你会看到我们的球碰到任一边缘都会反弹！然而，我们还发现了一个问题，当球碰撞到边缘，反弹之前：

![](/zh-CN/docs/Games/Tutorials/2D_Breakout_game_pure_JavaScript/Bounce_off_the_walls/ball-in-wall.png)

这是因为我们正在计算墙和球的中心碰撞点，而我们应该围绕它的周长来做。如果碰到墙壁，球应该会弹起来，而不是陷入墙壁一半时，所以让我们来调整一下我们的判断条件。更新你之前添加的代码：


```js
if (x + dx > canvas.width - ballRadius || x + dx < ballRadius) {
  dx = -dx;
}
if (y + dy > canvas.height - ballRadius || y + dy < ballRadius) {
  dy = -dy;
}
```


当球的中心到墙的边缘之间的距离与球的半径完全相同时，它将改变运动的方向。

比较你的代码
------

让我们再次检查这个部分的代码与你之间有何差异：

**备注：** 尝试修改你的代码，在每次碰到墙壁时都要把球的颜色改成随机的颜色。

下一步
---

现在我们已经到了我们的球正在移动和留在游戏板上的阶段。在第四章中，我们将看看如何实现一个可控制的 paddle - 参见paddle 和键盘控制 (en-US)。

* « 上一页 (en-US)
* 下一页 » (en-US)
鼠标控制
====

* « 上一页 (en-US)
* 下一页 » (en-US)

本篇为 Gamedev Canvas tutorial (en-US) 10 节教程中的**第 9 节**。在你完成这篇课程之后，你可以在 Gamedev-Canvas-workshop/lesson9.html 找到我们的源代码。

这个游戏实际已经完成，现在让我们着手去润色。我们已经添加过键盘控制，而加入鼠标控制也同样简单。

监听鼠标移动
------

监听鼠标移动甚至比监听按键更简单：只需监听 `mousemove` 这个事件即可。把下面这行代码和其他事件监听代码放在一起，在 `keyup event` 的下一行：


```js
document.addEventListener("mousemove", mouseMoveHandler, false);
```

将球板移动绑定到鼠标移动
------------

我们可以根据鼠标光标位置来更新球板位置——下面这个函数正是做这件事的。把这个函数加到你的代码中，接在你刚刚加入的那行后面：


```js
function mouseMoveHandler(e) {
  var relativeX = e.clientX - canvas.offsetLeft;
  if (relativeX > 0 && relativeX < canvas.width) {
    paddleX = relativeX - paddleWidth / 2;
  }
}
```


在这个函数中，我们首先计算 `relativeX` 的值，它等于鼠标在视窗中的水平位置（`e.clientX`）减去 canvas 元素左边框到视窗左边框的距离（`canvas.offsetLeft`）——这就得到了 canvas 元素左边框到鼠标的距离。若这个值大于零，且小于 canvas 的宽度，说明鼠标指针落在 canvas 边界内，这时就把 `paddleX` （等于球板左边缘的坐标）设为 `relativeX` 减速去球板宽度的一半。这样就确保位移是相对于球板中心进行的。

现在球板将跟随鼠标指针。不过由于我们将球板移动限制在 canvas 大小范围内，它不会从两边完全消失。

比较你的代码
------

以下是我们的示例代码，以便与你进行比较：

练习：调整球板移动的范围，使得整个球板总是可见，而不是在移动到边缘时被遮住一半。

下一步
---

现在我们已经拥有一个完整的游戏。我们的系列教程将以一些细节上的调整作为结束。 (en-US)

* « 上一页 (en-US)
* 下一页 » (en-US)
球板及键盘控制
=======

* « 上一页 (en-US)
* 下一页 » (en-US)

这是Gamedev Canvas tutorial (en-US)中的第四章。完成本课程后，你可以在Gamedev-Canvas-workshop/lesson4.html找到源码。

你可以看到球自由的、无限次的在墙壁上反弹，但是没有和我们发生任何交互。如果我们没有对它的控制操作，这仍然不是一个游戏。下面，我们新增一些用户操作：一个可以控制球的球板。

定义一个球板去接球
---------

我们需要添加一个球板去接球：为此需要先定义一些变量。在你的代码的顶部的其他变量下方添加下列代码：


```js
var paddleHeight = 10;
var paddleWidth = 75;
var paddleX = (canvas.width - paddleWidth) / 2;
```


然后定义球拍的长和宽，以及为了之后的处理同时定义 x 轴上的初始位置。新建一个方法来在页面上描绘球板。把下列代码添加到你的 `drawBall()` 方法里去：


```js
function drawPaddle() {
  ctx.beginPath();
  ctx.rect(paddleX, canvas.height - paddleHeight, paddleWidth, paddleHeight);
  ctx.fillStyle = "#0095DD";
  ctx.fill();
  ctx.closePath();
}
```

允许用户控制球板
--------

我们可以如愿的描绘出球板，也需让它听从用户的控制。是时候实现用键盘控制它了。我们需要：

* 两个变量以保存左右方向键是否被按下的信息。
* 两个事件监控器来捕捉按键的按下和松开动作。我们需要运行一些代码以在按键被按下时可以控制球拍的移动
* 两个用于处理按键被按下或松开后的事件处理方法
* 实现左右移动球拍

按键可以使用 boolean 变量来初始定义。在你的其他变量附近添加下列代码：


```js
var rightPressed = false;
var leftPressed = false;
```


这两个变量的默认值都是 false，因为在开始时按键没有被按下。为了监听按键的按下动作，我们需要添加两个监听器。把下列代码添加到底部的 `setInterval()` 的上一列去：


```js
document.addEventListener("keydown", keyDownHandler, false);
document.addEventListener("keyup", keyUpHandler, false);
```


当你按下任何键盘上的按键，按下事件被激活时 `keyDownHandler()` 方法会被调用。对于松开时的处理也是类似的：当松开按键时 keyUpHandler() 方法会被调用。把下列代码添加到 addEventListener() 下方：


```js
function keyDownHandler(e) {
  if (e.keyCode == 39) {
    rightPressed = true;
  } else if (e.keyCode == 37) {
    leftPressed = true;
  }
}

function keyUpHandler(e) {
  if (e.keyCode == 39) {
    rightPressed = false;
  } else if (e.keyCode == 37) {
    leftPressed = false;
  }
}
```


当按下一个按键，这个信息会被储存在一个变量中。每种情况下的相关变量都设置为`true`。当松开按键时，对应变量被设置回`false`。

两个函数都以一个事件作为参数，由`e`(event) 变量表示。从这里你可以得到有用的信息：keyCode 属性是被按下的键的信息。例如，keyCode 为 37 是左箭头键，而 39 是右箭头键。如果按下左键，那么 leftPressed 变量设置为 true，当松开时，leftPressed 变量设置为 false。右键同理。

### 球拍移动逻辑

我们现在有用于存储按键，事件监听器和相关功能的信息的变量。现在我们将看到实际的代码来使用这些变量，并在屏幕上移动球拍。在 draw（）函数内部，我们将检查每一帧被渲染的同时是否按下左或右键。我们的代码如下：


```js
if (rightPressed) {
  paddleX += 7;
} else if (leftPressed) {
  paddleX -= 7;
}
```


如果按一下左键，球拍将向左移动 7 个像素，如果按一下右键，球拍将向右移动 7 个像素。目前这个功能可以正常工作，但是如果我们按任意一个键的时间太长，球拍就会从画布的边缘消失。我们可以通过改变代码来改善这种情况，并且只能在画布的边界内移动球拍，如下所示：


```js
if (rightPressed && paddleX < canvas.width - paddleWidth) {
  paddleX += 7;
} else if (leftPressed && paddleX > 0) {
  paddleX -= 7;
}
```


我们使用在`Canvas`左侧的 0 和右侧的`canvas.width-paddleWidth`之间的`paddleX`位置移动，这会让球拍按预期的要求移动。

将上面的代码块添加到底部的`draw()`函数中，在右大括号的上方。

现在唯一要做的就是在`draw()`函数内调用`drawPaddle()`函数，将其实际渲染在屏幕上。在`draw()`函数内添加下面一行，就在调用`drawBall()`的那一行的下面：


```js
drawPaddle();
```

比较你的代码
------

以下是我们的示例代码，以便与你进行比较：

**备注：** 让球拍变快变慢，或者改变它的大小。

下一步
---

现在我们有一些类似于游戏的东西。唯一的麻烦就是无论如何你都可以继续用球拍击球。这一切都将在第五章中改变，游戏结束 (en-US)时，我们会为游戏添加一个最后的状态。

* « 上一页 (en-US)
* 下一页 » (en-US)
游戏结束
====

* « 上一页 (en-US)
* 下一页 » (en-US)

这是 Gamedev Canvas tutorial (en-US)教程的第五章。你可以在完成本课程后在这里Gamedev-Canvas-workshop/lesson5.html找到源代码。

看球从墙上反弹，并能够移动球盘是很有趣的。但除此之外，游戏什么都不做，也没有任何进展或最终目标。从游戏的角度来看，我们需要一个 game over。失败的逻辑很简单。如果你的球拍错过了球，并且球到达屏幕的底部边缘，那么游戏就结束了。

实现游戏失败
------

让我们在代码中实现，下面是第三章里的一段代码，让球从墙上反弹：


```js
if (x + dx > canvas.width - ballRadius || x + dx < ballRadius) {
  dx = -dx;
}

if (y + dy > canvas.height - ballRadius || y + dy < ballRadius) {
  dy = -dy;
}
```


我们不需要让球从四面墙上反弹，应该只允许三个 - 左，上，右。击中底部墙将结束游戏。我们将编辑第二个 if 代码块，这是一个 if else 块，当球碰撞到画布的底部边缘时，它会触发我们的“游戏结束”状态。现在我们将保持简单，显示一条警告消息，并通过重新加载页面重新开始游戏。

第一步，把你最开始使用的 `setInterval()` 函数


```js
setInterval(draw, 10);
```


替换成：


```js
var interval = setInterval(draw, 10);
```


然后将第二个 if 块替换为以下内容：


```js
if (y + dy < ballRadius) {
  dy = -dy;
} else if (y + dy > canvas.height - ballRadius) {
  alert("GAME OVER");
  document.location.reload();
}
```

让球拍接住球
------

本课中最后要做的是在球和球拍之间创建一些碰撞检测，以便它可以反弹并返回到游戏区域。最简单的方法是检查球的中心是否在球拍的左边和右边之间。再次更新你修改的代码的最后一位（第二个 if 块），如下所示：


```js
if (y + dy < ballRadius) {
  dy = -dy;
} else if (y + dy > canvas.height - ballRadius) {
  if (x > paddleX && x < paddleX + paddleWidth) {
    dy = -dy;
  } else {
    alert("GAME OVER");
    document.location.reload();
  }
}
```


如果球击中画布的底部边缘，我们需要检查它是否碰到球拍。如果是的话，就像你所期望的那样反弹。如果没有，那么游戏就像以前一样结束。

代码对比
----

这里是完整例子，对比一下代码吧！

**备注：** 当球碰到球拍时，让球移动得更快

下一步
---

到目前为止，我们的表现相当不错，游戏变得更有趣，并且现在你可以输了！但它仍然缺少一些东西。让我们继续前进到第六章 - 建造砖块 (en-US) - 并创造一些砖块来消灭它们。

* « 上一页 (en-US)
* 下一页 » (en-US)
让球动起来
=====

* « 上一页 (en-US)
* 下一页 » (en-US)

本篇是 Gamedev Canvas tutorial (en-US) 10 节教程中的第二节。如果你完成了本篇教程之后，你可以从 Gamedev-Canvas-workshop/lesson2.html 看到源码。

从上一节中你已经知道如何去绘制一个球。现在让我们使它动起来。从技术上讲，我们将在画布上绘制一个球，之后让它消失，然后在一个稍微不用的位置上再绘制一个一样的球。就想电影里的每一帧动起来的感觉。

我们需要定义一个绘图函数，每次使用一组不同的变量改变球体的位置；循环调用以保持画布上每一帧不断更新。你可以使用 JavaScript 时间函数 `setInterval()` (en-US) 或者 `window.requestAnimationFrame()`。

在你的 HTML 文件只保留前两行，删除其他所有的 JavaScript 代码并在 draw() 函数中添加以下内容保证每 10 毫秒执行一次 draw() 函数：


```js
function draw() {
  // drawing code
}
setInterval(draw, 10);
```


得益于 `setInterval` 的无限性，使得 `draw()` 函数将每 10 毫秒就会被调用，除非我们停止它。现在，我们来绘制小球吧，在 `draw()` 函数中添加以下内容：


```js
ctx.beginPath();
ctx.arc(50, 50, 10, 0, Math.PI \* 2);
ctx.fillStyle = "#0095DD";
ctx.fill();
ctx.closePath();
```


现在，尝试更新你的代码，球会在每一帧画面被绘制

让球动起来
-----

你不会注意到球正在不停地被重新刷，因为它没有移动。让我们改变这种情况。首先，我们不再使用固定位置 (50,50)，而是用 x 和 y 的变量来定义画布底部的起始点，然后使用这些变量来定义圆被绘制的位置。

首先，在`draw()`函数上方添加以下两行，以定义`x`和`y`：


```js
var x = canvas.width / 2;
var y = canvas.height - 30;
```


接下来更新 `draw()` 函数，在 `arc()` 方法中使用 `x` 和 `y` 变量，如下面高亮行所示：


```js
function draw() {
  ctx.beginPath();
  ctx.arc(x, y, 10, 0, Math.PI \* 2);
  ctx.fillStyle = "#0095DD";
  ctx.fill();
  ctx.closePath();
}
```


现在到了最重要的部分：我们想要在每一帧都被绘制出来之后，给 `x` 和 `y` 添加一个较小的值，让它看起来像是在移动。让我们将这些值定义为 `dx` 和 `dy`，并将它们的值分别设为 `2` 和 `-2`。在你的 x 和 y 变量声明下方添加以下内容：


```js
var dx = 2;
var dy = -2;
```


最后要做的是在每一帧上更新 `x` 和 `y`，在每一次更新中，把球画在新的位置上。将下面的两条新线添加到你的 `draw()` 函数：


```js
function draw() {
  ctx.beginPath();
  ctx.arc(x, y, 10, 0, Math.PI \* 2);
  ctx.fillStyle = "#0095DD";
  ctx.fill();
  ctx.closePath();
  x += dx;
  y += dy;
}
```


再次保存代码，并在浏览器中尝试。很好，尽管看起来球在后面留下了痕迹：

![](/zh-CN/docs/Games/Tutorials/2D_Breakout_game_pure_JavaScript/Move_the_ball/ball-trail.png)

在每一帧更新之前清空画布
------------

球移动时留下了轨迹，因为我们在每一帧上都画了一个新的圆，而没有去掉之前的一个圆。不要担心，因为有一个方法来清空画布的内容：`clearRect()`。该方法有四个参数：矩形左上角的 `x` 和 `y` 坐标，以及矩形的右下角的 `x` 和 `y` 坐标。这个矩形覆盖的整个区域里，之前所画的任何内容将被清除。

将下列高亮显示行添加到 `draw()` 函数：


```js
function draw() {
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  ctx.beginPath();
  ctx.arc(x, y, 10, 0, Math.PI \* 2);
  ctx.fillStyle = "#0095DD";
  ctx.fill();
  ctx.closePath();
  x += dx;
  y += dy;
}
```


保存你的代码并再次尝试，这次你将看到球移动后没有留下轨迹。每隔 10 毫秒，画布就会被清除，蓝色的圆圈 (我们的球) 将被绘制在一个给定的位置上，而 `x` 和 `y` 的值将在下一个帧被更新。

保持代码整洁
------

在接下来的几篇文章中，我们将在 `draw()` 函数中添加越来越多的命令，因此尽可能保持简单和整洁是很好的。让我们从把绘制球的代码移至一个单独的函数。

用以下两个函数替换现有的 `draw()` 函数：


```js
function drawBall() {
  ctx.beginPath();
  ctx.arc(x, y, 10, 0, Math.PI \* 2);
  ctx.fillStyle = "#0095DD";
  ctx.fill();
  ctx.closePath();
}

function draw() {
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  drawBall();
  x += dx;
  y += dy;
}
```

比较你的代码
------

你可以在下面的实时演示中查看本文的代码，并使用它来更好地了解其工作原理：

练习：尝试改变移动球的速度，或者移动球的方向。

下一步
---

我们已经画了我们的球，并将其移动，但它仍然消失在画布的边缘。在第三章中，我们将探讨如何使其 从墙壁上反弹 (en-US).

* « 上一页 (en-US)
* 下一页 » (en-US)
跟踪得分和获胜
=======

* « 上一页 (en-US)
* 下一页 » (en-US)

本篇为Gamedev Canvas tutorial (en-US)10 节教程中的\*\*第 8 节。\*\*在你完成这篇课程之后，你可以在Gamedev-Canvas-workshop/lesson8.html找到我们的源代码。

破坏砖块真的很酷，但更酷的是，游戏可以给每个用户击破的砖块奖励分数，并保持总分。

计算分数
----

如果你能在整个游戏中看到你的分数，最终你会给你的朋友留下深刻印象。你需要一个变量来记录分数。在变量的其余部分之后，将下面的内容添加到 JavaScript 中：


```js
var score = 0;
```


你还需要一个 `drawScore()` 函数来创建和更新分数显示。在 `collisionDetection()` 函数之后添加以下内容：


```js
function drawScore() {
  ctx.font = "16px Arial";
  ctx.fillStyle = "#0095DD";
  ctx.fillText("Score: " + score, 8, 20);
}
```


在画布上绘制文本类似于绘制形状。字体定义看起来与 CSS 中的字体定义完全一样——可以在`font()` 方法中设置大小和字体类型。然后使用`fillStyle()` 来设置字体的颜色，`fillText()` 来设置将放置在画布上的实际文本，和其放置位置。第一个参数是文本本身——上面的代码显示当前点的数量——最后两个参数是文本将放置在画布上的坐标。

若要在每次击中砖块时评分，则在 `collisionDetection()`中添加计分规则，以在每次检测到碰撞时增加得分变量的值。将下面突出显示的行添加到代码中：


```js
function collisionDetection() {
  for (var c = 0; c < brickColumnCount; c++) {
    for (var r = 0; r < brickRowCount; r++) {
      var b = bricks[c][r];
      if (b.status == 1) {
        if (
          x > b.x &&
          x < b.x + brickWidth &&
          y > b.y &&
          y < b.y + brickHeight
        ) {
          dy = -dy;
          b.status = 0;
          score++;
        }
      }
    }
  }
}
```


从 `draw()` 函数调用 `drawScore()` ，使每一个新帧的分数都保持最新，在 `draw()`中添加下面的行，在 `drawPaddle()` 下面调用：


```js
drawScore();
```

当所有砖块被破坏时显示获胜消息
---------------

收集这些点很有效，但是你不会永远添加它们 - 当所有的砖头都被破坏的时候呢？毕竟这是游戏的主要目的，所以如果收集到所有可用的点，你应该显示一个获胜的消息。将下面突出显示的部分添加到 `collisionDetection()` 函数中：


```js
function collisionDetection() {
  for (var c = 0; c < brickColumnCount; c++) {
    for (var r = 0; r < brickRowCount; r++) {
      var b = bricks[c][r];
      if (b.status == 1) {
        if (
          x > b.x &&
          x < b.x + brickWidth &&
          y > b.y &&
          y < b.y + brickHeight
        ) {
          dy = -dy;
          b.status = 0;
          score++;
          if (score == brickRowCount \* brickColumnCount) {
            alert("YOU WIN, CONGRATULATIONS!");
            document.location.reload();
          }
        }
      }
    }
  }
}
```


谢谢你做的这些，你的用户可以真正赢得游戏时，他们也摧毁了所有的砖块。当用户来到游戏还有一点非常重要，用户一旦点击了警告按钮， `document.location.reload()`函数将重新加载页面并重新启动游戏。

比较你的代码
------

最新的代码是这样（和工程），如果你想比较和对比它与你写的：

**备注：** 在每一个砖头击破后添加更多的分数，打印出收集到的点数在游戏结束警告框中。

下一节
---

游戏到这一步看起来相当不错。在下一课中，你将通过添加鼠标控件来扩大游戏的吸引力：Mouse controls (en-US).。

* « 上一页 (en-US)
* 下一页 » (en-US)
撞击处理
====

* « 上一页 (en-US)
* 下一页 » (en-US)

本篇为Gamedev Canvas tutorial (en-US)10 节教程中的\*\*第 7 节。\*\*在你完成这篇课程之后，你可以在Gamedev-Canvas-workshop/lesson7.html.找到我们的源代码。

我们已经在屏幕上画出了砖块，但游戏仍然没有那么有趣，因为球通过它们。我们需要考虑增加碰撞检测，这样球就可以弹击砖块并打破它们。

当然，这是我们的决定如何实现的，但是计算球是否触及矩形是很困难的，因为在画布中没有辅助函数。为了这个教程，我们将尽可能地做到这一点。我们将检查球的中心是否与任何给定的砖块碰撞。这不会每次都给出一个完美的结果，而且有很多更复杂的方法来进行碰撞检测，但是这对指导你的基本概念很有效。

撞击侦测函数
------

踢掉这一切，我们想创建一个碰撞检测功能，将循环通过所有砖块，并比较每一个砖的位置与球的坐标，因为每个帧绘制。为了更好地理解代码，我们将定义用于在碰撞检测的每个循环中存储砖块对象的 B 变量：


```js
function collisionDetection() {
  for (c = 0; c < brickColumnCount; c++) {
    for (r = 0; r < brickRowCount; r++) {
      var b = bricks[c][r];
      // calculations
    }
  }
}
```


如果球的中心在我们的一块砖块的坐标内，我们将改变球的方向。对于球的中心在砖块内，以下四个陈述都必须是正确的：

* 球的 X 位置大于砖的 X 位置。
* 球的 X 位置小于砖的 X 位置加上它的宽度。
* 球的 Y 位置大于砖的 Y 位置。
* 球的 Y 位置小于砖块的 Y 位置加上它的高度。

让我们在代码中写下：


```js
function collisionDetection() {
  for (c = 0; c < brickColumnCount; c++) {
    for (r = 0; r < brickRowCount; r++) {
      var b = bricks[c][r];
      if (x > b.x && x < b.x + brickWidth && y > b.y && y < b.y + brickHeight) {
        dy = -dy;
      }
    }
  }
}
```


将上面的块添加到代码中，在 `keyUpHandler()` 函数下面。

让砖块在被撞击之后消失
-----------

上述代码将按需要工作，球改变其方向。问题是砖块留在原地。我们必须想出一个办法来摆脱那些我们已经用球打中的砖。我们可以通过添加一个额外的参数来指示我们是否想在屏幕上画每个砖块。在初始化砖块的代码的一部分中，让我们为每个砖块对象添加一个状态属性。更新代码的下面部分，如突出显示的行所示：


```js
var bricks = [];
for (c = 0; c < brickColumnCount; c++) {
  bricks[c] = [];
  for (r = 0; r < brickRowCount; r++) {
    bricks[c][r] = { x: 0, y: 0, status: 1 };
  }
}
```


接下来，我们将在绘制之前在 `drawBricks()` 中检查每个砖块的 `status`属性的值 - 如果 `status`是 `1`，然后画它，但是如果它是 `0`，那么它被球击中，我们不再希望它在屏幕上。更新你的 `drawBricks()` 函数如下：


```js
function drawBricks() {
  for (c = 0; c < brickColumnCount; c++) {
    for (r = 0; r < brickRowCount; r++) {
      if (bricks[c][r].status == 1) {
        var brickX = c \* (brickWidth + brickPadding) + brickOffsetLeft;
        var brickY = r \* (brickHeight + brickPadding) + brickOffsetTop;
        bricks[c][r].x = brickX;
        bricks[c][r].y = brickY;
        ctx.beginPath();
        ctx.rect(brickX, brickY, brickWidth, brickHeight);
        ctx.fillStyle = "#0095DD";
        ctx.fill();
        ctx.closePath();
      }
    }
  }
}
```

跟踪并更新在撞击侦测函数中的状态
----------------

现在我们需要将砖块 `status` 属性包含在 `collisionDetection()`函数中：如果砖块是活动的（它的状态是 `1`），我们将检查碰撞是否发生；如果发生碰撞，我们将给定砖块的状态设置为 `0`，这样它就不会被绘制在屏幕上。更新你的 `collisionDetection()` 函数，如下所示：


```js
function collisionDetection() {
  for (c = 0; c < brickColumnCount; c++) {
    for (r = 0; r < brickRowCount; r++) {
      var b = bricks[c][r];
      if (b.status == 1) {
        if (
          x > b.x &&
          x < b.x + brickWidth &&
          y > b.y &&
          y < b.y + brickHeight
        ) {
          dy = -dy;
          b.status = 0;
        }
      }
    }
  }
}
```

调用我们的撞击侦测函数
-----------

最后要做的是向 `collisionDetection()` 函数添加一个调用到我们的主要`draw()` 函数。将下面的行添加到 `draw()` 函数中，就在`drawPaddle()` 调用的下面：


```js
collisionDetection();
```

对比你的代码
------

现在，在每一帧，每一块砖上都检查了球的碰撞检测。现在我们可以毁掉砖头：-！

**备注：** 练习：当球碰到砖头时，改变球的颜色。

下一节
---

我们现在肯定到了，继续前进吧！在第八章中，我们将探讨如何跟踪得分和获胜。Track the score and win (en-US).

* « 上一页 (en-US)
* 下一页 » (en-US)
创建、绘制画布
=======

* « 上一页 (en-US)
* 下一页 » (en-US)

本篇是 Gamedev Canvas tutorial (en-US) 10 节教程中的第一节。如果你完成了本篇教程之后，你可以从 Gamedev-Canvas-workshop/lesson1.html 看到源码。

在我们开始编写游戏功能之前，我们可以通过 HTML 的 canvas 标签创建支撑游戏的基本结构。

页面部分
----

HTML 文档的结构是非常简单的，我们的游戏将完全呈现在这个 HTML 的 canvas 标签中。你可以选择一款你最喜欢的文本编辑器，创建一个 HTML 文件，保存到你理想的位置，名称为 index.html。并添加以下代码：


```html
<!doctype html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>Gamedev Canvas Workshop</title>
    <style>
 \* {
 padding: 0;
 margin: 0;
 }
 canvas {
 background: #eee;
 display: block;
 margin: 0 auto;
 }
 </style>
  </head>
  <body>
    <canvas id="myCanvas" width="480" height="320"></canvas>

    <script>
 // JavaScript code goes here
 </script>
  </body>
</html>
```


在 head 标签中我们需要定义字符集（charset），标题（title）和一些基本的样式。在 body 标签中包含 canvas 标签和 javascript 标签，我们将在 javascript 标签中使用 JavaScript 代码来控制 canvas 标签中的内容展现。在 canvas 标签有一个名为 myCanvas 的 Id，根据这个属性我们可以很容易的获取到这个元素并设置他的宽为 480 像素，高为 320 像素。之后我们会将所有的 JavaScript 代码全部写到 javascript 标签中（<script>...</script>）。

画布的基本属性
-------

要想在 canvas 标签中呈现图像内容，我们必须在 JavaScript 代码中获取到这个元素。在 javascript 标签中添加以下代码；


```js
var canvas = document.getElementById("myCanvas");
var ctx = canvas.getContext("2d");
```


在这里我们可以声明一个变量保存 canvas 标签，然后我们需要创建变量 ctx, 这个变量可用来存储 2d 的渲染上下文，我们实际上就用它在 canvas 标签上绘制内容。

让我们来看一个例子，打印在画布上的红色正方形。添加以下代码到你的 JavaScript 里，然后在浏览器中打开 index.html 来看看效果。


```js
ctx.beginPath();
ctx.rect(20, 40, 50, 50);
ctx.fillStyle = "#FF0000";
ctx.fill();
ctx.closePath();
```


把全部指令放到 ctx.beginPath() 方法 和 ctx.closePath() 方法之间。我们使用 ctx.rect() 方法创建了一个矩形。这个方法的第一组参数（20，40）是确定这个矩形的左上角在画布上的坐标，第二组参数（50, 50）是指定矩形的宽度和高度。例子中矩形左上角的 x 坐标为 20 像素，y 坐标为 40 像素；宽和高各为 50 像素，这样画出一个完美的正方形。ctx.fillStyle 属性是用于填充绘画的颜色，这里填充的颜色为红色。ctx.fill() 方法是填充路径的内容区域生成实心的图形。

我们不局限于绘制矩形 — 下面这段代码会绘制出一个绿色的圆；尝试添加到 JavaScript 代码的底部，保存并刷新：


```js
ctx.beginPath();
ctx.arc(240, 160, 20, 0, Math.PI \* 2, false);
ctx.fillStyle = "green";
ctx.fill();
ctx.closePath();
```


正如你所见，我们再一次使用了 ctx.beginPath() 方法 和 ctx.closePath() 方法；在这两个方法中间，最重要的部分是 ctx.arc() 方法。它需要六个参数：

* 圆心的 `x` 和 `y` 轴坐标
* 半径
* 起始角度，结束角度。以弧度计（弧的圆形的三点钟位置是 0 度）
* 规定应该逆时针还是顺时针绘图。false = 顺时针，true = 逆时针。（可选）

ctx.fillStyle 属性的值看起来和之前不大一样。这是因为它与 CSS 一样，颜色可以指定为十六进制值也可以指定为 rgba() 函数，或者任何其他可用的颜色值函数。

当然我们也可以不使用 ctx.fill() 函数来填充图形和颜色，用 ctx.stroke() 函数来绘制定义图形的路径。试着将这段代码添加到你的 JavaScript:


```js
ctx.beginPath();
ctx.rect(160, 10, 100, 40);
ctx.strokeStyle = "rgba(0, 0, 255, 0.5)";
ctx.stroke();
ctx.closePath();
```


上面的代码绘制出一个蓝色边框的空心矩形。由于 rgba() 函数的特性，边框为蓝色半透明的状态。

比对你的代码
------

以上是第一节的全部代码，可以运行在 JSFiddle（在线 JS 代码调试工具）上：

**备注：** 练习：尝试改变给定几何图形的大小和颜色。

下一节
---

现在我们已经创建了基本的 HTML 和关于画布的基本知识。我们继续第二节，如何让球在游戏中动起来 — 让球动起来 (en-US)。

* « 上一页 (en-US)
* 下一页 » (en-US)
收尾工作
====

* « 上一页 (en-US)

本篇为 Gamedev Canvas tutorial (en-US) 10 节教程中的\*\*第 10 节也是最后一节。\*\*完成这篇课程后，你可以在 Gamedev-Canvas-workshop/lesson10.html 找到我们的源代码。

不管我们做什么游戏，它总是存在优化的空间。例如，我们可以为玩家多提供几条命，让他们能在发生一两次失误的情况下顺利完成游戏。或者，我们也可以在渲染代码上下工夫。

加入生命机制
------

在游戏中实现生命机制的思路很直接。让我们先新增一个变量，用来存储其生命值。把下面这行代码和我们声明其他变量的代码放在一起：


```js
var lives = 3;
```


在 canvas 上绘制生命值计数的做法几乎和绘制分数一样——把下面的函数添加到`drawScore()` 函数后面：


```js
function drawLives() {
  ctx.font = "16px Arial";
  ctx.fillStyle = "#0095DD";
  ctx.fillText("Lives: " + lives, canvas.width - 65, 20);
}
```


当玩家失误时，我们不立即结束游戏，而是减少生命计数，直到为零。在玩家用掉一条命后，我们也可以重置小球和球板位置。那么，在函数 `draw()` 中将下面三行：


```js
alert("GAME OVER");
document.location.reload();
clearInterval(interval); // Needed for Chrome to end game
```


替换为下面的代码，注意到我们加入了一点点逻辑控制：


```js
lives--;
if (!lives) {
  alert("GAME OVER");
  document.location.reload();
  clearInterval(interval); // Needed for Chrome to end game
} else {
  x = canvas.width / 2;
  y = canvas.height - 30;
  dx = 2;
  dy = -2;
  paddleX = (canvas.width - paddleWidth) / 2;
}
```


现在，当小球碰到屏幕底边时，我们让变量`lives` 的值减一。如果生命用尽，游戏就宣告结束；否则就重置小球与球板的位置，以及小球的速度。

### 渲染生命值

现在只需在 `draw()` 函数内调用`drawLives()` 即可。让我们把它加到`drawScore()` 的下一行：


```js
drawLives();
```

用 requestAnimationFrame() 优化渲染
------------------------------

现在让我们处理一些与游戏机制无关，但与画面渲染相关的东西。和我们目前使用`setInterval()` (en-US) 实现的固定帧率渲染相比，`requestAnimationFrame` 能让浏览器更好地渲染画面。让我们把下面这行代码：


```js
var interval = setInterval(draw, 10);
```


替换为：


```js
draw();
```


再把代码中的每一处


```js
clearInterval(interval); // Needed for Chrome to end game
```


删除。然后，在 `draw()` 函数的最下方（右花括号之前）加入下面这行代码。它的作用是使 `draw()` 函数递归调用自身：


```js
requestAnimationFrame(draw);
```


现在 `draw()` 函数在 `requestAnimationFrame()` 的循环中被反复调用，之先前做法最大的不同是，我们将帧率的控制权交给浏览器，而不是固定的 10 毫秒。浏览器会在适当的时机同步帧率，并且只在必要的时候才刷新渲染的图形。这使得我们的动画比之前的 `setInterval()` 方法更加流畅且高效。

比较你的代码
------

我们的游戏的最终版本已经完成！以上。

**备注：** 试着改变生命的数目和球从球板上反弹的角度。

游戏结束——暂时看来！
-----------

祝贺你——你完成了本教程的所有小节！现在，你应该已经掌握 canvas 操纵的基础和 2D 游戏背后的逻辑了。是时候去学习一些框架，继续你的游戏开发之旅了！你可以看看本系列的姊妹篇：用 Phaser 制作 2D 打砖块游戏 (en-US) 或者 Cyber Orb built in Phaser (en-US) 。或者，你也可以在 MDN 游戏区 中获得灵感和更多知识。

你也可以回到本教程的目录页 (en-US)。祝编程愉快！

* « 上一页 (en-US)