(小游戏)详解球球保卫大作战:html+js+canvas实现原理

21 篇文章 0 订阅
13 篇文章 0 订阅

首先先附上我的游戏地址:https://yating.online/easyGame/protectBall.html

喜欢就给我点个星吧:https://github.com/Chenyating/easyGame

pc端!!!!!!移动端没做处理;

一、简单介绍:

简单的介绍一下我的小游戏:

这个是个单恋,爱而不得的悲伤爱情故事

索性最后是有情人终成眷属的美好故事

外面的小球球说:你就是那个中心的小圆,而我只会朝那冲去,纵使铜墙铁壁阻拦,粉身碎骨照样前进

操作说明:

  1. 点击游戏开始;
  2. 通过鼠标旋转外框角度,防止黄色小球进入内框;

游戏规则:

  1. 黄色小球球进入中心球,中心球半径变大;
  2. 随着分数越来越大,防御外框半径变小,防御角度变小;
  3. 蓝色小球进入中心,分数加10;外框半径增加;(蓝色小球起的是保护防御作用)

结束条件:

  1. 预防角度为0度时;
  2. 中心球的半径始终大于外框的半径;

二、技术难点

1、需要你对canvas的方法熟悉;

2、本次我使用的框架是vue.js;

3、仅对pc端适用;

通过开发的过程,该游戏最大的难点如下:

  • 外框随鼠标的移动旋转;
  • 小球随机从四周向中心移动;
  • 小球到达中心点,经历的总总,条件到达中心的条件;(爱一个人好难,要感动一个人更难;【手动滑稽】)
  • 小球碰壁瞬间的炸裂效果;(爱一个人受伤了,那是心碎的感觉【手动狗头】)
  • 外框角度的变化;(被感动慢慢敞开心扉【手动猫头】)

三、实现步骤

接下来我开始详细的说明这个游戏设计过程,实现的逻辑;

首先先了解一下我的自定义的变量:了解一下,黄小圆(bgStage)与球球(ballStage)的信息;

世界背景:高度400*800;

黄小圆心扉开口,默认值为:2Π-3/2Π——也就是90度;

data: {
    bgStage: null, //背景旋转的圈圈
    ballStage: null, //这是小球球
    // 画布长宽
    canvasWidth: 400,
    canvasHeight: 800,
    // 大圆圈
    circleDeg: 1.5 * Math.PI,
    circleX: 200,
    circleY: 400,
    circleR: 50,
    // 中心小球
    centerCircleR: 10,
    smallBallDeg: 2 * Math.PI,
    smallBallR: 5,
    // 框框旋转的角度
    oldDeg: 0,
    angle: null,
    // 炸开特性例子半径;
    dotR: 1, //像素大小

    scope: 0, //分数
    subDeg: Math.PI / 12, //每次减少的度数
    changgeNum: 20, //变化的条件,分数每增加n分就,角度减少
    gameState: 0, //游戏状态

    gameTime: 0,
    // 颜色设置:
    safeColor: "#00FFFF", //安全颜色
    dangerColor: "#DC143C", //危险颜色
    nomalColor: "yellow" //正常颜色
  },

方法不多就几个:

1、绘制:

 drawCenterCircle(x, y, r, deg)、drawCircle(x, y, r, deg)

首先我们用canvas在画布上画小圆的心;

以及她的情感系统绿色外框——我们叫心扉,算了,我叫它框框;

由于黄小圆的心会被小球球入侵而被感动,终有一天占满爱意的心(黄小圆半径),会冲破防御爆发出来;这个时候游戏就结束了;

在这个过程中,它们的坐标半径会随时变化(x,y,r,deg);

不过在这里我暂未对x,y做出其他的改变,哪有人的心会流动来流动去哇!!

以下是绘制中心球以及外框圈的方法;

 // 绘制内部小球球:没毛病;
    drawCenterCircle(x, y, r, deg) {
      this.bgStage.beginPath();
      this.bgStage.fillStyle = "#FFFF00";
      this.bgStage.arc(x, y, r, 0, deg);
      this.bgStage.fill();
    },
    // 绘制外部大框框:没毛病;
    drawCircle(x, y, r, deg) {
      this.bgStage.beginPath();
      this.bgStage.strokeStyle = "#00FF80";
      this.bgStage.arc(x, y, r, 0, deg);
      this.bgStage.stroke();
    },

2、外框旋转:

 ballRotate(newDeg)

外框的旋转需要用到canvas的rotate()、和translate()方法;

  • rotate(角度值):通过(0,0)坐标旋转;其中图形是以y轴为0度;
  • translate():可自定义旋转以何处为原点,旋转;

每次旋转以后都要把旋转点换回去,具体会发生什么?那大概会上演一场轰轰烈烈的爱情吧……【手动滑稽】

  ballRotate(newDeg) {
      this.bgStage.clearRect(this.circleX - this.circleR, this.circleY - this.circleR, 2 * this.circleR, 2 * this.circleR);
      this.drawCenterCircle(
        this.circleX,
        this.circleY,
        this.centerCircleR,
        this.smallBallDeg
      );
      this.bgStage.translate(this.circleX, this.circleY);
      this.bgStage.rotate(newDeg);
      this.bgStage.beginPath();
      this.bgStage.arc(0, 0, this.circleR, 0, this.circleDeg);
      this.bgStage.stroke();
      this.bgStage.translate(-this.circleX, -this.circleY); //坐标转换必须为旋转回去之后
    },

