lover-fish
  • Introduction
  • 搭建环境
  • 绘制背景
  • 绘制海葵
  • 绘制果实
  • 果实动画
  • 控制果实的数量
  • 增加果实的类型
  • 绘制鱼
  • 让鱼动起来
  • 让鱼跟着鼠标动起来
  • 吃果实之碰撞检测
  • 创建鱼宝宝
  • 给鱼添加动画
  • 加入计分模块
  • 大鱼身体特效
  • 游戏结束逻辑
  • 增加吃果实特效
  • 海葵动画
  • 漂浮物
Powered by GitBook
On this page

海葵动画

这主要用到贝赛尔曲线,简单的来说就是 PS 中钢笔画出来的路径,通常会有个控制杆。

二次贝塞尔需要三个点,开始点,控制点(控制杆顶部所在的点),结束点。

了解更多 点这里

我们通过改变结束点的位置来实现摇摆。

修改 heady 就可以摆动了,摆动的规律加个 sin 函数.

加入 alpha 添加透明度,根据近实远虚原理构建空间感,加入 amp,摆动振幅。

import { ctx_two, cvs_height } from "./init";
import { deltaTime } from "./game-loop";

class Anemones{
  rootx: number[] = [];  // x 轴的坐标
  headx: number[] = [];  // 海葵头部的 x
  heady: number[] = []; // 海葵头部的 y

  num = 50; // 绘制数量

  alpha = 0 // 角度
  amp: number[] = [] // 振幅
  opacity: number[] = [] // 透明度
  /**
   * 其实就跟在 PS 里面画一样,只不过都是通过代码进行操作,不能通过鼠标进行操作。
   *
   * save() - restore() 做作用就是只对他们之间的代码应用这些画笔样式
   *
   * save() 就相当于暂存一下画笔的状态。开启一个快照,可以对这个快照进行任何操作
   *
   * restore() 恢复之前画笔的状态
   */
  draw(){
    this.alpha += deltaTime * 0.001; // 角度随时间变化
    let l = Math.sin(this.alpha)

    ctx_two.save() // 暂存画笔状态
    // 设置画笔样式

    ctx_two.strokeStyle = '#3b154e' // 设置画笔颜色
    ctx_two.lineWidth = 20; // 画笔的宽度
    ctx_two.lineCap = "round" // 圆角的线


    for (let i = 0; i < this.num; ++i) {

      this.headx[i] = this.rootx[i] + l * this.amp[i]

      ctx_two.beginPath() // 开始绘画
      ctx_two.globalAlpha = this.opacity[i]
      ctx_two.moveTo(this.rootx[i], cvs_height) // 把画笔移动到 x 点,画布的最下方出,从下往上画海葵
      ctx_two.quadraticCurveTo(
        this.rootx[i], cvs_height - 100, // 控制点
        this.headx[i], this.heady[i] // 结束点
        )
      ctx_two.stroke() // 确认,开始渲染
    }
    ctx_two.restore() // 恢复之前暂存的画笔状态

  }
  /**
   * 初始化海葵的 x 坐标和高度
   */
  constructor(){
    for (let i = 0; i < this.num; ++i) {
      this.rootx[i] = i * 16 + Math.random() * 20;
      this.headx[i] = this.rootx[i];
      this.heady[i] = cvs_height - 240 + Math.random() * 80;
      this.amp[i] = 20 + Math.random() * 30 // 设置振幅
      this.opacity[i] = (Math.random() * .6) + 0.6
    }
  }
}

export default Anemones;

此时果实的代码还要修改一下,当没有生长好的时候,也有一部分逻辑要完成。果实要跟着海葵摆动的逻辑。

import { cvs_height, ctx_two, anemones } from "./init";
import { deltaTime } from "./game-loop";


enum FruitType{
  Blue = 1,
  Orange
}

class Fruits{
  num: number = 30; // 绘画果实的数量
  alive: boolean[] =[];  // 判断果实是否存活
  x : number[]= []; // 果实的 x 坐标数组
  y : number[] = []; // 果实的 y 坐标数组
  diameter : number[] = []; // 果实的直径数组
  speed: number[] = []; // 控制果实成长速度、上浮速度的数组
  fruitType: FruitType[] = []; // 控制果实类型的枚举数组
  orange = new Image(); // 黄色果实
  blue = new Image(); // 蓝色果实
  aneNum: number[] = []; // 记录出生时候所在的海葵
  constructor(){
    for (let i = 0; i < this.num; ++i) {
      this.aneNum[i] = 0; // 初始化
      this.born(i);
    }
    this.orange.src = 'assets/img/fruit.png';
    this.blue.src = 'assets/img/blue.png';
  }

  // 绘制果实
  draw(){
    for (let i = 0; i < this.num; ++i) {

      // 只有属性为存活的时候才绘制
      if(this.alive[i]){
        let img = this.fruitType[i] === FruitType.Orange ? this.orange : this.blue; // 根据类型,拿到相应的图片

        if(this.diameter[i] <= 17) {
          this.diameter[i] += this.speed[i] * deltaTime; // 随着时间半径不断变大 也就是果实长大
          this.x[i] = anemones.headx[this.aneNum[i]]
          this.y[i] = anemones.heady[this.aneNum[i]] // 得到海葵顶点的 x 和 y


        }else{
          this.y[i] -= this.speed[i] * deltaTime; // 果实成熟之后, y 坐标减小,果实开始上升
        }

        // 把果实绘制出来,为了让果实居中,所以要减去图片高度一半,宽度一半
        // 就像实现水平居中一样 left: 50px; margin-left: -(图片宽度 / 2);
        // 第一个参数 图片, 第二三个参数,坐标轴的 x 和 y,第四五个参数,图片的宽高
        ctx_two.drawImage(img, this.x[i] - this.diameter[i] / 2, this.y[i] - this.diameter[i], this.diameter[i], this.diameter[i]);


      }

      if(this.y[i] <= -10) {
        this.alive[i] = false; // 果实出去了之后 存活状态为 flase
      }

    }
  }

  // 初始化果实
  born(i){
    let aneId = Math.floor( Math.random() * anemones.num ) // 随机拿到一个果实的 ID
    this.aneNum[i] = aneId
    this.speed[i] = Math.random() * 0.04 + 0.007; // 设置速度在区间 0.003 - 0.03 里
    this.alive[i] = true; // 先设置它的存活为 true
    this.diameter[i] = 0; // 未生长出来的果实半径为0

    this.fruitType[i] = (Math.random() >= 0.7) ? FruitType.Blue : FruitType.Orange; // 设置30%的几率产生蓝色果实

  }

  // 监视果实
  monitor() : void {
    let num = 0;
    for (let i = 0; i < this.num ; ++i) {
      if(this.alive[i]) num++; // 计数存活果实的数量

      if(num < 15) {
        // 产生一个果实
        this.reset()
        return ;
      }
    }
  }

  //重置果实的状态
  reset() {
    for (let i = 0; i < this.num; ++i) {
      if(!this.alive[i]) {
        this.born(i); // 假如存活为 false , 让它重新出生。
        return ; // 每次只重置一个果实
      }
    }
  }
  // 果实死亡
  dead(i){
    this.alive[i] = false;
  }
}


export default Fruits;

export { FruitType };
Previous增加吃果实特效Next漂浮物

Last updated 7 years ago