在我们的弹球演示中添加功能
在本评估中,您需要使用上一篇文章中的弹跳球演示作为起点,并添加一些新的、有趣的特性。
先决条件 | 在尝试本评估之前,您应该已经完成了本模块中的所有文章。 |
---|---|
目标 | 测试对 JavaScript 对象和面向对象结构的理解 |
起点
要开始本评估,请在本地计算机上的新目录中创建以下文件的本地副本:index-finished.html,style.css,以及main-finished.js,它们来自我们上一篇文章。
或者,您可以使用在线编辑器,例如CodePen,JSFiddle,或Glitch。您可以将 HTML、CSS 和 JavaScript 粘贴到其中一个在线编辑器中。如果使用的在线编辑器没有单独的 JavaScript 面板,请随意将其内嵌到 HTML 页面中的 <script>
元素中。
注意:如果您卡住了,您可以通过我们的沟通渠道之一与我们联系。
提示和技巧
在您开始之前,有几点需要提醒您。
- 本评估相当具有挑战性。在开始编码之前,请仔细阅读整个评估内容,并逐个步骤缓慢而谨慎地进行。
- 最好在每个阶段运行后保存演示的单独副本,这样如果之后遇到问题,您就可以参考它。
项目简述
我们的弹跳球演示很有趣,但现在我们要通过添加一个用户控制的邪恶圆圈来使其更具互动性,如果邪恶圆圈碰到球,就会吞噬它们。我们还想通过创建一个通用的 Shape()
对象,让我们的球和邪恶圆圈能够从它继承,来测试您的对象构建技能。最后,我们要添加一个分数计数器来跟踪剩余的球的数量。
以下屏幕截图展示了完成后的程序的外观。
为了让您更好地了解,请查看完成的示例(不要偷看源代码!)
完成步骤
以下部分描述了您需要做的事情。
创建 Shape 类
首先,创建一个新的 Shape
类。它只有一个构造函数。Shape
构造函数应该与最初 Ball()
构造函数的方式相同,定义 x
、y
、velX
和 velY
属性,但不要定义 color
和 size
属性。
Ball
类应该使用 extends
从 Shape
派生。Ball
的构造函数应该
- 接收与之前相同的参数:
x
、y
、velX
、velY
、size
和color
- 使用
super()
调用Shape
构造函数,并将x
、y
、velX
和velY
参数传递给它 - 从传入的参数中初始化它自己的
color
和size
属性。
Ball
构造函数应该定义一个名为 exists
的新属性,用于跟踪球是否存在于程序中(还没有被邪恶圆圈吞噬)。它应该是一个布尔值 (true
/false
),在构造函数中初始化为 true
。
Ball
类的 collisionDetect()
方法需要一个小小的更新。只有当 exists
属性为 true
时,才需要考虑球进行碰撞检测。因此,将现有的 collisionDetect()
代码替换为以下代码
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
类的定义。它应该使用 extends
从 Shape
继承。
EvilCircle 构造函数
EvilCircle
的构造函数应该
- 只接收
x
和y
参数 - 将
x
和y
参数传递给Shape
超类,以及硬编码为 20 的velX
和velY
值。您应该使用类似super(x, y, 20, 20);
的代码来实现这一点 - 将
color
设置为white
,将size
设置为10
。
最后,构造函数应该设置使用户能够在屏幕上移动邪恶圆圈的代码
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()
该方法与 Ball
的 draw()
方法具有相同的目的:在画布上绘制对象实例。EvilCircle
的 draw()
方法的工作方式非常类似,因此您可以从复制 Ball
的 draw()
方法开始。然后,您应该进行以下更改
- 我们希望邪恶圆圈不填充,而只是有一个外围线(描边)。您可以通过更新
fillStyle
和fill()
为strokeStyle
和stroke()
来实现这一点。 - 我们还希望使描边稍微更厚一些,这样您就可以更轻松地看到邪恶圆圈。这可以通过在
beginPath()
调用之后(3 即可)设置lineWidth
的值来实现。
checkBounds()
该方法将执行与 Ball
的 update()
方法的第一部分相同的操作——查看邪恶圆圈是否会移出屏幕边缘,并阻止它这样做。同样,您只需复制 Ball
的 update()
方法即可,但您应该做一些更改
- 删除最后两行——我们不想在每一帧上自动更新邪恶圆圈的位置,因为我们会以其他方式移动它,如您将在下面看到的那样。
- 在
if ()
语句中,如果测试结果为真,我们不想更新velX
/velY
;而是要更改x
/y
的值,以便邪恶圆圈稍微弹回屏幕上。添加或减去(视情况而定)邪恶圆圈的size
属性是合理的。
collisionDetect()
该方法的工作方式与 Ball
的 collisionDetect()
方法非常类似,因此您可以使用它的副本作为该新方法的基础。但是,有一些区别
- 在外部
if
语句中,您不再需要检查正在迭代的当前球是否与进行检查的球相同——因为它不再是球,而是邪恶圆圈!相反,您需要做一项测试,以查看正在检查的球是否存在(您可以使用哪个属性来实现这一点?)。如果它不存在,说明它已经被邪恶圆圈吞噬了,因此无需再次检查它。 - 在内部
if
语句中,您不再希望当检测到碰撞时使对象改变颜色——而是,您希望将与邪恶圆圈碰撞的所有球设置为不再存在(同样,您认为该怎么做?)。
将邪恶圆圈引入程序
现在我们已经定义了邪恶圆圈,我们需要让它出现在我们的场景中。为此,您需要对 loop()
函数进行一些更改。
- 首先,创建一个新的邪恶圆圈对象实例(指定必要的参数)。您只需要执行一次,而不需要在循环的每次迭代中都执行。
- 在您遍历每个球并为每个球调用
draw()
、update()
和collisionDetect()
函数的地方,请确保只有在当前球存在的情况下才调用这些函数。 - 在循环的每次迭代中,调用邪恶圆圈实例的
draw()
、checkBounds()
和collisionDetect()
方法。