Object building practice

 

在之前的文章中,我们研究了所有基本的JavaScript对象理论和语法细节,为您提供了坚实的基础. 在本文中,我们将深入实践练习,为您提供更多构建自定义JavaScript对象的实践,从而获得有趣而丰富多彩的结果.

Prerequisites: 基本的计算机知识,对HTML和CSS的基本理解,对JavaScript的基础知识(请参阅" 第一步构建模块" )和OOJS的基础知识(请参见"对象简介" ).
Objective: 在实际环境中使用对象和面向对象技术获得一些实践.

Let's bounce some balls

在本文中,我们将编写一个经典的"弹跳球"演示,向您展示如何在JavaScript中使用有用的对象. 我们的小球会在屏幕上弹跳,并在彼此接触时改变颜色. 完成的示例将如下所示:

此示例将利用Canvas API将球绘制到屏幕上,并使用requestAnimationFrame API来对整个显示进行动画处理-您无需事先了解这些API,我们希望到那时为止完成本文后,您将有兴趣进一步探索它们. 在此过程中,我们将利用一些漂亮的对象,并向您展示一些不错的技术,例如将球从墙壁上弹起,并检查它们是否相互碰撞 (也称为碰撞检测 ).

Getting started

首先,对我们的index.htmlstyle.cssmain.js文件进行本地复制. 这些分别包含以下内容:

  1. 一个非常简单的HTML文档,具有一个<h1>元素,一个用于绘制球的<canvas>元素以及将我们的CSS和JavaScript应用于HTML的元素.
  2. 一些非常简单的样式,主要用于设置<h1>样式和位置,并消除页面边缘周围的任何滚动条或边距(这样看起来很整洁).
  3. 一些JavaScript用于设置<canvas>元素并提供我们将要使用的常规功能.

脚本的第一部分如下所示:

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

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

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

该脚本获取对<canvas>元素的引用,然后在其上调用getContext()方法,从而为我们提供可以开始绘制的上下文. 结果常数( ctx )是直接代表画布绘制区域并允许我们在其上绘制2D形状的对象.

接下来,我们设置名为widthheight常量,并将canvas元素的宽度和高度(由canvas.widthcanvas.height属性表示)设置为等于浏览器视口的宽度和高度(网页显示的区域) —可以从Window.innerWidthWindow.innerHeight属性获得).

Note that we are chaining multiple assignments together, to get the variables all set quicker — this is perfectly OK.

初始脚本的最后一部分如下所示:

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

此函数以两个数字作为参数,并返回介于两个数字之间的一个随机数.

Modeling a ball in our program

我们的程序将在屏幕上弹起许多球. 由于这些球都将以相同的方式起作用,因此用对象表示它们是有意义的. 让我们从在代码底部添加以下构造函数开始.

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;
}

这里,我们包含一些参数,这些参数定义了每个球在程序中起作用所需的属性:

  • xy坐标-球在屏幕上开始的水平和垂直坐标. 范围是0(左上角)到浏览器视口的宽度和高度(右下角).
  • 水平和垂直速度( velXvelY )-每个球都具有水平和垂直速度; 实际上,当我们对球进行动画处理时,这些值会定期添加到x / y坐标值中,以使它们在每一帧上移动这么多.
  • color -每个球都有颜色.
  • size -每个球都有一个大小-这是它的半径(以像素为单位).

这处理属性,但是方法呢? 我们想让自己的球在程序中真正发挥作用.

Drawing the ball

首先,将以下draw()方法添加到Ball()prototype

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

使用此功能,我们可以通过调用之前定义的2D画布上下文的一系列成员( ctx )来告诉球将其自身绘制到屏幕上. 上下文就像纸张,现在我们要命令笔在上面画些东西:

  • 首先,我们使用beginPath()声明要在纸上绘制形状.
  • 接下来,我们使用fillStyle定义形状所需的color -将其设置为球的color属性.
  • 接下来,我们使用arc()方法在纸张上绘制圆弧形状. 其参数为:
    • 圆弧中心的xy位置-我们指定球的xy属性.
    • 圆弧的半径,在这种情况下,是球的size属性.
    • 最后两个参数指定围绕其绘制圆弧的圆的度数的起点和终点. 在这里,我们指定0度和2 * PI ,这相当于以弧度表示的360度(令人讨厌的是,您必须以弧度指定). 这给了我们一个完整的圈子. 如果仅指定1 * PI ,则将得到一个半圆(180度).
  • 最后,我们使用fill()方法,该方法基本上声明"完成绘制以beginPath()开始的路径,并使用我们在fillStyle指定的颜色填充它占用的区域."

You can start testing your object out already.

  1. 到目前为止,请保存代码,然后在浏览器中加载HTML文件.
  2. 打开浏览器的JavaScript控制台,然后刷新页面,以便画布大小更改为控制台打开时保留的较小的可见视口.
  3. 输入以下内容以创建一个新的球实例:
      让testBall = new Ball(50,100,4,4,'blue',10); 
  4. 尝试致电其成员:
      testBall.x
     testBall.size
     testBall.color
     testBall.draw() 
  5. 当您输入最后一行时,您应该看到球在画布上的某处画了自己.

Updating the ball's data

我们可以将球拉到适当的位置,但是要实际移动球,我们需要某种更新功能. 在JavaScript文件的底部添加以下代码,以向Ball()prototype添加一个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(球从顶部边缘移出).

在每种情况下,我们都将球的size包括在计算中,因为x / y坐标位于球的中心,但是我们希望球的边缘从周界反弹—我们不希望球在开始反弹之前先离开屏幕一半.

最后两velX值添加到x坐标,将velY值添加到y坐标-每次调用此方法时,球实际上都会移动.

