《flappy bird》是一款由来自越南的独立游戏开发者Dong Nguyen所开发的作品,游戏于2013年5月24日上线,并在2014年2月突然暴红。2014年2月,《Flappy Bird》被开发者本人从苹果及谷歌应用商店(Google Play)撤下。2014年8月份正式回归App Store,正式加入Flappy迷们期待已久的多人对战模式。游戏中玩家必须控制一只小鸟,跨越由各种不同长度水管所组成的障碍。

目录

初始化数据

初始化函数init

随机管道数据

游戏的主要逻辑及绘制

所有内容的绘制

鸟的碰撞判断

成绩与键盘鼠标事件

初始化body

总结


初始化数据

建议所有的js拆开成多个文件,方便理解,不然500多行代码会疯掉的,上下拉动多次心态会变动的。

      var boxx = 0;var boxy = 0;var boxwidth = 384;var boxheight = 512;var backgroundwidth = 384;var backgroundheight = 448;var groundwidth = 18.5;var groundheight = 64;var birdwidth = 46;var birdheight = 32;var birdx = 192 - birdwidth;var birdy = 224 - birdheight;var birdvy = 0; //鸟初始的y轴速度var birdimage;var gravity = 1; //重力加速度var jumpvelocity = 11; //跳跃时获得的向上速度var birdstate;var upbackground;var bottombackground;var bottomstate;var pipeupimage;var pipedownimage;var pipewidth = 69; //管道的宽度var blankwidth = 126; //上下管道之间的间隔var pipeinterval = pipewidth + 120; //两个管道之间的间隔var pipenumber = 0; //当前已经读取管道高度的个数var fps = 30; //游戏的帧数,推荐在30~60之间var gamestate = 0; //游戏状态:0--未开始,1--已开始,2--已结束var times; //地板图片的条数  Math.ceil(boxwidth/groundwidth)+1;var highscore = 0; //得到过的最高分var score = 0; //目前得到的分数var movespeed = groundwidth / 4; //场景向左移动的速度,为底部场景的宽度的1/4var tipimage; //开始的提示图片var tipwidth = 168;var tipheight = 136;var boardimage; //分数板的图片var boardx;var boardy = 140;var boardwidth = 282;var boardheight = 245;var canvas;var ctx;var i;var pipeheight = [];//各种音效var flysound; //飞翔的声音var scoresound; //得分的声音var hitsound; //撞到管道的声音var deadsound; //死亡的声音var swooshingsound; //切换界面时的声音var pipeoncanvas = [//要显示在Canvas上的管道的location和height[0, 0],[0, 0],[0, 0],];

初始化函数init

