在我们的弹球演示中添加功能

在本评估中,您需要使用上一篇文章中的弹跳球演示作为起点,并添加一些新的、有趣的特性。

先决条件 在尝试本评估之前,您应该已经完成了本模块中的所有文章。
目标 测试对 JavaScript 对象和面向对象结构的理解

起点

要开始本评估,请在本地计算机上的新目录中创建以下文件的本地副本:index-finished.htmlstyle.css,以及main-finished.js,它们来自我们上一篇文章。

或者,您可以使用在线编辑器,例如CodePenJSFiddle,或Glitch。您可以将 HTML、CSS 和 JavaScript 粘贴到其中一个在线编辑器中。如果使用的在线编辑器没有单独的 JavaScript 面板,请随意将其内嵌到 HTML 页面中的 <script> 元素中。

注意:如果您卡住了,您可以通过我们的沟通渠道之一与我们联系。

提示和技巧

在您开始之前,有几点需要提醒您。

  • 本评估相当具有挑战性。在开始编码之前,请仔细阅读整个评估内容,并逐个步骤缓慢而谨慎地进行。
  • 最好在每个阶段运行后保存演示的单独副本,这样如果之后遇到问题,您就可以参考它。

项目简述

我们的弹跳球演示很有趣,但现在我们要通过添加一个用户控制的邪恶圆圈来使其更具互动性,如果邪恶圆圈碰到球,就会吞噬它们。我们还想通过创建一个通用的 Shape() 对象,让我们的球和邪恶圆圈能够从它继承,来测试您的对象构建技能。最后,我们要添加一个分数计数器来跟踪剩余的球的数量。

以下屏幕截图展示了完成后的程序的外观。

Screenshot of the bouncing balls demo page. A white-outlined circle is visible in addition to the colored balls, and the text "Ball count: 23" is visible under the heading.

为了让您更好地了解,请查看完成的示例(不要偷看源代码!)

完成步骤

以下部分描述了您需要做的事情。

创建 Shape 类

首先,创建一个新的 Shape 类。它只有一个构造函数。Shape 构造函数应该与最初 Ball() 构造函数的方式相同,定义 xyvelXvelY 属性,但不要定义 colorsize 属性。

Ball 类应该使用 extendsShape 派生。Ball 的构造函数应该

  • 接收与之前相同的参数:xyvelXvelYsizecolor
  • 使用 super() 调用 Shape 构造函数,并将 xyvelXvelY 参数传递给它
  • 从传入的参数中初始化它自己的 colorsize 属性。

Ball 构造函数应该定义一个名为 exists 的新属性,用于跟踪球是否存在于程序中(还没有被邪恶圆圈吞噬)。它应该是一个布尔值 (true/false),在构造函数中初始化为 true

Ball 类的 collisionDetect() 方法需要一个小小的更新。只有当 exists 属性为 true 时,才需要考虑球进行碰撞检测。因此,将现有的 collisionDetect() 代码替换为以下代码

js
collisionDetect() {
  for (const ball of balls) {
    if (!(this === ball) && ball.exists) {
      const dx = this.x - ball.x;
      const dy = this.y - ball.y;
      const distance = Math.sqrt(dx * dx + dy * dy);

      if (distance < this.size + ball.size) {
        ball.color = this.color = randomRGB();
      }
    }
  }
}

如上所述,唯一增加的是检查球是否存在——通过在 if 条件语句中使用 ball.exists

球的 draw()update() 方法定义应该能够保持与之前完全相同。

此时,尝试重新加载代码——它应该与之前一样工作,使用我们重新设计的对象。

定义 EvilCircle

现在是时候让坏家伙登场了——EvilCircle()!我们的游戏中只会有一个邪恶圆圈,但我们仍然要使用从 Shape() 继承的构造函数来定义它,以便您练习一下。您可能想稍后在应用程序中添加另一个圆圈,它可以由另一个玩家控制,或者让多个由计算机控制的邪恶圆圈出现。您可能不会用一个邪恶圆圈来征服世界,但对于本评估来说已经足够了。

创建一个 EvilCircle 类的定义。它应该使用 extendsShape 继承。

EvilCircle 构造函数

