字节青训营前端实践01
使用canvas来实现一个弹跳小球小游戏
本文通过编写一个弹球 demo 来展示 JavaScript 中对象的重要性。我们的小球会在屏幕上弹跳,当它们碰到彼此时会变色。最终会像这样:
这个实例将会利用 Canvas API 来在屏幕上画小球,还会用到 requestAnimationFrame API 来使整个画面动起来,这个过程中我们会用到一些技巧,比如小球从墙上反弹,检查它们是否撞到了对方(也就是碰撞检测)。
项目结构
包含以下三个文件:index.html、style.css 和 main.js。它们分别包含以下内容:
一个非常简单的 HTML 文档,包括一个 <h1>
元素、一个<canvas>
元素来画小球,还有一些元素将 CSS 和 JavaScript 运用到我们的 HTML 中。
一些非常简单的样式,主要是 <h1>
元素的样式和定位,另外还能使画面填充整个页面从而摆脱滚动条和边缘的空白(这样看起来非常简洁)
一些 JavaScript 用来设置 <canvas>
元素,并提供我们要用到的基本函数。
1 |
|
1 | html, body { |
JavaScript代码
接下来我们来分析JavaScript代码
准备部分
脚本的第一部分是:
1 | const canvas = document.querySelector("canvas"); |
用canvas
变量代指<canvas>
元素,然后对其调用了getContext()
,来获得了一个开始画画的环境,赋给了ctx
,ctx
是一个对象,直接代指画布上的一块允许我们绘制 2D 图形的区域。
接下来,我们设置 width
和 height
变量,并且让画布元素的宽和高(分别使用 canvas.width
和 canvas.height
表示)等于浏览器的宽和高(也就是网页显示的区域 — 可以从 Window.innerWidth
和 Window.innerHeight
参数获得)。
这里的小技巧是串联了多个赋值表达式在一起,这样能更快地设置变量。
1 | // 生成随机数的函数 |
第一个函数为我们生成一个 min 至 max 之间的随机整数,第二个函数为我们生成一个随机的颜色值。事实上第二个函数返回的是一个格式为’rgb(1,2,3)’这样的字符串,这是css颜色属性的格式
为程序中的小球建立模型
我们的项目中会有很多小球在屏幕上跳来跳去。因此这些小球会以相同的方式运作,从而我们可以通过一个对象实例化它们。首先,我们将下面的构造器加入到代码的底部。
1 | function Ball(x, y, velX, velY, color, size) { |
这个构造器中定义了每个小球需要的参数:
- x 和 y 坐标 —— 小球在屏幕上最开始时候的坐标。坐标的范围从 0(左上角)到浏览器视口的宽和高(右下角)。
- 水平和竖直速度(velX 和 velY)—— 我们会给每个小球一个水平和竖直速度。实际上,当我们让这些球开始运动时候,每过一帧都会给小球的 x 和 y 坐标加一次这些值。
- color —— 每一个小球会有自己的颜色。
- size —— 每一个小球会有自己的大小 — 也就是小球的半径,以像素为单位。
为小球的原型加上方法
画小球
首先给小球的原型加上 draw() 方法:
1 | Ball.prototype.draw = function () { |
通过使用这个函数,通过使用我们之前定义的 ctx对象 的方法,我们就可以让在屏幕上画出小球了。ctx 的内容区域就像是一张纸,现在我们就可以命令我们的笔画一点东西。
- 首先,我们使用 beginPath() 来声明我们现在要开始在纸上画一个图形了。
- 然后,我们使用 fillStyle 来定义这个图形的颜色 — 这个值正是小球的颜色属性。
- 接下来,我们使用 arc() 方法来在纸上画出一段圆弧。有这些参数:
- x 和 y 是圆弧的中心的坐标 —— 也就是小球的中心坐标。
- 圆弧的半径 —— 小球的半径。
- 最后两个参数是开始和结束,也就是圆弧对应的夹角,单位以弧度表示。这里我们用的是 0 和 2 * PI,也就是 360 度(如果你设置成 0 和 1 * PI,则只会出现一个半圆,也就是 180 度)
- 最后,我们使用 fill() 方法,也就是声明我们结束了以 beginPath() 开始的绘画,并且使用我们之前设置的颜色进行填充。
我们可以先测试一下小球能否被画出来,通过一下的代码
1 | let testBall = new Ball(50, 100, 4, 4, "blue", 10); |
这样你应该会在你的画布上看到一个小球被画出来了。
更新小球的数据
我们需要一个函数来更新小球的状态,所以给小球的原型添加一个update()
方法
1 | Ball.prototype.update = function () { |
代码的前四个if
部分是用来检查小球是否碰到画布的边缘。如果碰到我们就反转小球的速度方向。同时我们都会加上小球的半径,因为 x
或者y
坐标是小球中心的坐标,我们希望小球在其边界接触浏览器窗口的边界时反弹,而不是小球的一部分都不见了再返回。
最后两行,我们将 velX
的值加到 x
的坐标上,将 velY
的值加到 y
坐标上 —— 每次调用这个方法的时候小球就移动这么多。
制作小球的动画
- 首先我们需要一个地方储存小球,下面的数组会干这件事
1 | let balls = []; |
- 给小球的原型添加collisionDetect()方法
1 | Ball.prototype.collisionDetect = function () { |
- 对于每个小球,我们都要检查其他的小球是否和当前这个小球相撞了。为了达到此目的,我们构造另外一个 for 循环来遍历 balls[] 数组中的小球。
- 在循环里面,我们使用一个 if 语句来检查遍历的小球是否是当前的小球。我们不希望检测到一个小球撞到了自己!为了达到这个目的,我们需要检查当前小球 (即正在调用 collisionDetect 方法的球) 是否和被循环到的小球 (for 循环检测中的当前遍历所引用的球) 是不是同一个。我们使用 ! 来否定判断,因此只有两个小球不是同一个时,条件判断中的代码才会运行。
- 我们使用了一个常见的算法来检测两个小球是否相撞了,两个小球中心的距离是否小于两个小球的半径之和。
- 如果检测到了碰撞,会运行 if 语句中的代码。我们会将两个小球的颜色都设置成随机的一种。
- 几乎所有的动画效果都会用到一个运动循环,也就是每一帧都自动更新视图。这是大多数游戏或者其他类似项目的基础。
1 | function loop() { |
loop() 函数做了下面的事情:
- 将整个画布的颜色设置成半透明的黑色。然后使用 fillRect()(这四个参数分别是起始的坐标、绘制的矩形的宽和高)画出一个填充满整个画布的矩形。这是在下一个视图画出来时用来遮住之前的视图的。如果不这样做得话,你就会在屏幕上看到一条蛇的形状而不是小球的运动了。用来填充的颜色设置成半透明的rgba(0,0,0,0.25),也就是让之前的视图留下来一点点,从而你可以看到小球运动时的轨迹。如果你将 0.25 设置成 1 时,你就完全看不到了。试着改变其中的值查看造成的影响。
- 当且仅当小球数量小于等于 25 时,将 random() 函数产生的数字传入新的小球实例从而创建一个新的小球,并且加入到数组中。
- 遍历数组中的所有小球,并且让每个小球都调用 draw() 和 update() 函数来将自己画出来,并且再接下来的每一帧都按照其速度进行位置的更新。
- 使用 requestAnimationFrame() 方法再运行一次函数 —— 当一个函数正在运行时传递相同的函数名,从而每隔一小段时间都会运行一次这个函数,这样我们可以得到一个平滑的动画效果。这主要是通过递归完成的 —— 也就是说函数每次运行的时候都会调用自己,从而可以一遍又一遍得运行。
记得调用loop函数
1 | loop(); |
这样就完成了弹跳彩球的小游戏,运行一下index.html
看一下吧。