鼠标旋转的过程决定了,框框的旋转;所以获取旋转的角度最为重要;


3、获取旋转的角度:

getMousePosition(event)

在画布中我们通过鼠标的点击,把界面分为4个象限;

1、通过计算鼠标点击象限的坐标,可以计算出鼠标相对于x轴的斜率,从而计算出与x方向的夹角;

这里需要我们用三角函数acrtan得出鼠标的角度;所以,最后我们可以得到蓝色区域为鼠标相对于x轴的角度;

2、得出鼠标的夹角还不能结束,因为这不是框框该旋转的角度;

框框该旋转的角度 = 新鼠标点与旧鼠标点的夹角;

// 获取鼠标在界面上的位置:旋转:没毛病;
    getMousePosition(event) {
      // 全部都化成绝对值;
      if (event.layerX > this.circleX) {
        var x = event.layerX - this.circleX;
      } else {
        var x = this.circleX - event.layerX;
      }
      if (event.layerY > this.circleY) {
        var y = event.layerY - this.circleY;
      } else {
        var y = this.circleY - event.layerY;
      }
      // 现在鼠标的角度;
      this.angle = Math.atan(y / x);

      // 第一象限
      if (event.layerX > this.circleX && event.layerY < this.circleY) {
        this.angle = Math.PI - this.angle;
        // 第二象限
      } else if (event.layerX < this.circleX && event.layerY < this.circleY) {
        this.angle = this.angle;
        // 第三象限
      } else if (event.layerX < this.circleX && event.layerY > this.circleY) {
        this.angle = 2 * Math.PI - this.angle;
        // 第四象限
      } else if (event.layerX > this.circleX && event.layerY > this.circleY) {
        this.angle = Math.PI + this.angle;
      }

      if (this.oldDeg == 0) {
        this.ballRotate(this.angle);
        this.oldDeg = this.angle;
      } else {
        //   需要移动的角度
        var newDeg = this.angle - this.oldDeg;
        //   于是新的鼠标角度将成为旧的鼠标角度;
        this.oldDeg = this.angle;
        this.ballRotate(newDeg);
      }
    },

4、小球球向中心移动的过程:

getLineFunction(x1, y1, color)、moveSmallBall(k, b, x1, y1, color)

作为一个小球球,它的坐标(x,y)一直都在变动,我们要知道它的运动轨迹、和自身颜色;

1、小球球向中心移动的过程是一条直线距离,只要我们已知小球球坐标(x1,y1),那么我们就可以计算出它的运动轨迹:y=kx+b;

所以我们需要写一个方法获得小球球的k、b值

 // 小球直线路径公式:得到,K,B,X1,Y1:没毛病;
    getLineFunction(x1, y1, color) {
      // 求随机小球到中心点的直线公式;y=kx+b;
      var k = (y1 - this.circleY) / (x1 - this.circleX);
      var b = y1 - k * x1;
      // 求完以后开始绘画移动的小球球
      this.moveSmallBall(k, b, x1, y1, color);
    },

2、求出小球球的运动轨迹函数,那么我们就可以让小球球移动了。

小球球到达中心需要历经各种困难,才能成功到达;(追妹子哪能不碰壁呢?【手动滑稽】)

用流程图简单解释一下:小球球每移动一次都要进行以下校验流程;

 // 小球向下一格的重绘:重点部分:没毛病
    moveSmallBall(k, b, x1, y1, color) {
      var myVar = null;
      clearTimeout(myVar);
      var r1 =
        (x1 - this.circleX) * (x1 - this.circleX) +
        (y1 - this.circleY) * (y1 - this.circleY);
      var r2 = this.circleR * this.circleR;
      // 没有到达边界,则继续前进;
      if (r1 > r2) {
        // 那么(x,y),x+1判断;
        this.getNextXY(k, b, x1, y1, 1, color);
        return;
      }
      // 到达边界,判断一下,此时K值是否在缺口的范围之内;
      if (r1 <= r2) {
        // 判断一下小球所在的象限
        var angleK = this.whichQuadrant(k, x1, y1);
        // 判断一下小球所在的象限
        if (angleK > this.angle - parseInt(this.scope / this.changgeNum) * this.subDeg && angleK <= this.angle + (2 * Math.PI - this.circleDeg)) {
          this.getNextXY(k, b, x1, y1, 0, color);
          // 判断是否到达中心
          this.ifCenter(k, b, x1, y1, color);
          return;
        }
        // k值不在缺口范围之内,结束;
        else {
          clearTimeout(myVar);
          // 清空原来的小球球;
          this.ballStage.clearRect(
            x1 - this.smallBallR,
            y1 - this.smallBallR,
            2 * this.smallBallR,
            2 * this.smallBallR
          );
          // 这里要写一个炸裂的动画效果;
          this.scope+=1;
          this.specialEffects(x1, y1, this.smallBallR);
          return;
        }
      }
    },