初始化的函数,将所有数据进行初始化,主要是画布以及图片的初始化。

      function init() {ctx = document.getElementById("canvas").getContext("2d");flysound = document.getElementById("flysound");scoresound = document.getElementById("scoresound");hitsound = document.getElementById("hitsound");deadsound = document.getElementById("deadsound");swooshingsound = document.getElementById("swooshingsound");ctx.lineWidth = 2;ctx.font = "bold 40px HirakakuProN-W6"; //绘制字体还原ctx.fillStyle = "#FFFFFF";upbackground = new Image();upbackground.src = "images/background.png";bottombackground = new Image();bottombackground.src = "images/ground.png";bottomstate = 1;birdimage = new Image();birdimage.src = "images/bird.png";birdstate = 1;tipimage = new Image();tipimage.src = "images/space_tip.png";boardimage = new Image();boardimage.src = "images/scoreboard.png";boardx = (backgroundwidth - boardwidth) / 2;pipeupimage = new Image();pipeupimage.src = "images/pipeup.png";pipedownimage = new Image();pipedownimage.src = "images/pipedown.png";times = Math.ceil(boxwidth / groundwidth) + 1;initPipe();canvas = document.getElementById("canvas");canvas.addEventListener("mousedown", mouseDown, false);window.addEventListener("keydown", keyDown, false);//window.addEventListener("keydown",getkeyAndMove,false);setInterval(run, 1000 / fps);}

随机管道数据

随机范围需要控制好,出现在容器外是没有任何意义的。

      function initPipe() {for (i = 0; i < 200; i++)pipeheight[i] = Math.ceil(Math.random() * 216) + 56; //高度范围从56~272for (i = 0; i < 3; i++) {pipeoncanvas[i][0] = boxwidth + i * pipeinterval;pipeoncanvas[i][1] = pipeheight[pipenumber];pipenumber++;}}

游戏的主要逻辑及绘制

这里能看到有很多的函数,这里只是进行一定的控制。

      function run() {//游戏未开始if (gamestate == 0) {drawBeginScene(); //绘制开始场景drawBird(); //绘制鸟drawTip(); //绘制提示}//游戏进行中if (gamestate == 1) {birdvy = birdvy + gravity;drawScene(); //绘制场景drawBird(); //绘制鸟drawScore(); //绘制分数checkBird(); //检测鸟是否与物体发生碰撞}//游戏结束if (gamestate == 2) {if (birdy + birdheight < backgroundheight)//如果鸟没有落地birdvy = birdvy + gravity;else {birdvy = 0;birdy = backgroundheight - birdheight;}drawEndScene(); //绘制结束场景drawBird(); //绘制鸟drawScoreBoard(); //绘制分数板//ctx.fillRect(boardx+14,boardy+boardheight-40,75,40); // 测试重新开始按钮的位置}}

所有内容的绘制

绘制是比较麻烦的,而且需要绘制的内容比较多,很多人会喜欢直接使用图片的方式来操作。 绘制过程其实可以让你的各方面能力成长的更快一些,但是绘制过程中很多细节会出现思想卡壳的情况,别担心,用笔头画一画就很容易明白了。

 

绘制过程中可以拆开绘制,并且放置在多个js文件中,不着急整体拼接,否则几百行代码操作复杂度较高。

      //绘制提示function drawTip() {ctx.drawImage(tipimage,birdx - 57,birdy + birdheight + 10,tipwidth,tipheight);}//绘制分数板function drawScoreBoard() {//绘制分数板ctx.drawImage(boardimage, boardx, boardy, boardwidth, boardheight);//绘制当前的得分ctx.fillText(score, boardx + 140, boardheight / 2 + boardy - 8); //132//绘制最高分ctx.fillText(highscore, boardx + 140, boardheight / 2 + boardy + 44); //184}//绘制开始场景(不包括管道)function drawBeginScene() {//清理画布上上一桢的画面ctx.clearRect(boxx, boxy, boxwidth, boxheight);//绘制上方静态背景ctx.drawImage(upbackground, 0, 0, backgroundwidth, backgroundheight);//绘制下方的动态背景drawmovingscene();//绘制边框线ctx.strokeRect(boxx + 1, boxy + 1, boxwidth - 2, boxheight - 2);}//绘制场景function drawScene() {ctx.clearRect(boxx, boxy, boxwidth, boxheight); //清理画布上上一桢的画面ctx.drawImage(upbackground, 0, 0, backgroundwidth, backgroundheight); //绘制上方静态背景drawmovingscene(); //绘制下方的动态背景drawAllPipe(); //绘制管道ctx.strokeRect(boxx + 1, boxy + 1, boxwidth - 2, boxheight - 2); //绘制边框线}//绘制结束场景(不包括管道)function drawEndScene() {ctx.clearRect(boxx, boxy, boxwidth, boxheight); //清理画布上上一桢的画面ctx.drawImage(upbackground, 0, 0, backgroundwidth, backgroundheight); //绘制上方静态背景//绘制下方的静态背景,根据bottomstate来判断如何绘制静态地面switch (bottomstate) {case 1:for (i = 0; i < times; i++)ctx.drawImage(bottombackground,groundwidth * (i - 0.75),backgroundheight,groundwidth,groundheight);break;case 2:for (i = 0; i < times; i++)ctx.drawImage(bottombackground,groundwidth * i,backgroundheight,groundwidth,groundheight);break;case 3:for (i = 0; i < times; i++)ctx.drawImage(bottombackground,groundwidth * (i - 0.25),backgroundheight,groundwidth,groundheight);break;case 4:for (i = 0; i < times; i++)ctx.drawImage(bottombackground,groundwidth * (i - 0.5),backgroundheight,groundwidth,groundheight);}//绘制当前的柱子for (i = 0; i < 3; i++) {drawPipe(pipeoncanvas[i][0], pipeoncanvas[i][1]);}ctx.strokeRect(boxx + 1, boxy + 1, boxwidth - 2, boxheight - 2); //绘制边框线}//绘制下方的动态背景function drawmovingscene() {if (bottomstate == 1) {for (i = 0; i < times; i++)ctx.drawImage(bottombackground,groundwidth * i,backgroundheight,groundwidth,groundheight);bottomstate = 2;} else if (bottomstate == 2) {for (i = 0; i < times; i++)ctx.drawImage(bottombackground,groundwidth * (i - 0.25),backgroundheight,groundwidth,groundheight);bottomstate = 3;} else if (bottomstate == 3) {for (i = 0; i < times; i++)ctx.drawImage(bottombackground,groundwidth * (i - 0.5),backgroundheight,groundwidth,groundheight);bottomstate = 4;} else if (bottomstate == 4) {for (i = 0; i < times; i++)ctx.drawImage(bottombackground,groundwidth * (i - 0.75),backgroundheight,groundwidth,groundheight);bottomstate = 1;}}//使用给定的高度和位置绘制上下两根管道function drawPipe(location, height) {//绘制下方的管道ctx.drawImage(pipeupimage,0,0,pipewidth * 2,height * 2,location,boxheight - (height + groundheight),pipewidth,height);//绘制上方的管道ctx.drawImage(pipedownimage,0,793 - (backgroundheight - height - blankwidth) * 2,pipewidth * 2,(backgroundheight - height - blankwidth) * 2,location,0,pipewidth,backgroundheight - height - blankwidth);}//绘制需要显示的管道function drawAllPipe() {for (i = 0; i < 3; i++) {pipeoncanvas[i][0] = pipeoncanvas[i][0] - movespeed;}if (pipeoncanvas[0][0] <= -pipewidth) {pipeoncanvas[0][0] = pipeoncanvas[1][0];pipeoncanvas[0][1] = pipeoncanvas[1][1];pipeoncanvas[1][0] = pipeoncanvas[2][0];pipeoncanvas[1][1] = pipeoncanvas[2][1];pipeoncanvas[2][0] = pipeoncanvas[2][0] + pipeinterval;pipeoncanvas[2][1] = pipeheight[pipenumber];pipenumber++;}for (i = 0; i < 3; i++) {drawPipe(pipeoncanvas[i][0], pipeoncanvas[i][1]);}}function drawBird() {birdy = birdy + birdvy;if (gamestate == 0) {drawMovingBird();}//根据鸟的y轴速度来判断鸟的朝向,只在游戏进行阶段生效else if (gamestate == 1) {ctx.save();if (birdvy <= 8) {ctx.translate(birdx + birdwidth / 2, birdy + birdheight / 2);ctx.rotate(-Math.PI / 6);ctx.translate(-birdx - birdwidth / 2, -birdy - birdheight / 2);}if (birdvy > 8 && birdvy <= 12) {ctx.translate(birdx + birdwidth / 2, birdy + birdheight / 2);ctx.rotate(Math.PI / 6);ctx.translate(-birdx - birdwidth / 2, -birdy - birdheight / 2);}if (birdvy > 12 && birdvy <= 16) {ctx.translate(birdx + birdwidth / 2, birdy + birdheight / 2);ctx.rotate(Math.PI / 3);ctx.translate(-birdx - birdwidth / 2, -birdy - birdheight / 2);}if (birdvy > 16) {ctx.translate(birdx + birdwidth / 2, birdy + birdheight / 2);ctx.rotate(Math.PI / 2);ctx.translate(-birdx - birdwidth / 2, -birdy - birdheight / 2);}drawMovingBird();ctx.restore();}//游戏结束后鸟头向下并停止活动else if (gamestate == 2) {ctx.save();ctx.translate(birdx + birdwidth / 2, birdy + birdheight / 2);ctx.rotate(Math.PI / 2);ctx.translate(-birdx - birdwidth / 2, -birdy - birdheight / 2);ctx.drawImage(birdimage,0,0,92,64,birdx,birdy,birdwidth,birdheight);ctx.restore();}}//绘制扇动翅膀的鸟function drawMovingBird() {if (birdstate == 1 || birdstate == 2 || birdstate == 3) {ctx.drawImage(birdimage,0,0,92,64,birdx,birdy,birdwidth,birdheight);birdstate++;} else if (birdstate == 4 || birdstate == 5 || birdstate == 6) {ctx.drawImage(birdimage,92,0,92,64,birdx,birdy,birdwidth,birdheight);birdstate++;} else if (birdstate == 7 || birdstate == 8 || birdstate == 9) {ctx.drawImage(birdimage,184,0,92,64,birdx,birdy,birdwidth,birdheight);birdstate++;if (birdstate == 9) birdstate = 1;}}function drawScore() {ctx.fillText(score, boxwidth / 2 - 2, 120);}

鸟的碰撞判断

判断游戏是否还能进行,无论是管道产生碰撞,或者接触到了容器底部,也就是碰撞地面都属于挂掉了。加上声音控制就有一定的效果了。

      //检查鸟是否与管道产生碰撞(不可能与第三组管道重合),以及鸟是否碰撞地面function checkBird() {//通过了一根管道加一分if ((birdx > pipeoncanvas[0][0] &&birdx < pipeoncanvas[0][0] + movespeed) ||(birdx > pipeoncanvas[1][0] && birdx < pipeoncanvas[1][0] + movespeed)) {playSound(scoresound, "sounds/point.mp3");score++;}//先判断第一组管道//如果鸟在x轴上与第一组管道重合if (birdx + birdwidth > pipeoncanvas[0][0] &&birdx + birdwidth < pipeoncanvas[0][0] + pipewidth + birdwidth) {//如果鸟在y轴上与第一组管道上部或下部重合if (birdy < backgroundheight - pipeoncanvas[0][1] - blankwidth ||birdy + birdheight > backgroundheight - pipeoncanvas[0][1]) {hitPipe();}}//判断第二组管道//如果鸟在x轴上与第二组管道重合//这里我原本使用else if出现了问题,但第一版中却没有问题,对比代码后发现原因是上方第一个if后没有加大括号,//这里的else无法区分对应哪一个if,加上大括号后问题解决,建议将if后的内容都加上大括号,养成良好的变成习惯else if (birdx + birdwidth > pipeoncanvas[1][0] &&birdx + birdwidth < pipeoncanvas[1][0] + pipewidth + birdwidth) {//如果鸟在y轴上与第二组管道上部或下部重合if (birdy < backgroundheight - pipeoncanvas[1][1] - blankwidth ||birdy + birdheight > backgroundheight - pipeoncanvas[1][1]) {hitPipe();}}//判断是否碰撞地面else if (birdy + birdheight > backgroundheight) {hitPipe();}}//撞击到管道或地面后的一些操作function hitPipe() {ctx.font = "bold 40px HirakakuProN-W6";//ctx.font="bold 35px HarlemNights";ctx.fillStyle = "#000000";playSound(hitsound, "sounds/hit.mp3");playSound(deadsound, "sounds/die.mp3");updateScore();gamestate = 2; //游戏结束}

成绩与键盘鼠标事件

成绩这里很好理解,就是1分1分的增加,键盘鼠标事件比较麻烦,需要计算x与y轴,音乐播放情况等等,代码中注释给的比较全面,不太容易理解的地方可以通过debug来挨个看看执行过程。

     //刷新最好成绩function updateScore() {if (score > highscore) highscore = score;}//处理键盘事件function keyDown() {if (gamestate == 0) {playSound(swooshingsound, "sounds/swooshing.mp3");birdvy = -jumpvelocity;gamestate = 1;} else if (gamestate == 1) {playSound(flysound, "sounds/wing.mp3");birdvy = -jumpvelocity;}}//处理鼠标点击事件,相比键盘多了位置判断function mouseDown(ev) {var mx; //存储鼠标横坐标var my; //存储鼠标纵坐标if (ev.layerX || ev.layerX == 0) {// Firefoxmx = ev.layerX;my = ev.layerY;} else if (ev.offsetX || ev.offsetX == 0) {// Operamx = ev.offsetX;my = ev.offsetY;}if (gamestate == 0) {playSound(swooshingsound, "sounds/swooshing.mp3");birdvy = -jumpvelocity;gamestate = 1;} else if (gamestate == 1) {playSound(flysound, "sounds/wing.mp3");birdvy = -jumpvelocity;}//游戏结束后判断是否点击了重新开始else if (gamestate == 2) {//ctx.fillRect(boardx+14,boardy+boardheight-40,75,40);//鼠标是否在重新开始按钮上if (mx > boardx + 14 &&mx < boardx + 89 &&my > boardy + boardheight - 40 &&my < boardy + boardheight) {playSound(swooshingsound, "sounds/swooshing.mp3");restart();}}}function restart() {gamestate = 0; //回到未开始状态//ctx.font="bold 40px HarlemNights"; //绘制字体还原ctx.font = "bold 40px HirakakuProN-W6"; //绘制字体还原ctx.fillStyle = "#FFFFFF";score = 0; //当前分数清零pipenumber = 0; //读取的管道数清零initPipe(); //重新初始化水管高度birdx = 192 - birdwidth; //鸟的位置和速度回到初始值birdy = 224 - birdheight;birdvy = 0;}function playSound(sound, src) {if (src != "" && typeof src != undefined) {sound.src = src;}}

初始化body

初始化的过程中我们主要针对各种音频进行初始化,路径在js中设置即可,留下一个canvas做所有内容的呈现容器即可。

<body onLoad="init();"><audio id="flysound" playcount="1" autoplay="true" src=""></audio><audio id="scoresound" playcount="1" autoplay="true" src=""></audio><audio id="hitsound" playcount="1" autoplay="true" src=""></audio><audio id="deadsound" playcount="1" autoplay="true" src=""></audio><audio id="swooshingsound" playcount="1" autoplay="true" src=""></audio><canvas id="canvas" width="384" height="512" style="margin-top: 8px"></canvas>
</body>

总结

这个代码是来自于GitHub的,搞下来后跟着编写了两遍也就会的差不多了,其实创新的难度还是很大的,开源的东西多学习学习才会有更多的思路来创造自己的开源内容,而且这个案例的内容还是非常不错的,建议喜欢前端的技术人们初学的时候可以捉摸捉摸。

图片资源绘制效果,背景可以自己更换,鸟有3种飞行状态,地面就一种,水管主要是上下两个部分。

像素鸟html与js源码(4节课勉强做完)相关推荐

  1. three.js 源码注释(一)./Three.js

    商域无疆 (http://blog.csdn.net/omni360/) 本文遵循"署名-非商业用途-保持一致"创作公用协议 转载请保留此句:商域无疆 -  本博客专注于 敏捷开发 ...

  2. 【笔记-vue】《imooc-vue.js高仿饿了么》、《imooc-vue 音乐app》、《imooc-vue.js源码全方位解析》

    20170709 - 20171128:<imooc-vue.js高仿饿了么> 一.第一章 课程简介 1-1课程简介 1.需求分析-脚手架工具-数据mock-架构设计-代码编写-自测-编译 ...

  3. MVVM架构~knockoutjs系列之从Knockout.Validation.js源码中学习它的用法

    说在前 有时,我们在使用一个插件时,在网上即找不到它的相关API,这时,我们会很抓狂的,与其抓狂,还不如踏下心来,分析一下它的源码,事实上,对于JS这种开发语言来说,它开发的插件的使用方法都在它的源码 ...

  4. vue.js源码学习分享(一)

    今天看了vue.js源码  发现非常不错,想一边看一遍写博客和大家分享 /*** Convert a value to a string that is actually rendered. *转换一 ...

  5. js怎么调用wasm_Long.js源码解析

    基于现在市面上到处都是 Vue/React 之类的源码分析文章实在是太多了.(虽然我也写过 Vite的源码解析 所以这次来写点不一样的.由于微信这边用的是 protobuf 来进行 rpc 调用.所以 ...

  6. underscore.js源码研究(5)

    概述 很早就想研究underscore源码了,虽然underscore.js这个库有些过时了,但是我还是想学习一下库的架构,函数式编程以及常用方法的编写这些方面的内容,又恰好没什么其它要研究的了,所以 ...

  7. php tire树,Immutable.js源码之List 类型的详细解析(附示例)

    本篇文章给大家带来的内容是关于Immutable.js源码之List 类型的详细解析(附示例),有一定的参考价值,有需要的朋友可以参考一下,希望对你有所帮助. 一.存储图解 我以下面这段代码为例子,画 ...

  8. 我的世界SkyPixel像素天空HTML官网源码

    简介: 我的世界SkyPixel像素天空HTML官网源码,上传HTML源码至空间网站目录,绑定域名即可访问.源码自带中文和英文 网盘下载地址: http://kekewangLuo.cc/1AtBrL ...

  9. 常用JS库源码 - store.js源码/underscore.js源码

    常用JS库源码 Store.js源码 "use strict" // Module export pattern from // https://github.com/umdjs/ ...

最新文章

  1. 快速创建Angular组件并定义传参、绑定自定义事件的方法
  2. R语言ggplot2可视化将图像标题(title)居中显示实战
  3. 计算机基础课程教学创新,【计算机基础论文】大学计算机基础课程教学创新探讨(共5359字)...
  4. 读服务器文件,读取服务器文件
  5. 用计算机模仿大脑,用计算机复刻大脑
  6. ElementUI自定义icon步骤条
  7. LeetCode 459. 重复的子字符串(数学)
  8. Ubuntu 16.04 使用useradd添加用户后没有家目录的解决方法
  9. 列表list,元组tuple,字符串
  10. GitHub发布年度机器学习榜:TensorFlow最火,PyTorch失踪,Julia第四
  11. pytorch自动求导-07
  12. Mac vscode花屏问题解决
  13. 定时备份windows机器上的文件到linux服务器上的操作梳理(rsync)
  14. 数字电路-逻辑函数化简
  15. 面经手册 · 第12篇《面试官,ThreadLocal 你要这么问,我就挂了!》
  16. 超强总结,用心分享丨大数据超神之路(三):Linux必备知识
  17. 人工蜂群算法(Artificial Bee Colony, ABC)MATALAB代码详细解析
  18. 二分查找法及二分搜索树及其C++实现
  19. 添加VBA控件按钮及操作提示框
  20. FFmpeg视频解码中的YUV420P格式

热门文章

  1. 微软、滴滴、360等前端大厂面试题
  2. 计算机编程技术的历史变迁以及未来发展
  3. 至每一位在努力奋斗的人-----复旦女生的高三生活
  4. 1244:和为给定数
  5. Pricing the future
  6. matlab复函数求模长,matlab计算带有复数的函数,最后求复数函数的模,结果里面却有...
  7. 雪球产品期权价值蒙特卡洛模拟(1)
  8. 〖NOIP2004P〗FBI树
  9. ubuntu chromium代码编译
  10. UDP的应用范围、与TCP之比较