为我们的弹跳球演示添加功能
在此评估中,你应该使用上一篇文章中的弹跳球演示作为起点,并向其中添加一些新的有趣的功能。
¥In this assessment, you are expected to use the bouncing balls demo from the previous article as a starting point, and add some new and interesting features to it.
先决条件: | 在尝试进行此评估之前,你应该已经阅读了本模块中的所有文章。 |
---|---|
目标: | 测试对 JavaScript 对象和面向对象结构的理解 |
初始点
¥Starting point
要开始此评估,请在本地计算机的新目录中制作上一篇文章中的 index-finished.html、style.css 和 main-finished.js 的本地副本。
¥To get this assessment started, make a local copy of index-finished.html, style.css, and main-finished.js from our last article in a new directory in your local computer.
或者,你可以使用在线编辑器,例如 CodePen、JSFiddle 或 Glitch。你可以将 HTML、CSS 和 JavaScript 粘贴到这些在线编辑器之一中。如果你使用的在线编辑器没有单独的 JavaScript 面板,请随意将其内联到 HTML 页面内的 <script>
元素中。
¥Alternatively, you could use an online editor such as CodePen, JSFiddle, or Glitch. You could paste the HTML, CSS and JavaScript into one of these online editors. If the online editor you are using doesn't have a separate JavaScript panel, feel free to put it inline in a <script>
element inside the HTML page.
注意:如果你遇到困难,可以通过我们的 沟通渠道 之一与我们联系。
¥Note: If you get stuck, you can reach out to us in one of our communication channels.
提示和技巧
工程概要
¥Project brief
我们的弹力球演示很有趣,但现在我们想通过添加用户控制的邪恶圈来使其更具互动性,如果它抓住球,它就会吃掉球。我们还想通过创建我们的球和邪恶圆圈可以继承的通用 Shape()
对象来测试你的对象构建技能。最后,我们想要添加一个计分计数器来跟踪剩余要捕获的球的数量。
¥Our bouncy ball demo is fun, but now we want to make it a little bit more interactive by adding a user-controlled evil circle, which will eat the balls if it catches them. We also want to test your object-building skills by creating a generic Shape()
object that our balls and evil circle can inherit from. Finally, we want to add a score counter to track the number of balls left to capture.
下面的屏幕截图让你了解完成的程序应该是什么样子:
¥The following screenshot gives you an idea of what the finished program should look like:
为了让你有更多的了解,请查看 完成的例子(不要偷看源代码!)
¥To give you more of an idea, have a look at the finished example (no peeking at the source code!)
完成步骤
创建形状类
¥Create a Shape class
首先,创建一个新的 Shape
类。这只有一个构造函数。Shape
构造函数应以与 Ball()
构造函数最初相同的方式定义 x
、y
、velX
和 velY
属性,但不定义 color
和 size
属性。
¥First of all, create a new Shape
class. This has only a constructor. The Shape
constructor should define the x
, y
, velX
, and velY
properties in the same way as the Ball()
constructor did originally, but not the color
and size
properties.
Ball
类应该使用 extends
从 Shape
派生。Ball
的构造函数应该:
¥The Ball
class should be made to derive from Shape
using extends
. The constructor for Ball
should:
- 采用与之前相同的参数:
x
、y
、velX
、velY
、size
和color
- 使用
super()
调用Shape
构造函数,传入x
、y
、velX
和velY
参数 - 根据给定的参数初始化它自己的
color
和size
属性。
Ball
构造函数应该定义一个名为 exists
的新属性,用于跟踪程序中是否存在小球(没有被邪恶循环吃掉)。这应该是一个布尔值 (true
/false
),在构造函数中初始化为 true
。
¥The Ball
constructor should define a new property called exists
, which is used to track whether the balls exist in the program (have not been eaten by the evil circle). This should be a boolean (true
/false
), initialized to true
in the constructor.
Ball
类的 collisionDetect()
方法需要一个小更新。仅当 exists
属性为 true
时才需要考虑球进行碰撞检测。因此,将现有的 collisionDetect()
代码替换为以下代码:
¥The collisionDetect()
method of the Ball
class needs a small update. A ball needs to be considered for collision detection only if the exists
property is true
. So, replace the existing collisionDetect()
code with the following code:
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
来检查球是否存在。
¥As discussed above, the only addition is to check if the ball exists — by using ball.exists
in the if
conditional.
球 draw()
和 update()
方法定义应该能够与以前完全相同。
¥The ball draw()
and update()
method definitions should be able to stay exactly the same as they were before.
此时,尝试重新加载代码 - 对于我们重新设计的对象,它应该与以前一样工作。
¥At this point, try reloading the code — it should work just the same as it did before, with our redesigned objects.
定义邪恶圈
¥Defining EvilCircle
现在是时候来见见坏人了 - EvilCircle()
!我们的游戏只会涉及一个邪恶循环,但我们仍然会使用继承自 Shape()
的构造函数来定义它,以便给你一些练习。你可能想稍后向应用添加另一个可以由其他玩家控制的圈子,或者有几个计算机控制的邪恶圈子。你可能不会用一个邪恶的循环来接管世界,但它足以完成这个评估。
¥Now it's time to meet the bad guy — the EvilCircle()
! Our game is only going to involve one evil circle, but we are still going to define it using a constructor that inherits from Shape()
, to give you some practice. You might want to add another circle to the app later on that can be controlled by another player, or have several computer-controlled evil circles. You're probably not going to take over the world with a single evil circle, but it will do for this assessment.
为 EvilCircle
类创建定义。它应该使用 extends
从 Shape
继承。
¥Create a definition for an EvilCircle
class. It should inherit from Shape
using extends
.
邪恶圈构造函数
¥EvilCircle constructor
EvilCircle
的构造函数应该:
¥The constructor for EvilCircle
should:
- 仅传递
x
、y
参数 - 将
x
、y
参数连同硬编码为 20 的velX
和velY
值一起传递给Shape
超类。你应该使用super(x, y, 20, 20);
这样的代码来执行此操作 - 将
color
设置为white
,将size
设置为10
。
最后,构造函数应该设置使用户能够在屏幕上移动邪恶圆圈的代码:
¥Finally, the constructor should set up the code enabling the user to move the evil circle around the screen:
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
属性来查看按下的是哪个键。如果是指定的四个键之一,则邪恶圈将向左/右/上/下移动。
¥This adds a keydown
event listener to the window
object so that when a key is pressed, the event object's key
property is consulted to see which key is pressed. If it is one of the four specified keys, then the evil circle will move left/right/up/down.
为 EvilCircle 定义方法
¥Defining methods for EvilCircle
EvilCircle
类应该具有三个方法,如下所述。
¥The EvilCircle
class should have three methods, as described below.
draw()
此方法与 Ball
的 draw()
方法具有相同的目的:它在画布上绘制对象实例。EvilCircle
的 draw()
方法将以非常相似的方式工作,因此你可以从复制 Ball
的 draw()
方法开始。然后你应该进行以下更改:
¥This method has the same purpose as the draw()
method for Ball
: it draws the object instance on the canvas. The draw()
method for EvilCircle
will work in a very similar way, so you can start by copying the draw()
method for Ball
. You should then make the following changes:
- 我们不希望邪恶圈被填满,而只是有一条外线(描边)。你可以通过将
fillStyle
和fill()
分别更新为strokeStyle
和stroke()
来实现此目的。 - 我们还想让笔画粗一点,这样你就可以更容易地看到邪恶的圆圈。这可以通过在
beginPath()
调用之后的某处设置lineWidth
的值来实现(3 即可)。
checkBounds()
此方法将与 Ball
的 update()
方法的第一部分执行相同的操作 - 查看邪恶圆圈是否会离开屏幕边缘,并阻止它这样做。同样,你基本上可以将 update()
方法复制到 Ball
,但你应该进行一些更改:
¥This method will do the same thing as the first part of the update()
method for Ball
— look to see whether the evil circle is going to go off the edge of the screen, and stop it from doing so. Again, you can mostly just copy the update()
method for Ball
, but there are a few changes you should make:
- 去掉最后两行 - 我们不想在每一帧上自动更新邪恶圆圈的位置,因为我们将以其他方式移动它,如下所示。
- 在
if ()
语句中,如果测试返回 true,我们不想更新velX
/velY
;我们想改为更改x
/y
的值,以便邪恶圆圈稍微弹回到屏幕上。添加或减去(视情况而定)邪恶循环的size
属性是有意义的。
collisionDetect()
此方法的作用方式与 Ball
方法的 collisionDetect()
方法非常相似,因此你可以使用该方法的副本作为此新方法的基础。但有一些区别:
¥This method will act in a very similar way to the collisionDetect()
method for Ball
method, so you can use a copy of that as the basis of this new method. But there are a couple of differences:
- 在外部
if
语句中,你不再需要检查迭代中的当前球是否与正在检查的球相同 - 因为它不再是球,它是邪恶循环!相反,你需要进行测试以查看正在检查的球是否存在(你可以使用哪个属性来执行此操作?)。如果不存在,那就已经被邪恶圈子吃掉了,没必要再去检查。 - 在内部
if
语句中,你不再希望在检测到碰撞时使对象改变颜色 - 相反,你希望将与邪恶圆圈碰撞的任何球设置为不再存在(再次,你认为你如何? 会这样做吗?)。
将邪恶圈带入程序
¥Bringing the evil circle into the program
现在我们已经定义了邪恶圈,我们需要让它真正出现在我们的场景中。为此,你需要对 loop()
函数进行一些更改。
¥Now we've defined the evil circle, we need to actually make it appear in our scene. To do this, you need to make some changes to the loop()
function.
- 首先,创建一个新的邪恶循环对象实例(指定必要的参数)。你只需执行一次此操作,而不是在循环的每次迭代中执行此操作。
- 在循环遍历每个球并为每个球调用
draw()
、update()
和collisionDetect()
函数时,请确保仅当当前球存在时才调用这些函数。 - 在循环的每次迭代中调用邪恶循环实例的
draw()
、checkBounds()
和collisionDetect()
方法。
实现得分计数器
¥Implementing the score counter
要实现分数计数器,请执行以下步骤:
¥To implement the score counter, follow the following steps: