前面介绍了一些基础概念和示例,本篇开始进入正题,目标是实现一个电子化蝌蚪的小项目

首先再来回看一下我们想要做出的最终效果:

界面中的每一个小蝌蚪,彷佛有生命般的在”自由“运动,他们长得一样,身体的结构都是由一个头,一个颈部和一个尾巴组成,但是各自都有各自的运动方向,及运动速度,同时还添加了类似碰撞检测的机制。这模仿的效果,相当的惊艳。
以上的描述,实际上就是我们本次任务的需求,那么废话不多说,让我们钻进代码的世界中,用代码,“创造虚拟生命”吧。本项目是基于vue2框架开发。

执行逻辑:在mounted生命周期中,依次调用了三个函数

mounted() {
  this.init()
  this.drawLovePath()
  this.drawTadpole()
},
  1. 首先,我们需要利用paperjs,我们需要初始化世界。

    initWorld() {
      console.log("$-_------------初始化世界---------------$-_-");
      const canvas = this.$refs.tadpole;
      // canvas的dom节点给到paper装载
      this.paper = paper;
      this.paper.setup(canvas);
      // 加装各类事件
      this.paper.view.onResize = this.onResize;
      this.paper.view.onFrame = this.onFrame;
      this.paper.view.onKeyDown = this.onKeyDown;
      this.paper.view.onMouseDown = this.onMouseDown;
    },

    实质做的事情很简单,挂载页面canvas作为世界,然后绑定各种我们可能需要的相关方法。

  2. 有了世界,执行绘制蝌蚪主函数,实例化众多的蝌蚪

      drawLovePath() {
        this.heartPath = new paper.Path('M514.69629,624.70313c-7.10205,-27.02441 -17.2373,-52.39453 -30.40576,-76.10059c-13.17383,-23.70703 -38.65137,-60.52246 -76.44434,-110.45801c-27.71631,-36.64355 -44.78174,-59.89355 -51.19189,-69.74414c-10.5376,-16.02979 -18.15527,-30.74951 -22.84717,-44.14893c-4.69727,-13.39893 -7.04297,-26.97021 -7.04297,-40.71289c0,-25.42432 8.47119,-46.72559 25.42383,-63.90381c16.94775,-17.17871 37.90527,-25.76758 62.87354,-25.76758c25.19287,0 47.06885,8.93262 65.62158,26.79834c13.96826,13.28662 25.30615,33.10059 34.01318,59.4375c7.55859,-25.88037 18.20898,-45.57666 31.95215,-59.09424c19.00879,-18.32178 40.99707,-27.48535 65.96484,-27.48535c24.7373,0 45.69531,8.53564 62.87305,25.5957c17.17871,17.06592 25.76855,37.39551 25.76855,60.98389c0,20.61377 -5.04102,42.08691 -15.11719,64.41895c-10.08203,22.33203 -29.54687,51.59521 -58.40723,87.78271c-37.56738,47.41211 -64.93457,86.35352 -82.11328,116.8125c-13.51758,24.0498 -23.82422,49.24902 -30.9209,75.58594z')
      },
     
    // // 创建蝌蚪军团
      drawTadpole() {
        for (let i = 0; i < 50; i++) {
          const location = this.random()
          this.boids.push(new Boid(location, 10, 0.05))
        }
      },

    其中,变量heartPath是通过直接引入svg数据,画出一个心形的path路径,如下图所示。后面我们会让所有的蝌蚪沿着该图形的边缘游动。

在后面的for循环中,每次我们在画布中随机生成一个坐标点,并随同另外两个固定值,用于蝌蚪Boid这个类的输入。这是外围的代码。实质就是传入三个参数,一个确定其位置,一个确定其最大速度,还有一个,确定其最大力量,后面详解。

蝌蚪类的细节

  1. constructor
// 初始化tadpole类
// position: 随机的point类坐标
// maxSpeed: 10
// maxForce:  0.05
constructor(position, maxSpeed, maxForce) {
  this.position = position
  // 尾巴点数[10, 14]
  this.tailAmount = Math.random() * 5 + 10

  // 蝌蚪移动速度,此值极其重要,关系到蝌蚪实例的生命力
  this.acceleration = new paper.Point()

  // vector这个变量,决定了下一帧,或者说是下一时刻,小蝌蚪的运动目的地
  // eslint-disable-next-line new-cap
  this.vector = new paper.Point.random()

  // 取值范围:[10, 10.5),干嘛的未知
  this.maxSpeed = maxSpeed + strength

  // 取值范围:[0.05, 0.55),干嘛的未知
  this.maxForce = maxForce + strength
  this.count = 0
  this.createItems()
}

结合上述代码, 简要介绍一下我们是如何像当年女娲捏人一般,用代码捏出我们的蝌蚪军团的。
实例化蝌蚪的三个输入,分别是坐标、速度、强度。.constructor构造函数,初始化实例时,自动执行。该函数的作用主要是为了初始化蝌蚪示例的众多变量的数值,在最后调用createItems函数,其功能顾名思义。

  1. createItems
    // 定义蝌蚪的身体组成
    createItems() {
      // 椭圆,代表蝌蚪的头部
      this.head = new paper.Shape.Ellipse({
        //   center: [0, 0],
        center: [this.position.x, this.position.y],
        size: [15, 10],
        fillColor: 'orange'
      })
      // 尾巴
      this.path = new paper.Path({
        strokeColor: 'green',
        strokeWidth: 2,
        strokeCap: 'round'
      })
      // 目测是尾巴长度
      for (let i = 0; i < this.amount; i++) {
        this.path.add(new paper.Point())
      }
      // 颈部
      this.shortPath = new paper.Path({
        strokeColor: 'white',
        strokeWidth: 4,
        strokeCap: 'round'
      })
      for (let i = 0; i < 3; i++) {
        this.shortPath.add(new paper.Point())
      }
    }
    在createItems中,我们首先根据当前蝌蚪示例的坐标信息,绘制其头部。对了补充一下,官方示例的这个蝌蚪类,其身体由三部分组成,包括头部、颈部和尾巴,分别用不同颜色标识如下:

其中,头部head用一个椭圆标识,通过new paper.Shape.Ellipse,给他一个椭圆的center及长短半径的值(佩服自己居然还记得椭圆的长短半径),当然还有颜色。而尾巴path以及颈部shortPath则都是path,两者均由众多的点组成。注意:截止此时,除了头部已经画出,颈部及尾巴均没有实际画出,实质上是通过加point的方式,埋了几个坑位.
到此阶段,准备工作大概做了约莫一半了,剩下的就是一些细节问题了。此时跑代码,你应该能看到如下的景象:

蝌蚪们的都有了,颈部和尾巴还没有。当然了,如果觉得有必要一开始连通脖子和颈部也一块初始化,也可以。看自己的需求。

  1. 接下来我们要做的,是让他们动起来
onFrame() {
  this.boids.forEach((boid) => {
    boid.run(this.boids, false)
  })
},

代码逻辑很清晰,帧级别触发函数,遍历我们的蝌蚪军团实例,挨个调用run方法。正因为是帧级别的事件触发,因此从肉眼的角度观察,蝌蚪们就连贯的动起来了。那么,run函数里头,具体是怎么实现的呢?

咱们下期继续讲解。本文毕