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

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

Prerequisites: Basic computer literacy, a basic understanding of HTML and CSS, familiarity with JavaScript basics (see First steps and Building blocks) and OOJS basics (see Introduction to objects).
Objective: To get some practice with using objects and object-oriented techniques in a real world context.

Let's bounce some balls

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

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

让我们开始吧

首先, 将这些文件 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参数获得)。

You'll see here that we are chaining multiple assignments together, to get the variables all set quicker — this is perfectly OK.

最后一点原始代码如下:

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 的方法。context 就像是一张纸,现在我们就可以命令我们的笔画一点东西。

  • 首先,我们使用 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;
}

The first four parts of the function check whether the ball has reached the edge of the canvas. If it has, we reverse the polarity of the relevant velocity to make the ball travel in the opposite direction. So for example, if the ball was traveling upwards (positive velY), then the vertical velocity is changed so that it starts to travel downwards instead (negative velY).

In the four cases, we are:

  • Checking to see whether the x coordinate is greater than the width of the canvas (the ball is going off the right hand edge).
  • Checking to see whether the x coordinate is smaller than 0 (the ball is going off the left hand edge).
  • Checking to see whether the y coordinate is greater than the height of the canvas (the ball is going off the bottom edge).
  • Checking to see whether the y coordinate is smaller than 0 (the ball is going off the top edge).

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

The last two lines add the velX value to the x coordinate, and the velY value to the y coordinate — the ball is in effect moved each time this method is called.最后两行,我们将 velX 的值加到 x 的坐标上,将 velY 的值加到 y 坐标上 — 每次调用这个方法的时候小球就移动这么多。

This will do for now; let's get on with some animation!

让球动起来 

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

  1. First, we need somewhere to store all our balls. The following array will do this job — add it to the bottom of your code now:
    var balls = [];

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

  2. Add the following to the bottom of your code now:
    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() (the four parameters provide a start coordinate, and a width and height for the rectangle drawn) draws a rectangle of the color across the whole width and height of the canvas。这是在下一个视图画出来时用来遮住之前的视图的。如果不这样做得话,你就会在屏幕上看到一条蛇的形状而不是小球的运动了。用来填充的颜色设置成半透明的,rgba(0,0,0,0.25),也就是让之前的视图留下来一点点,从而你可以看到小球运动时的尾巴。如果你将 0.25 设置成 1 时,你就完全看不到了。试着改变其中的值查看造成的影响。
    • 当且仅当小球数量小于 25 时,将 random( ) 函数产生的数字传入新的小球实例从而创建一个新的小球,并且加入到数组中。因此当屏幕上有 25 个小球时,不会再出现更多小球。你可以改变这个值,从而看到不同小球个数造成的影响。如果你的电脑或者浏览器性能不怎么样的话,几千个小球的速度就会明显慢下来。
    • 遍历数组中的所有小球,并且让每个小球都调用 draw( ) 和 update( ) 函数来将自己画出来,并且再接下来的每一帧都按照其速度进行必要的跟新。
    • 使用 requestAnimationFrame() 方法再运行一次函数 — when this method is constantly run and passed the same function name, it will run that function a set number of times per second to create a smooth animation. This is generally done recursively — which means that the function is calling itself every time it runs, so it will run over and over again.当一个函数正在运行时传递相同的函数名,从而每隔一小段时间都会运行一次这个函数,从而得到一个平滑的动画效果。这主要是通过递归完成的 — 也就是说函数每次运行的时候都会调用自己,从而可以一遍又一遍得运行。
  3. 最后但是非常重要的是,加上下面这一行 — 让动画开始运行的话我们需要调用这个函数。
    loop();

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

增加撞击侦察

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

  1. 首先在 update( ) 方法下加上下面的方法 (i.e. the Ball.prototype.update block)。
    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) +')';
          }
        }
      }
    }

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

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

Note: If you have trouble getting this example to work, try comparing your JavaScript code against our finished version (also see it running live).

Summary

We hope you had fun writing your own real world random bouncing balls example, using various object and object-oriented techniques from throughout the module! This should have given you some useful practice in using objects, and good real world context.

That's it for object articles — all that remains now is for you to test your skills in the object assessment.

See also

文档标签和贡献者

 此页面的贡献者: shifengchen, Zeng
 最后编辑者: shifengchen,