这将立即执行; 让我们继续一些动画吧!

Animating the ball

现在,让我们变得有趣. 现在,我们将开始向画布中添加球,并对它们进行动画处理.

  1. 首先,我们需要创建一个存储所有球的位置,然后填充它. 以下将完成此工作-现在将其添加到代码的底部:
      让球= [];
    
     而(balls.length <25){
       令size = random(10,20);
       让ball = new Ball(
         //始终绘制至少一个球宽度的球位置
         //远离画布的边缘,以避免绘制错误
         随机(0 +大小,宽度-大小),
         随机(0 +大小,高度-大小),
         随机(-7,7)
         随机(-7,7)
         'rgb('+ random(0,255)+','+ random(0,255)+','+ random(0,255)+')',
         尺寸
       );
    
       ball.push(球);
     }
    

    while循环使用使用random random()函数生成的随机值创建Ball()的新实例,然后push()放入balls数组的末尾,但仅当数组中的balls数量小于25.因此,当我们在屏幕上有25个球时,将不再出现球. 您可以尝试更改balls.length < 25的数字,以在屏幕上获得更多或更少的球. 根据计算机/浏览器的处理能力,指定几千个球可能会减慢动画的速度!

  2. 现在将以下内容添加到代码底部:
      函数loop(){
       ctx.fillStyle ='rgba(0,0,0,0.25)';
       ctx.fillRect(0,0,width,height);
    
       for(令i = 0; i <ball.length; i ++){
         球[i] .draw();
         球[i] .update();
       }
    
       requestAnimationFrame(loop);
     } 

    所有使事物动起来的程序通常都包含一个动画循环,该循环用于更新程序中的信息,然后在动画的每一帧上渲染结果视图. 这是大多数游戏和其他此类程序的基础. 我们的loop()函数执行以下操作:

    • 将画布填充颜色设置为半透明的黑色,然后使用fillRect()在画布的整个宽度和高度上绘制该颜色的矩形(这四个参数提供了起始坐标,以及绘制的矩形的宽度和高度). 这用于在绘制下一帧之前掩盖前一帧的图形. 如果不这样做,您只会看到长长的蛇在画布上蠕动,而不是球在移动! 填充的颜色设置为半透明, rgba(0,0,0,0.25) ,以允许前面的几帧略微发亮,从而在球移动时产生小的痕迹. 如果将0.25更改为1,您将再也看不到它们. 尝试更改此数字以查看其效果.
    • 循环遍历balls数组中的所有球,并运行每个球的draw()update()函数以在屏幕上draw()每个球,然后及时更新下一帧的位置和速度.
    • 使用requestAnimationFrame()方法再次运行该函数-重复运行此方法并传递相同的函数名称时,它将每秒运行该函数设定次数以创建平滑动画. 通常这是递归完成的-这意味着该函数每次运行时都会调用自身,因此它会一遍又一遍地运行.
  3. 最后但并非最不重要的一点是,将以下行添加到代码的底部-我们需要调用一次函数才能启动动画.
      环(); 

这就是基本知识-尝试保存并刷新以测试弹跳球!

Adding collision detection

现在,为了获得一些乐趣,让我们在程序中添加一些碰撞检测,以便我们的球知道何时击中了另一个球.

  1. 首先,在定义了update()方法的下面添加以下方法定义(即Ball.prototype.update块).
      Ball.prototype.collisionDetect = function(){
       for(让j = 0; j <ball.length; j ++){
         if(!(this === balls [j])){
           const dx = this.x-球[j] .x;
           const dy = this.y-球[j] .y;
           常量距离= Math.sqrt(dx * dx + dy * dy);
    
           if(距离<this.size +球[j] .size){
             balls [j] .color = this.color ='rgb('+ random(0,255)+','+ random(0,255)+','+ random(0,255)+')';
           }
         }
       }
     } 

    这种方法有点复杂,所以如果您现在还不清楚它的工作原理,请不要担心. 解释如下:

    • 对于每个球,我们需要检查其他每个球是否与当前球发生碰撞. 为此,我们开始另一个for循环,以循环遍历balls[]数组中的所有球.
    • 在for循环中,立即使用if语句检查当前循环通过的球是否与我们当前检查的球相同. 我们不想检查一个球是否与自身发生碰撞! 为此,我们检查当前球(即,正在调用collisionDetect方法的球)是否与循环球(即,clashDetect中for循环的当前迭代所引用的球)相同.方法). 然后我们使用! 否定的检查,从而使内部的代码if ,如果他们是一样的说法只运行.
    • 然后,我们使用一种通用算法来检查两个圆的碰撞. 我们基本上是在检查两个圆圈的区域是否重叠. 这将在2D碰撞检测中进一步说明.
    • 如果检测到冲突,则会运行内部if语句中的代码. 在这种情况下,我们仅将两个圆圈的color属性设置为新的随机颜色. 我们本来可以做的事情要复杂得多,例如使球彼此真实地弹开,但实施起来却要复杂得多. 对于这样的物理模拟,开发人员倾向于使用游戏或物理库,如PhysicsJSmatter.js移相器等.
  2. 您还需要在动画的每个帧中调用此方法. 在balls[i].update();下面添加以下内容balls[i].update(); 线:
      球[i] .collisionDetect(); 
  3. 再次保存并刷新演示,当球碰撞时,您会看到球变色!

注意 :如果您无法使该示例正常工作,请尝试将JavaScript代码与我们的最终版本进行比较(另请参见实时运行 ).

Summary

我们希望您在整个模块中使用各种对象和面向对象的技术来编写自己的真实世界的随机弹跳球示例时感到开心! 这应该为您提供了一些有用的使用对象的实践,以及良好的现实环境.

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

See also

In this module