这篇翻译不完整。请帮忙从英语翻译这篇文章

在前面的文章中,我们学习了 JavaScript 的面向对象理论和基本的语法知识,这些都是非常好的基础。这篇文章中我们将进行一次实战演练,练习如何构造 JavaScript 对象,最终你会得到一个色彩缤纷的结果!

预备知识: 基本的计算机知识,了解HTML与CSS的基本概念,熟悉JavaScript基本知识 (请参阅 First steps 和 Building blocks) 和面向对象的JavaScript (OOJS) 基础 (请参阅 Introduction to objects).
目标: 练习对象和面向对象的技术

实现一些弹跳球

在这篇文章中我们将写一个”bouncing balls“的实例。我们的小球会在屏幕上弹跳,当它们碰到彼此时会变色。最终会像这样:

这个实例将会利用 Canvas API 来在屏幕上画小球, 还会用到 requestAnimationFrame API 来使整个画面动起来 — 你不需要知道关于这些 API 的知识, 然后我们希望你完成这个练习之后你会有兴趣探索这些知识。这个过程中我们会用到一些非常小巧的东西,会向你展示一些技巧比如小球从墙上跳下来,比如检查它们是否撞到了对方 (也就是碰撞检测).

让我们开始吧

首先, 将这些文件 index.html, style.css, and main.js 复制到本地. 这些文件分别包括以下内容:

  1. 一个非常简单的 HTML 文档,包括一个 <h1> 元素, 一个<canvas> 元素来画小球,还有一些元素将 CSS 和 JavaScript 运用到我们的 HTML 中。
  2. 一些非常简单的样式,主要是运用到 <h1> 元素上,摆脱了滚动条和页面边缘的空白(从而看起来非常简洁)
  3. 一些 JavaScript 用来设置 <canvas> 元素,提供我们要用到的基本函数。

第一部分的代码先这样:

var canvas = document.querySelector('canvas');

var ctx = canvas.getContext('2d');

var width = canvas.width = window.innerWidth;
var height = canvas.height = window.innerHeight;

这个脚本使用变量代指了 <canvas> 元素, 然后对其调用 getContext() 从而我们获得一个开始画画的环境。存储以上操作结果的变量(ctx)是一个对象,直接代指 canvas 上的一块允许我们绘制 2D 图形的区域。

接下来,我们设置 width 和 height 变量,并且让 canvas 元素的宽和高(分别使用 canvas.width 和 canvas.height 表示)等于浏览器的宽和高(也就是网页显示的区域 — 可以从Window.innerWidth 和 Window.innerHeight参数获得)。

你会看到我们在这里串联了多个赋值表达式在一起,这样能更快地设置变量.-这是完全正确的。

最后一点原始代码如下:

function random(min, max) {
  var num = Math.floor(Math.random() * (max - min + 1)) + min;
  return num;
}

这个函数将两个数字作为参数传入,并且返回一个这两个数字之间的一个随机数。

将我们项目中的小球模型化

我们的项目中会有很多小球在屏幕上跳来跳去。因此这些小球会以相同的方式运作,从而我们可以通过一个对象实例化它们。首先,我们将下面的构造器加入到代码的底部。

function Ball(x, y, velX, velY, color, size) {
  this.x = x;
  this.y = y;
  this.velX = velX;
  this.velY = velY;
  this.color = color;
  this.size = size;
}

这个构造器中定义了每个小球需要的参数:

  • x 和 y 坐标 — 小球在屏幕上最开始时候的坐标。坐标的范围从 0 (左上角)到浏览器视窗的宽和高(右下角)。
  • 水平和竖直速度(velX 和 velY)— 我们会给每个小球一个水平和竖直速度。实际上,当我们让这些球开始运动时候,每过一帧都会给小球的 x 和 y 坐标加一次这些值。
  • color — 每一个小球会有自己的颜色。
  • size — 每一个小球会有自己的大小 — 也就是小球的半径,以像素为单位。

这里说明了参数,那么方法呢?实际上我们想让小球动起来。

画小球

首先给小球的原型加上 draw( ) 方法:

Ball.prototype.draw = function() {
  ctx.beginPath();
  ctx.fillStyle = this.color;
  ctx.arc(this.x, this.y, this.size, 0, 2 * Math.PI);
  ctx.fill();
}

