今天在准备团队分享内容的时候写了个弹球,突然发现如果控制弹球不掉落并往前穿越障碍物,不就是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>
}
}
网友评论