美文网首页
(前端)200行代码实现Flappy Bird

(前端)200行代码实现Flappy Bird

作者: Lawrence_4aee | 来源:发表于2020-06-12 12:28 被阅读0次

今天在准备团队分享内容的时候写了个弹球,突然发现如果控制弹球不掉落并往前穿越障碍物,不就是Flappy bird吗,于是乎用canvas实现了这个以前大火的小游戏。
git地址:https://github.com/MrLawrence1990/canvas-flappy-bird

思路

Bird类

(实现中为简化贴图工作,使用圆形代替),在canvas中具有位置属性,具有绘制方法;实现中为简化贴图工作,使用球类Ball代替小鸟


const G = 9.8const SPEED_LEVEL = 80const TANXINGXISHU = 0.7export default classBall{

  constructor(r, color) {

    this.radius = r;

    this.color = color;

    this.vx = 0;

    this.vy = 0;

    this.x = 0;

    this.y = 0;

    this.candrod = true;

    this.canvas = null    this.grv = 1  }

  paintTo (canvas) {

    if (!this.canvas) {

      this.canvas = canvas

      this.ctx = canvas.getContext("2d")

    }

  }

  toggleGrv (up) {

    this.grv = up? -1 : 1  }

  paint () {

    if (!this.canvas) {

      return    }

    this.ctx.beginPath();

    this.ctx.arc(this.x, this.y, this.radius - 1, 0, Math.PI * 2);

    this.ctx.fillStyle = this.color;

    this.ctx.fill();

  }

  run (t) {

    this.vy = this.vy + G * t * this.grv;

    this.y += t * this.vy * SPEED_LEVEL;

    this.paint();

  }

}

障碍物

障碍物用链表的形式关联,当前一个障碍物运行到一定的随机距离后,下一个障碍物开始运行

const randomColor = function () {
  var r = Math.floor((Math.random() + Math.random()) * 128);
  var g = Math.floor((Math.random() + Math.random()) * 128);
  var b = Math.floor((Math.random() + Math.random()) * 128);
  var color = '#' + r.toString(16) + g.toString(16) + b.toString(16);
  return color;
}

const randomNum = function (minNum, maxNum) {
  switch (arguments.length) {
    case 1:
      return parseInt(Math.random() * minNum + 1, 10);
    case 2:
      return parseInt(Math.random() * (maxNum - minNum + 1) + minNum, 10);
    default:
      return 0;
  }
}

const SPEED = 200
const WAITING_SPACE = 100

export default class {
  constructor(canvas) {
    this.canvas = canvas
    this.ctx = canvas.getContext("2d")
    this.move_count = 0
  }
  paint () {
    if (!this.canvas) {
      return
    }
    this.ctx.fillStyle = this.color;
    this.ctx.fillRect(this.x, this.y, this.width, this.height);
  }
  reset (waiting) {
    // 重置到起点
    this.inited = true
    this.color = randomColor()
    this.width = randomNum(10, 60)
    this.height = randomNum(200, this.canvas.height - 100)
    this.x = this.canvas.width + WAITING_SPACE
    this.up = Math.round(Math.random())
    this.y = this.up ? 0 : this.canvas.height - this.height
    this.next_trigger = false
    this.triger_space = this.width + randomNum(160, 200)
    this.waiting = waiting
  }

  setNext (pillar) {
    this.next = pillar
  }

  move (t) {
    if (!this.inited || this.waiting) {
      return
    }
    this.x -= t * SPEED;
    if (this.x < -WAITING_SPACE) {
      this.reset(true)
    }
    if ((this.x + this.width) < ((this.canvas.width + WAITING_SPACE) - this.triger_space) && !this.next_trigger) {
      this.next && this.next.reset()
      this.next_trigger = true
    }
    this.paint();
  }
}

逻辑实现(React)

核心流程:初始化障碍物链表、初始化事件、canvas绘制

import React from 'react';
import './App.css';
import Ball from './elelemt/ball'
import Pillar from './elelemt/pillar';

export default class App extends React.Component {
  constructor() {
    super()
    this.pillars = []
    this.pause = false
    this.score = 0
    this.t = 16 / 1000;
  }
  componentDidMount () {
    this.canvas = document.getElementById('cas')
    this.ctx = this.canvas.getContext("2d")
    this.ctx.font = 'oblique small-caps normal 24px Arial';
    this.ctx.strokeStyle = '#ff7a45'
    this.ball = new Ball(20, 'red', this.canvas)
    this.ball.paintTo(this.canvas)
    this.initPillar()
    this.reset()
    this.animate()
    document.onkeydown = (e) => {
      if (e.code === 'Space') {
        if (this.pause) {
          this.pause = false
          this.pillars[0].reset()
        }
        this.toggleBirdVy(true)
      }
    }
    document.onkeyup = (e) => {
      if (e.code === 'Space') {
        this.toggleBirdVy(false)
      }
    }
    this.pause = true
  }
  initPillar () {
    let pillar
    let nextPillar
    for (let i = 0; i <= 10; i++) {
      pillar = nextPillar ? nextPillar : new Pillar(this.canvas)
      if (i === 10) {
        pillar.setNext(this.pillars[0])
      } else {
        nextPillar = new Pillar(this.canvas)
        pillar.setNext(nextPillar)
      }
      this.pillars.push(pillar)
    }
  }
  animate () {
    if (!this.pause) {
      this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height)
      this.ctx.save();
      this.ctx.fillStyle = "rgba(255,255,255,0.2)";
      this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height)
      this.ctx.restore();
      this.ball.run(this.t)
      this.pillars.forEach(pillar => {
        pillar.move(this.t)
        if (this.crash(pillar, this.ball)) {
          this.pause = true
        }
      })
      this.score = this.score + 0.01
      this.ctx.strokeText(`score: ${this.score.toFixed(2)}`, 20, 40)
    }
    window.requestAnimationFrame(this.animate.bind(this));
  }
  crash (pillar, ball) {
    if ((ball.x + ball.radius) > pillar.x && ball.x < (pillar.x + pillar.width) && ((ball.y < pillar.height && pillar.up) || ((ball.y + ball.radius) > (this.canvas.height - pillar.height) && !pillar.up))) {
      alert('crash')
      this.reset()
    }
    if (ball.y <= ball.radius || ball.y >= (this.canvas.height - ball.radius)) {
      alert('crash')
      this.reset()
    }
  }
  reset () {
    this.ball.x = 200
    this.ball.y = 200
    this.pillars.forEach(pillar => {
      pillar.reset(true)
    })
    this.score = 0
    this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height)
    this.ball.run(this.t)
    this.pause = true
  }
  toggleBirdVy (status) {
    this.ball.toggleGrv(status);
  }
  render () {
    return <canvas id="cas" width="800" height="400" style={{ 'backgroundColor': 'rgba(0,0,0,.1)' }}></canvas>
  }
}

最终效果

demo.gif

相关文章

网友评论

      本文标题:(前端)200行代码实现Flappy Bird

      本文链接:https://www.haomeiwen.com/subject/pwygtktx.html