通过使用这个函数,我们就可以让小球在屏幕上画出自己了,通过使用我们之前定义的 ctx 的方法。ctx的内容区域就像是一张纸,现在我们就可以命令我们的笔画一点东西。

  • 首先,我们使用 beginPath() 来声明我们现在要开始在纸上画一个图形了。
  • 然后,我们使用 fillStyle 来定义这个形状的颜色 — 我们把这个加到小球的颜色属性。
  • 接下来,我们使用 arc() 方法来在纸上画出一段圆弧。有这些参数:
    • x 和 y 是 arc 中心的坐标 — 也就是小球的中心坐标。
    • arc 的半径 — 小球的半径。
    • 最后两个参数是开始和结束的角度,也就是圆弧对应的夹角。这里我们用的是 0 和 2 * PI,也就是 360 度(如果你设置成 0 和 1 * PI,则只会出现一个半圆,也就是 180 度)
  • 最后,我们使用 fill() 方法,也就是声明我们结束了以 beginPath()开始的绘画,并且使用我们之前设置的颜色进行填充。 

现在你已经可以测试你的对象了。

  1. 保存代码,将 HTML 加载到浏览器中。
  2. 打开浏览器中的 JavaScript 控制台,刷新页面,从而画布可以根据可视的区域调整自己的大小。
  3. 通过下面的代码创建一个小球实例。
    var testBall = new Ball(50, 100, 4, 4, 'blue', 10);
  4. 返回实例中的属性值。
    testBall.x
    testBall.size
    testBall.color
    testBall.draw()
  5. 当你键入最后一行的时候,你会在你的画布上看到一个小球被画出来了。

更新小球的数据

我们可以在一个固定位置画出小球,但是他们不会动,我们需要一种函数来进行更新。在 JavaScript 文件底部加上下面的代码,也就是给小球原型加上一个 update()方法。

Ball.prototype.update = function() {
  if ((this.x + this.size) >= width) {
    this.velX = -(this.velX);
  }

  if ((this.x - this.size) <= 0) {
    this.velX = -(this.velX);
  }

  if ((this.y + this.size) >= height) {
    this.velY = -(this.velY);
  }

  if ((this.y - this.size) <= 0) {
    this.velY = -(this.velY);
  }

  this.x += this.velX;
  this.y += this.velY;
}

函数的前四个部分用来检查小球是否碰到画布的边缘.如果碰到,我们反转小球的方向来让它向反方向移动. 就比如说,如果小球正向上移动 (正 velY), 然后垂直速度发生改变,小球就向下移动 (负 velY).

在这四部分中,我们:

  • 检查小球的x坐标是否大于画布的宽度 (小球会从右图廓离开).
  • 检查小球的x坐标是否小于0 (小球会从左图廓离开).
  • 检查小球的y坐标是否大于画布的高度(小球会从下图廓离开).
  • 检查小球的y坐标是否小于0 (小球会从上图廓离开).

在每种情况下,我们都会加上小球的半径,因为 x/y 坐标是小球中心的坐标,但是我们希望小球在其边界接触浏览器窗口的边界时反弹,而不是小球的一部分都不见了再返回。

最后两行,我们将 velX 的值加到 x 的坐标上,将 velY 的值加到 y 坐标上 — 每次调用这个方法的时候小球就移动这么多。

暂时先这样做; 让我们继续做一些动画!

让球动起来 