3、小球球移动都需要对它的y进行改变;至于y是要加减1,要看小球球位于第几个象限,然后再对它进行移动;

根据流程图,小球球会有3次对y改变;加上对条件的筛选,我将他们合并成在一个方法里:

// 求下一步的值,重绘,然后再判断是否到达边界:没毛病
    getNextXY(k, b, x1, y1, judge, color) {
      // 求下一步的值
      // 先判断一下X,Y的值,要朝哪个方向向中心去;
      if (y1 < this.circleY) {
        var x2 = (y1 - b) / k;
        var y2 = y1 + 1;
      }
      if (y1 > this.circleY) {
        var x2 = (y1 - b) / k;
        var y2 = y1 - 1;
      }
      // 求完下一步的值后,还是重新绘制小球;

      // 继续前进
      if (judge == 0) {
        this.drawSmallBall(x1, y1, x2, y2, color);
        return;
      }
      // 判断是否到达边界
      if (judge == 1) {
        this.drawSmallBall(x1, y1, x2, y2, color);
        myVar = setTimeout(() => {
          this.moveSmallBall(k, b, x2, y2, color);
        }, 10);
        return;
      }
      // 判断是否到中心点
      if (judge == 2) {
        // 判断一下,进入圈内的是否是安全小球
        if (color == this.safeColor) {
          this.drawSmallBall(x1, y1, x2, y2, this.safeColor);
        } else {
          this.drawSmallBall(x1, y1, x2, y2, this.dangerColor);
        }
        myVar = setTimeout(() => {
          this.ifCenter(k, b, x2, y2, color);
        }, 10);
        return;
      }
    },

5、最后我们在随机绘制从边框出现的小球球们!

 randomSmallBall() {
      var randomBall = null;
      clearTimeout(randomBall);
      this.getLineFunction(0, this.randomY(), this.nomalColor);
      this.getLineFunction(this.canvasWidth, this.randomY(), this.nomalColor);
      this.getLineFunction(this.randomX(), 0, this.nomalColor);
      this.getLineFunction(this.randomX(), this.canvasHeight, this.nomalColor);
      // 随机出现一个安全的小球球
      if(this.randomN(2)==1){
        this.safeBall();
      }
      // 当框半径小于等于中心球半径,或者角度为0;游戏结束;
      if (this.circleR <= this.smallBallR || this.circleDeg == 0) {
        this.gameOver();
        clearTimeout(randomBall);
        return;
      } else {
        randomBall = setTimeout(() => {
          this.randomSmallBall();
        }, 4000);
      }
    },

游戏的特别设计之处:

每个游戏都有急救包,刺激战场都有;

所以我就添加了一个蓝色安全小球球;他的作用可以使得中心小球的半径减少;使框框半径增大

缓解了黄小球很快被追走的情况;

这就是我为啥,在前面的方法里都要参数都要加上color;

这是为了方便判断是否是蓝色安全小球,好缓解黄小球太快爱上小球球的速度过快;

 safeBall() {
      var randomColor = this.randomN(5);
      if (randomColor == 1) {
        this.getLineFunction(0, this.randomY(), this.safeColor);
      }
      if (randomColor == 2) {
        this.getLineFunction(this.canvasWidth, this.randomY(), this.safeColor);
      }
      if (randomColor == 3) {
        this.getLineFunction(this.randomX(), 0, this.safeColor);
      }
      if (randomColor == 4) {
        this.getLineFunction(this.randomX(), this.canvasHeight, this.safeColor);
      }
    },

游戏结束的判断:

  1. 预防角度为0度时;
  2. 中心球的半径始终大于外框的半径;

最后有情人终成眷属!结束!恭喜恭喜,喜提一枚女朋友;

需要以下3个监听方法:

1、每次中心小球的半径增大,都要重绘一下中心小球,并且判断一下,中心小球的半径是否超过框框的半径了;

如上图,一旦超过,那么黄小球已经成功的被小球球感动了,并且爱上他了,恭喜恭喜,喜提一枚女朋友~


    // 中心小球半径增大,重绘中心小球
    centerCircleR: function (R) {
      this.drawCenterCircle(this.circleX, this.circleY, R, this.smallBallDeg)
      if (R >= this.circleR) {
        this.bgStage.clearRect(this.circleX - this.circleR, this.circleX - this.circleR, 2 * this.circleR, 2 * this.circleR);
        this.gameOver();
        return alert("游戏结束,你的得分是:" + this.scope)
      }
    }

2、随着分值的增大,游戏难度也必须越来越大;

我采取的措施就是:能防御的角度变小,框的半径减少;