EvilCircle 的构造函数应该

  • 只接收 xy 参数
  • xy 参数传递给 Shape 超类,以及硬编码为 20 的 velXvelY 值。您应该使用类似 super(x, y, 20, 20); 的代码来实现这一点
  • color 设置为 white,将 size 设置为 10

最后,构造函数应该设置使用户能够在屏幕上移动邪恶圆圈的代码

js
window.addEventListener("keydown", (e) => {
  switch (e.key) {
    case "a":
      this.x -= this.velX;
      break;
    case "d":
      this.x += this.velX;
      break;
    case "w":
      this.y -= this.velY;
      break;
    case "s":
      this.y += this.velY;
      break;
  }
});

这将向 window 对象添加一个 keydown 事件监听器,以便当按下某个键时,会查询事件对象的 key 属性,以查看按下了哪个键。如果它是四个指定键之一,那么邪恶圆圈就会向左/右/上/下移动。

为 EvilCircle 定义方法

EvilCircle 类应该有三个方法,如下所述。

draw()

该方法与 Balldraw() 方法具有相同的目的:在画布上绘制对象实例。EvilCircledraw() 方法的工作方式非常类似,因此您可以从复制 Balldraw() 方法开始。然后,您应该进行以下更改

  • 我们希望邪恶圆圈不填充,而只是有一个外围线(描边)。您可以通过更新 fillStylefill()strokeStylestroke() 来实现这一点。
  • 我们还希望使描边稍微更厚一些,这样您就可以更轻松地看到邪恶圆圈。这可以通过在 beginPath() 调用之后(3 即可)设置 lineWidth 的值来实现。

checkBounds()

该方法将执行与 Ballupdate() 方法的第一部分相同的操作——查看邪恶圆圈是否会移出屏幕边缘,并阻止它这样做。同样,您只需复制 Ballupdate() 方法即可,但您应该做一些更改

  • 删除最后两行——我们不想在每一帧上自动更新邪恶圆圈的位置,因为我们会以其他方式移动它,如您将在下面看到的那样。
  • if () 语句中,如果测试结果为真,我们不想更新 velX/velY;而是要更改 x/y 的值,以便邪恶圆圈稍微弹回屏幕上。添加或减去(视情况而定)邪恶圆圈的 size 属性是合理的。

collisionDetect()

该方法的工作方式与 BallcollisionDetect() 方法非常类似,因此您可以使用它的副本作为该新方法的基础。但是,有一些区别

  • 在外部 if 语句中,您不再需要检查正在迭代的当前球是否与进行检查的球相同——因为它不再是球,而是邪恶圆圈!相反,您需要做一项测试,以查看正在检查的球是否存在(您可以使用哪个属性来实现这一点?)。如果它不存在,说明它已经被邪恶圆圈吞噬了,因此无需再次检查它。
  • 在内部 if 语句中,您不再希望当检测到碰撞时使对象改变颜色——而是,您希望将与邪恶圆圈碰撞的所有球设置为不再存在(同样,您认为该怎么做?)。

将邪恶圆圈引入程序

现在我们已经定义了邪恶圆圈,我们需要让它出现在我们的场景中。为此,您需要对 loop() 函数进行一些更改。

  • 首先,创建一个新的邪恶圆圈对象实例(指定必要的参数)。您只需要执行一次,而不需要在循环的每次迭代中都执行。
  • 在您遍历每个球并为每个球调用 draw()update()collisionDetect() 函数的地方,请确保只有在当前球存在的情况下才调用这些函数。
  • 在循环的每次迭代中,调用邪恶圆圈实例的 draw()checkBounds()collisionDetect() 方法。

实现分数计数器

要实现分数计数器,请执行以下步骤

  1. 在您的 HTML 文件中,在包含文本“Ball count: ”的 h1 元素下方添加一个 <p> 元素。
  2. 在您的 CSS 文件中,在底部添加以下规则
    css
    p {
      position: absolute;
      margin: 0;
      top: 35px;
      right: 5px;
      color: #aaa;
    }
    
  3. 在您的 JavaScript 中,进行以下更新
    • 创建一个变量,用于存储指向该段落的引用。
    • 以某种方式保存屏幕上球的数量。
    • 每次在场景中添加球时,递增计数并显示更新的球的数量。
    • 每次邪恶圆圈吞噬一个球(导致它不存在)时,递减计数并显示更新的球的数量。