现在就变得非常有趣了。我们在画布上加上一些小球,并且让他们动起来。

  1. 首先我们需要一个地方储存小球,下面的数组会干这件事 — 现在将它添加到你的代码底部:
    var balls = [];

    几乎所有的动画效果都会用到一个运动循环,也就是每一帧都自动更新视图。这是大多数游戏或者其他类似项目的基础。

  2. 现在将它添加到你的代码底部:
    function loop() {
      ctx.fillStyle = 'rgba(0, 0, 0, 0.25)';
      ctx.fillRect(0, 0, width, height);
    
      while (balls.length < 25) {
        var ball = new Ball(
          random(0,width),
          random(0,height),
          random(-7,7),
          random(-7,7),
          'rgb(' + random(0,255) + ',' + random(0,255) + ',' + random(0,255) +')',
          random(10,20)
        );
        balls.push(ball);
      }
    
      for (var i = 0; i < balls.length; i++) {
        balls[i].draw();
        balls[i].update();
      }
    
      requestAnimationFrame(loop);
    }

    loop()函数做了下面的事情:

    • 将整个画布的颜色设置成半透明的黑色。然后使用  fillRect() (这四个参数提供一个起始坐标, 绘制的矩形的宽和高) 在画布的整个宽度和高度上绘制整个矩形的颜色。这是在下一个视图画出来时用来遮住之前的视图的。如果不这样做得话,你就会在屏幕上看到一条蛇的形状而不是小球的运动了。用来填充的颜色设置成半透明的,rgba(0,0,0,0.25),也就是让之前的视图留下来一点点,从而你可以看到小球运动时的尾巴。如果你将 0.25 设置成 1 时,你就完全看不到了。试着改变其中的值查看造成的影响。
    • 当且仅当小球数量小于 25 时,将 random( ) 函数产生的数字传入新的小球实例从而创建一个新的小球,并且加入到数组中。因此当屏幕上有 25 个小球时,不会再出现更多小球。你可以改变这个值,从而看到不同小球个数造成的影响。如果你的电脑或者浏览器性能不怎么样的话,几千个小球的速度就会明显慢下来。
    • 遍历数组中的所有小球,并且让每个小球都调用 draw( ) 和 update( ) 函数来将自己画出来,并且再接下来的每一帧都按照其速度进行必要的跟新。
    • 使用 requestAnimationFrame() 方法再运行一次函数 — 当一个函数正在运行时传递相同的函数名,从而每隔一小段时间都会运行一次这个函数,从而得到一个平滑的动画效果。这主要是通过递归完成的 — 也就是说函数每次运行的时候都会调用自己,从而可以一遍又一遍得运行。
  3. 最后但是非常重要的是,加上下面这一行 — 让动画开始运行的话我们需要调用这个函数。
    loop();

完成这些基础的之后在浏览器打开测试一下!

增加撞击侦察

现在会更加有趣,给我们的项目加上撞击侦察,从而当小球撞击时代码可以察觉到。

  1. 首先在 update( ) 方法下加上下面的方法 (例如 Ball.prototype.update的下面)。
    Ball.prototype.collisionDetect = function() {
      for (var j = 0; j < balls.length; j++) {
        if (!(this === balls[j])) {
          var dx = this.x - balls[j].x;
          var dy = this.y - balls[j].y;
          var distance = Math.sqrt(dx * dx + dy * dy);
    
          if (distance < this.size + balls[j].size) {
            balls[j].color = this.color = 'rgb(' + random(0, 255) + ',' + random(0, 255) + ',' + random(0, 255) +')';
          }
        }
      }
    }

    这个方法有一点点复杂,因此如果不理解的话不必过分担心,对代码的解释如下:

    • 对于每个小球,我们都要检查其他的小球是否和当前这个小球相撞了。为了达到此目的,我们构造另外一个循环来遍历数组中的小球。
    • 在循环里面,我们使用一个条件判断来检查当前遍历的小球是否与当前的小球相同。我们不希望检测到一个小球撞到了自己!为了达到这个目的,我们需要检查当前小球 (即正在调用collisionDetect方法的球) 是否和被循环到的小球 (for循环检测中的当前遍历所引用的球) 是不是同一个。我们使用 !来否定判断,因此只有两个小球不是同一个时,条件判断中的代码才会运行。
    • 我们使用了一个常见的算法来检测两个小球是否相撞了,两个小球中心的距离是否小于两个小球的半径之和。这些会在 2D collision detection 介绍地更加详细。
    • 如果两个小球真的相撞了,会运行 if 下的代码。我们会将两个小球的颜色都设置成随机的一种。我们也可以将这步操作变得复杂一点,比如让两个小球弹开,那样需要植入更加复杂的代码。像这样的物理场景,有以下专门的库比如 PhysicsJS, matter.js, Phaser 等。
  2. 我们也需要在每一帧动画中都调用这个函数,因此在 balls[i].update() 加上下面的代码:
    balls[i].collisionDetect();
  3. 保存文件,刷新浏览器,你就会看到小球在撞击之后变色了!

注意: 如果你无法让此案例顺利执行,可参考源码(finished version) ,或者参阅实际执行的效果(running live).

概要

我们希望你玩得开心,编写出你自己的随机弹跳球的例子,在整个程序中使用各种对象和面向对象的技术! 这应该给你一些使用对象的有用的实践。

对象文章就到这里了。现在剩下的就是在下一章的对象评估中测试你的技能。

另可参见

文档标签和贡献者

最后编辑者: NotDead-NotPerish-Rebirth,