黄小球显然已经不能招架小球球的猛烈追求了;对小球球敞开的心扉也越来越多了恭喜恭喜,喜提一枚女朋友

 // 分值增大,角度减少,框的半径减少;
    scope: function (val) {
      if (val % this.changgeNum == 0) {
        this.circleDeg -= this.subDeg;
        this.bgStage.clearRect(0, 0, this.canvasWidth, this.canvasHeight);
        this.circleR -= 5;
        this.drawCenterCircle(this.circleX, this.circleY, this.centerCircleR, this.smallBallDeg)
        if (this.circleR <= this.centerCircleR) {
          this.bgStage.clearRect(this.circleX - this.circleR, this.circleX - this.circleR, 2 * this.circleR, 2 * this.circleR);
          this.gameOver();
          return alert("游戏结束,你的得分是:" + this.scope)
        }

        this.drawCircle(this.circleX, this.circleY, this.circleR, this.circleDeg);
      }
    },

3、当防御角度小于等于0时,游戏也结束了。

妹子对你敞开心扉,说明你俩的缘分来了【手动滑稽】恭喜恭喜,喜提一枚女朋友

 circleDeg: function (val) {
      if (val <= 0) {
        this.gameOver();
        return alert("游戏结束,你的得分是:" + this.scope)
      }
    }

基本上游戏的大概步骤我们就已经讲完;

其他:

本次开发需要你掌握:canvas画布的方法,以及数学三角函数、直线函数、坐标系等基础知识;

1、小球球能否进入到框框的判断

从图中我们可知,小球球要进入到缺口的条件就是,小球球的与-y的夹角在蓝色缺口的范围之内;

追妹子肯定要往正确的方向走到妹子心里去嘛。

否则就无法进入中心;该方法和判断鼠标角度方法类似;

    // 判断小球的角度
    whichQuadrant(k, x1, y1) {
      if (k <= 0) {
        k = -k;
      }
      // 现在鼠标的角度;
      var angleK = Math.atan(k);
      // 第一象限
      if (x1 > this.circleX && y1 < this.circleY) {
        return (angleK = Math.PI - angleK);
        // 第二象限
      } else if (x1 < this.circleX && y1 < this.circleY) {
        return (angleK = angleK);
        // 第三象限
      } else if (x1 < this.circleX && y1 > this.circleY) {
        return (angleK = 2 * Math.PI - angleK);
        // 第四象限
      } else if (x1 > this.circleX && y1 > this.circleY) {
        return (angleK = Math.PI + angleK);
      }
    },

2、小球球心碎碰壁特效:

老实说,是不是有点像炸屎的感觉||||||||||~~,哈哈哈哈;

原来我使用的是红色,但是看起来有点血腥。看起来有一种血腥味很重的感觉;

在追求妹子的道路上,哪能没有阻碍呢?所以,当小球球碰壁的时候,它必须心碎!

所以小球球心碎的过程就是:一颗大球变成由n多个小小球组成,小小球向四周散开,透明度变小;

  • 这就涉及到了canvas的像素点;getImageData() 复制画布上指定矩形的像素数据:

对于 ImageData 对象中的每个像素,都存在着四方面的信息,即 RGBA 值:

  • R - 红色 (0-255)
  • G - 绿色 (0-255)
  • B - 蓝色 (0-255)
  • A - alpha 通道 (0-255; 0 是透明的,255 是完全可见的)

获取到的imgData就是一个存放着制定矩形中所有像素数组的数组,第一个像素的R是imgData[0],G是imgData[1],B是imgData[2],A则是imgData[3],第二个像素的R是imgData[4],G是imgData[5],B是imgData[6],A则是imgData[7]。。。以此类推。

所以我在距离dotR的距离就画一颗小小球;

 specialEffects(x, y, r) {
      // 绘制新的;
      this.ballStage.beginPath();
      this.ballStage.fillStyle = "yellow";
      this.ballStage.arc(x, y, r, 0, Math.PI * 2);
      this.ballStage.fill();
      var imgData = this.ballStage.getImageData(x, y, 2 * this.smallBallR, 2 * this.smallBallR);
      // 把原来的图像去掉;
      this.ballStage.clearRect(x - this.smallBallR, y - this.smallBallR, 2 * this.smallBallR, 2 * this.smallBallR);

      for (var i = 0; i < imgData.width; i += this.dotR) {
        for (var j = 0; j < imgData.height; j += this.dotR) {
          if (imgData.data[i + 3] >= 128) {
            this.painDot(i + x, y + j, this.dotR)
          }
        }
      }
    },

而小小球的运动轨迹与小球球类似,我们以此类推就可以了。就是在小小球每次移动的时候,把透明度减少就可以;

// 绘制:像素点:没毛病
    painDot(x, y, r) {
      this.ballStage.beginPath();
      this.ballStage.fillStyle = "yellow";
      this.ballStage.arc(x, y, r, 0, Math.PI * 2);
      this.ballStage.fill();
      this.readyMove(x, y)
    },
    // 像素点各自开始移动:没毛病
    readyMove(x, y) {
      var k = this.randomN(4);
      if (this.randomN(2) == 1) {
        k = -k;
      } else {
        k = k;
      }
      var b = y - k * x;
      this.moveDot(k, b, x, y, 100)
    },
    // 像素点移动
    moveDot(k, b, x, y, t) {
      var dotVar = null;
      clearTimeout(dotVar);
      //   // 判断以下,一会像素点要朝哪个方向移动
      if (this.randomN(2) == 1) {
        var x2 = x + 10;
      }
      if (this.randomN(2) == 0) {
        var x2 = x - 10
      }
      var y2 = k * x2 + b;
      //   // 求完下一步的值后,还是重新绘制小球;
      this.ballStage.clearRect(x - this.dotR, y - this.dotR, 2 * this.dotR, 2 * this.dotR);
      this.ballStage.beginPath();
      this.ballStage.fillStyle = "yellow";
      this.ballStage.arc(x2, y2, this.dotR, 0, Math.PI * 2);
      this.ballStage.fill();
      //   // 让像素点透明
      t -= 1;
      if (t >= 0) {
        this.ballStage.globalAlpha -= 0.3;
        dotVar = setTimeout(() => {
          this.moveDot(k, b, x2, y2, t);
        }, 100);
      } else {
        clearTimeout(dotVar);
        this.ballStage.globalAlpha = 0;
        return;
      }

    },

最后,感谢观看~有什么问题请留言~看到马上回复~

最后,感谢观看~有什么问题请留言~看到马上回复~

最后,感谢观看~有什么问题请留言~看到马上回复~

最后,感谢观看~有什么问题请留言~看到马上回复~

最后,感谢观看~有什么问题请留言~看到马上回复~

html代码:

<!DOCTYPE html>
<html>

<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=yes" />
  <meta http-equiv="X-UA-Compatible" content="IE=edge" />
  <title>逼死强迫症小游戏-YATING</title>
  <!-- <meta name="viewport" content="width=device-width, initial-scale=1"> -->
  <link rel="stylesheet" href="css/protectBall.css" />
  <script src="js/vue.js"></script>
  <script src="js/jquery.js"></script>
</head>

<body>
  <div id="protectBall">
    <div class="interface-bg">
      <div class="info">
        <div id="title" class="text" @click=" begin()">游戏开始</div>
        <div>分数:{{scope}}</div>
        <div>时间:{{gameTime}}秒</div>
      </div>
      <canvas id="bgStage" width="400" height="800"> </canvas>
      <canvas id="ballStage" width="400" height="800"> </canvas>
    </div>
  </div>
  <script src="js/protectBall.js"></script>
  <script>
    // document.body.addEventListener('touchmove', function (e) {
    //   e.preventDefault(); //阻止默认的处理方式(阻止下拉滑动的效果)
    // }, {
    //   passive: false
    // }); //passive 参数不能省略,用来兼容ios和android
    document
      .getElementById("ballStage")
      .addEventListener("mousemove", function (e) {
        protectBall.getMousePosition(e);
      });
  </script>
</body>

</html>

css代码:

body,
html {
  width: 100%;
  height: 100%;
  overflow-y: hidden;
  background: #000000;
}

* {
  padding: 0;
  margin: 0;
}

#protectBall {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: center;
}

.text {
  color: aliceblue;
}

.interface-bg {
  margin: 0 auto;
}

.info{
  padding: 1rem;
  display: flex;
  flex-wrap: wrap;
  color: #ffffff;
  justify-content: space-around;
}
.explain{
  text-align: center;
  padding: 1rem;
  color: #ffffff;
}

#bgStage {
  position: absolute;
  margin: 0 auto;
  background: radial-gradient(#004141, #000000);
  border: solid 0.3rem #333333;
}

#ballStage {
  position: relative;
  border: solid 0.3rem #333333;
}

js代码:

var protectBall = new Vue({
  el: "#protectBall",
  data: {
    bgStage: null, //背景旋转的圈圈
    ballStage: null, //这是小球球
    // 画布长宽
    canvasWidth: 400,
    canvasHeight: 800,
    // 大圆圈
    circleDeg: 0.5 * Math.PI,
    circleX: 200,
    circleY: 400,
    circleR: 50,
    // 中心小球
    centerCircleR: 10,
    smallBallDeg: 2 * Math.PI,
    smallBallR: 5,
    // 框框旋转的角度
    oldDeg: 0,
    angle: null,
    // 炸开特性例子半径;
    dotR: 1, //像素大小

    scope: 0, //分数
    subDeg: Math.PI / 12, //每次减少的度数
    changgeNum: 5, //变化的条件,分数每增加n分就,角度减少
    gameState: 0, //游戏状态

    gameTime: 0,
    // 颜色设置:
    safeColor: "#00FFFF", //安全颜色
    dangerColor: "#DC143C", //危险颜色
    nomalColor: "red" //正常颜色
  },
  methods: {
    begin() {
      if (this.gameState != 0) {
        //游戏未开始时候
        $("#title").html("");
        $("#title").html("游戏开始");
        this.gameOver();
      } else {

        this.gameState = 1;
        $("#title").html("");
        $("#title").html("游戏结束");
        this.randomSmallBall();
        this.getTime();
      }
    },
    // 计算时间
    getTime(){
      var timeVar=null;
      clearTimeout(timeVar);
      this.gameTime+=1;
      if(this.gameState==0){
        clearTimeout(timeVar);
      }
      else{
         timeVar = setTimeout(() => {
           this.getTime();
        }, 1000);
      }
    },
    // 随机取任意值:没毛病;
    randomN(num) {
      var x = parseInt(Math.random() * num);
      return x;
    },
    // 随机取x值,x不能在x轴上:没毛病;
    randomX() {
      var x = parseInt(Math.random() * this.canvasWidth);
      if (x == this.circleX) {
        this.randomX();
      }
      return x;
    },
    // 随机取y值,y不能再y轴上:没毛病;
    randomY() {
      var y = parseInt(Math.random() * this.canvasHeight);
      if (y == this.circleY) {
        this.randomY();
      }
      return y;
    },
    // 绘制内部小球球:没毛病;
    drawCenterCircle(x, y, r, deg) {
      this.bgStage.beginPath();
      this.bgStage.fillStyle = "#FFFF00";
      this.bgStage.arc(x, y, r, 0, deg);
      this.bgStage.fill();
    },
    // 绘制外部大框框:没毛病;
    drawCircle(x, y, r, deg) {
      this.bgStage.beginPath();
      this.bgStage.strokeStyle = "#00FF80";
      this.bgStage.arc(x, y, r, 0, deg);
      this.bgStage.stroke();
    },
    // 大框框旋转,参数为旋转的角度(newDeg):没毛病;
    ballRotate(newDeg) {
      this.bgStage.clearRect(this.circleX - this.circleR, this.circleY - this.circleR, 2 * this.circleR, 2 * this.circleR);
      this.drawCenterCircle(
        this.circleX,
        this.circleY,
        this.centerCircleR,
        this.smallBallDeg
      );
      this.bgStage.translate(this.circleX, this.circleY);
      this.bgStage.rotate(newDeg);
      this.bgStage.beginPath();
      this.bgStage.arc(0, 0, this.circleR, 0, this.circleDeg);
      this.bgStage.stroke();
      this.bgStage.translate(-this.circleX, -this.circleY); //坐标转换必须为旋转回去之后
    },
    // 判断小球的角度
    whichQuadrant(k, x1, y1) {
      if (k <= 0) {
        k = -k;
      }
      // 现在鼠标的角度;
      var angleK = Math.atan(k);
      // 第一象限
      if (x1 > this.circleX && y1 < this.circleY) {
        return (angleK = Math.PI - angleK);
        // 第二象限
      } else if (x1 < this.circleX && y1 < this.circleY) {
        return (angleK = angleK);
        // 第三象限
      } else if (x1 < this.circleX && y1 > this.circleY) {
        return (angleK = 2 * Math.PI - angleK);
        // 第四象限
      } else if (x1 > this.circleX && y1 > this.circleY) {
        return (angleK = Math.PI + angleK);
      }
    },
    // 获取鼠标在界面上的位置:旋转:没毛病;
    getMousePosition(event) {
      // 全部都化成绝对值;
      if (event.layerX > this.circleX) {
        var x = event.layerX - this.circleX;
      } else {
        var x = this.circleX - event.layerX;
      }
      if (event.layerY > this.circleY) {
        var y = event.layerY - this.circleY;
      } else {
        var y = this.circleY - event.layerY;
      }
      // 现在鼠标的角度;
      this.angle = Math.atan(y / x);

      // 第一象限
      if (event.layerX > this.circleX && event.layerY < this.circleY) {
        this.angle = Math.PI - this.angle;
        // 第二象限
      } else if (event.layerX < this.circleX && event.layerY < this.circleY) {
        this.angle = this.angle;
        // 第三象限
      } else if (event.layerX < this.circleX && event.layerY > this.circleY) {
        this.angle = 2 * Math.PI - this.angle;
        // 第四象限
      } else if (event.layerX > this.circleX && event.layerY > this.circleY) {
        this.angle = Math.PI + this.angle;
      }

      if (this.oldDeg == 0) {
        this.ballRotate(this.angle);
        this.oldDeg = this.angle;
      } else {
        //   需要移动的角度
        var newDeg = this.angle - this.oldDeg;
        //   于是新的鼠标角度将成为旧的鼠标角度;
        this.oldDeg = this.angle;
        this.ballRotate(newDeg);
      }
    },
    // 随机出现一个安全小球球
    safeBall() {
      var randomColor = this.randomN(5);
      if (randomColor == 1) {
        this.getLineFunction(0, this.randomY(), this.safeColor);
      }
      if (randomColor == 2) {
        this.getLineFunction(this.canvasWidth, this.randomY(), this.safeColor);
      }
      if (randomColor == 3) {
        this.getLineFunction(this.randomX(), 0, this.safeColor);
      }
      if (randomColor == 4) {
        this.getLineFunction(this.randomX(), this.canvasHeight, this.safeColor);
      }
    },
    // 开始出现众多小球球们:没毛病;
    randomSmallBall() {
      var randomBall = null;
      clearTimeout(randomBall);
      this.getLineFunction(0, this.randomY(), this.nomalColor);
      this.getLineFunction(this.canvasWidth, this.randomY(), this.nomalColor);
      this.getLineFunction(this.randomX(), 0, this.nomalColor);
      this.getLineFunction(this.randomX(), this.canvasHeight, this.nomalColor);
      // 随机出现一个安全的小球球
      if(this.randomN(2)==1){
        this.safeBall();
      }
      // 当框半径小于等于中心球半径,或者角度为0;游戏结束;
      if (this.circleR <= this.smallBallR || this.circleDeg == 0) {
        this.gameOver();
        clearTimeout(randomBall);
        return;
      } else {
        randomBall = setTimeout(() => {
          this.randomSmallBall();
        }, 4000);
      }
    },
    // 小球直线路径公式:得到,K,B,X1,Y1:没毛病;
    getLineFunction(x1, y1, color) {
      // 求随机小球到中心点的直线公式;y=kx+b;
      var k = (y1 - this.circleY) / (x1 - this.circleX);
      var b = y1 - k * x1;
      // 求完以后开始绘画移动的小球球
      this.moveSmallBall(k, b, x1, y1, color);
    },
    // 小球向下一格的重绘:重点部分:没毛病
    moveSmallBall(k, b, x1, y1, color) {
      var myVar = null;
      clearTimeout(myVar);
      var r1 =
        (x1 - this.circleX) * (x1 - this.circleX) +
        (y1 - this.circleY) * (y1 - this.circleY);
      var r2 = this.circleR * this.circleR;
      // 没有到达边界,则继续前进;
      if (r1 > r2) {
        // 那么(x,y),x+1判断;
        this.getNextXY(k, b, x1, y1, 1, color);
        return;
      }
      // 到达边界,判断一下,此时K值是否在缺口的范围之内;
      if (r1 <= r2) {
        // 判断一下小球所在的象限
        var angleK = this.whichQuadrant(k, x1, y1);
        // 判断一下小球所在的象限
        if (angleK > this.angle - parseInt(this.scope / this.changgeNum) * this.subDeg && angleK <= this.angle + (2 * Math.PI - this.circleDeg)) {
          this.getNextXY(k, b, x1, y1, 0, color);
          // 判断是否到达中心
          this.ifCenter(k, b, x1, y1, color);
          return;
        }
        // k值不在缺口范围之内,结束;
        else {
          clearTimeout(myVar);
          // 清空原来的小球球;
          this.ballStage.clearRect(
            x1 - this.smallBallR,
            y1 - this.smallBallR,
            2 * this.smallBallR,
            2 * this.smallBallR
          );
          // 这里要写一个炸裂的动画效果;
          this.scope+=1;
          this.specialEffects(x1, y1, this.smallBallR);
          return;
        }
      }
    },
    // 求下一步的值,重绘,然后再判断是否到达边界:没毛病
    getNextXY(k, b, x1, y1, judge, color) {
      // 求下一步的值
      // 先判断一下X,Y的值,要朝哪个方向向中心去;
      if (y1 < this.circleY) {
        var x2 = (y1 - b) / k;
        var y2 = y1 + 1;
      }
      if (y1 > this.circleY) {
        var x2 = (y1 - b) / k;
        var y2 = y1 - 1;
      }
      // 求完下一步的值后,还是重新绘制小球;

      // 继续前进
      if (judge == 0) {
        this.drawSmallBall(x1, y1, x2, y2, color);
        return;
      }
      // 判断是否到达边界
      if (judge == 1) {
        this.drawSmallBall(x1, y1, x2, y2, color);
        myVar = setTimeout(() => {
          this.moveSmallBall(k, b, x2, y2, color);
        }, 10);
        return;
      }
      // 判断是否到中心点
      if (judge == 2) {
        // 判断一下,进入圈内的是否是安全小球
        if (color == this.safeColor) {
          this.drawSmallBall(x1, y1, x2, y2, this.safeColor);
        } else {
          this.drawSmallBall(x1, y1, x2, y2, this.dangerColor);
        }
        myVar = setTimeout(() => {
          this.ifCenter(k, b, x2, y2, color);
        }, 10);
        return;
      }
    },
    // 绘制小球球:1为初始,2为现状:没毛病
    drawSmallBall(x1, y1, x2, y2, color) {
      // 清空原来的小球球;
      this.ballStage.clearRect(
        x1 - this.smallBallR,
        y1 - this.smallBallR,
        2 * this.smallBallR,
        2 * this.smallBallR
      );
      // 重新绘制移动的小球球

      this.ballStage.beginPath();
      this.ballStage.fillStyle = color;
      this.ballStage.arc(x2, y2, this.smallBallR - 1, 0, this.smallBallDeg);
      this.ballStage.globalAlpha = 1;
      this.ballStage.fill();
    },
    // 判断是否到中心了:没毛病
    ifCenter(k, b, x1, y1, color) {
      if (x1 == undefined && y1 == undefined) {
        this.bgStage.clearRect(this.circleX - this.centerCircleR, this.circleX - this.centerCircleR, 2 * this.centerCircleR, 2 * this.centerCircleR);
        if(color==this.safeColor){
          // 若为蓝色小球,小球半径减小,外框半径增大,分数加5
          this.centerCircleR -= 1;
          this.circleR+=5;
          this.scope+=5;
        }
        else{
          // 红色小球进入,小球半径增大
          this.centerCircleR+=2;
        }
        return;
      } else {
        this.getNextXY(k, b, x1, y1, 2, color);
      }
    },

    // 以下是小球到达边界时的动画效果;
    // 获取小球到达边界的像素点:没毛病
    specialEffects(x, y, r) {
      // 绘制新的;
      this.ballStage.beginPath();
      this.ballStage.fillStyle = "yellow";
      this.ballStage.arc(x, y, r, 0, Math.PI * 2);
      this.ballStage.fill();
      var imgData = this.ballStage.getImageData(x, y, 2 * this.smallBallR, 2 * this.smallBallR);
      // 把原来的图像去掉;
      this.ballStage.clearRect(x - this.smallBallR, y - this.smallBallR, 2 * this.smallBallR, 2 * this.smallBallR);

      for (var i = 0; i < imgData.width; i += this.dotR) {
        for (var j = 0; j < imgData.height; j += this.dotR) {
          if (imgData.data[i + 3] >= 128) {
            this.painDot(i + x, y + j, this.dotR)
          }
        }
      }
    },
    // 绘制:像素点:没毛病
    painDot(x, y, r) {
      this.ballStage.beginPath();
      this.ballStage.fillStyle = "yellow";
      this.ballStage.arc(x, y, r, 0, Math.PI * 2);
      this.ballStage.fill();
      this.readyMove(x, y)
    },
    // 像素点各自开始移动:没毛病
    readyMove(x, y) {
      var k = this.randomN(4);
      if (this.randomN(2) == 1) {
        k = -k;
      } else {
        k = k;
      }
      var b = y - k * x;
      this.moveDot(k, b, x, y, 100)
    },
    // 像素点移动
    moveDot(k, b, x, y, t) {
      var dotVar = null;
      clearTimeout(dotVar);
      //   // 判断以下,一会像素点要朝哪个方向移动
      if (this.randomN(2) == 1) {
        var x2 = x + 10;
      }
      if (this.randomN(2) == 0) {
        var x2 = x - 10
      }
      var y2 = k * x2 + b;
      //   // 求完下一步的值后,还是重新绘制小球;
      this.ballStage.clearRect(x - this.dotR, y - this.dotR, 2 * this.dotR, 2 * this.dotR);
      this.ballStage.beginPath();
      this.ballStage.fillStyle = "yellow";
      this.ballStage.arc(x2, y2, this.dotR, 0, Math.PI * 2);
      this.ballStage.fill();
      //   // 让像素点透明
      t -= 1;
      if (t >= 0) {
        this.ballStage.globalAlpha -= 0.3;
        dotVar = setTimeout(() => {
          this.moveDot(k, b, x2, y2, t);
        }, 100);
      } else {
        clearTimeout(dotVar);
        this.ballStage.globalAlpha = 0;
        return;
      }

    },
    // 恢复初始状态;
    gameOver() {
      // 刷新当前页面;
      window.location.reload();
    }
  },
  mounted() {
    // 选择外框
    var c1 = document.getElementById("bgStage");
    this.bgStage = c1.getContext("2d");
    // 移动小球
    var c2 = document.getElementById("ballStage");
    this.ballStage = c2.getContext("2d");
    // 画圈圈
    this.drawCenterCircle(
      this.circleX,
      this.circleY,
      this.centerCircleR,
      this.smallBallDeg
    );
    this.drawCircle(this.circleX, this.circleY, this.circleR, this.circleDeg);
    this.ballRotate(-Math.PI / 2);
  },
  watch: {
    // 中心小球半径增大,重绘中心小球
    centerCircleR: function (R) {
      this.drawCenterCircle(this.circleX, this.circleY, R, this.smallBallDeg)
      if (R >= this.circleR) {
        this.bgStage.clearRect(this.circleX - this.circleR, this.circleX - this.circleR, 2 * this.circleR, 2 * this.circleR);
        this.gameOver();
        return alert("游戏结束,你的得分是:" + this.scope)
      }
    },
    // 分值增大,角度减少,框的半径减少;
    scope: function (val) {
      if (val % this.changgeNum == 0) {
        this.circleDeg -= this.subDeg;
        this.bgStage.clearRect(0, 0, this.canvasWidth, this.canvasHeight);
        this.circleR -= 5;
        this.drawCenterCircle(this.circleX, this.circleY, this.centerCircleR, this.smallBallDeg)
        if (this.circleR <= this.centerCircleR) {
          this.bgStage.clearRect(this.circleX - this.circleR, this.circleX - this.circleR, 2 * this.circleR, 2 * this.circleR);
          this.gameOver();
          return alert("游戏结束,你的得分是:" + this.scope)
        }

        this.drawCircle(this.circleX, this.circleY, this.circleR, this.circleDeg);
      }
    },
    circleDeg: function (val) {
      if (val <= 0) {
        this.gameOver();
        return alert("游戏结束,你的得分是:" + this.scope)
      }
    }
  },
});

  • 8
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 15
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 15
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值