当前位置:   article > 正文

前端实战小案例--canvas实战之FlappyBird小游戏_flappy bird 前端

flappy bird 前端

前端实战小案例--canvas实战之FlappyBird小游戏

想练习更多前端案例,请进个人主页,点击前端实战案例->传送门

觉得不错的记得点个赞?支持一下我0.0!谢谢了!

不积跬步无以至千里,不积小流无以成江海。

效果图如下:(由于图片有大小限制,所以录制的是10帧,有点卡顿的感觉,实际是很流畅的)

素材如下:

 

文件结构目录如下:

代码如下:

index.html

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <meta charset="utf-8">
  5. <meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no">
  6. <title>FlappyBird</title>
  7. <link rel="stylesheet" href="./css/index.css" />
  8. </head>
  9. <body>
  10. <canvas>
  11. <span>您的浏览器不支持画布元素,请使用谷歌浏览器</span>
  12. </canvas>
  13. </body>
  14. <script src="./js/Game.js"></script>
  15. <script src="./js/Background.js"></script>
  16. <script src="./js/Land.js"></script>
  17. <script src="./js/Pipe.js"></script>
  18. <script src="./js/Bird.js"></script>
  19. <script src="./js/SceneManager.js"></script>
  20. <script>
  21. var game = new Game();
  22. </script>
  23. </html>

index.css

  1. *{
  2. margin: 0;
  3. padding: 0;
  4. }
  5. canvas{
  6. display: block;
  7. margin: 0 auto;
  8. }

resource.json

  1. [
  2. {
  3. "imgs":[
  4. {"name":"Back1", "url":"./resource/images/Back1.png"},
  5. {"name":"Bird", "url":"./resource/images/Bird.png"},
  6. {"name":"Flash", "url":"./resource/images/Flash.png"},
  7. {"name":"Logo", "url":"./resource/images/Logo.png"},
  8. {"name":"Number", "url":"./resource/images/Number.png"},
  9. {"name":"Land2", "url":"./resource/images/Road2.png"},
  10. {"name":"Pipe1", "url":"./resource/images/Pipe1.png"},
  11. {"name":"Pipe2", "url":"./resource/images/Pipe2.png"},
  12. {"name":"Bomb", "url":"./resource/images/Bomb.png"}
  13. ]
  14. }
  15. ]

Background.js

  1. //背景类
  2. (function () {
  3. var Background = window.Background = function () {
  4. this.image = game.imgArr["Back1"];
  5. this.speed = 0;
  6. };
  7. Background.prototype.render = function () {
  8. game.ctx.clearRect(0,0,game.canvas.width,game.canvas.height);
  9. game.ctx.drawImage(this.image,this.speed,game.canvas.height*0.618-this.image.height*0.618);
  10. game.ctx.drawImage(this.image,this.image.width+this.speed,game.canvas.height*0.618-this.image.height*0.618);
  11. game.ctx.drawImage(this.image,this.image.width*2+this.speed,game.canvas.height*0.618-this.image.height*0.618);
  12. game.ctx.save();
  13. game.ctx.fillStyle = "#4EC0CA";
  14. game.ctx.beginPath();
  15. game.ctx.fillRect(0,0,game.canvas.width,game.canvas.height*0.618-this.image.height*0.618+10);
  16. game.ctx.restore();
  17. game.ctx.save();
  18. game.ctx.fillStyle = "#DED895";
  19. game.ctx.beginPath();
  20. game.ctx.fillRect(0,game.canvas.height*0.618+this.image.height*(1-0.618)-10,game.canvas.width,game.canvas.height*(1-0.618)-this.image.height*(1-0.618)+10);
  21. game.ctx.restore();
  22. };
  23. Background.prototype.update = function () {
  24. this.speed--;
  25. if(-this.speed === this.image.width){
  26. this.speed = 0;
  27. }
  28. }
  29. })();

Bird.js

  1. (function () {
  2. var Bird = window.Bird = function () {
  3. this.image = game.imgArr["Bird"];
  4. this.x = game.canvas.width*(1-0.618);
  5. this.y = game.canvas.height*0.618*(1-0.618);
  6. //小鸟旋转的角度
  7. this.deg = 0;
  8. //小鸟是否在飞
  9. this.isFly = false;
  10. //帧数
  11. this.fno = 0;
  12. //鸟的颜色
  13. this.birdColor = 24*parseInt(Math.random()*3);
  14. //鸟扇动翅膀的图片的编号
  15. this.wingNo = 0;
  16. //下面的四个变量用于碰撞检测,代表鸟的上下左右的最小矩形框
  17. this.L = this.R = this.T = this.B = 0;
  18. //该变量用于保存鸟是否为死亡状态
  19. this.isDie = false;
  20. };
  21. Bird.prototype.render = function () {
  22. game.ctx.save();
  23. game.ctx.translate(this.x,this.y);
  24. game.ctx.rotate(this.deg);
  25. game.ctx.beginPath();
  26. game.ctx.drawImage(this.image,34*this.wingNo,this.birdColor,34,24,-17,-12,34,24);
  27. game.ctx.restore();
  28. // game.ctx.fillRect(this.L,this.T,34,24);
  29. };
  30. Bird.prototype.update = function () {
  31. this.L = this.x-17;
  32. this.R = this.x + 17;
  33. this.T = this.y-12;
  34. this.B = this.y + 12;
  35. //判断鸟是否飞出天空,真则不让飞出,
  36. if(this.T < 0){
  37. //这里不能使用this.T = 0;因为
  38. //game.ctx.drawImage(this.image,34*this.wingNo,this.birdColor,34,24,-17,-12,34,24);
  39. //game.ctx.translate(this.x,this.y);这里是根据x,y来绘制鸟的,固定T鸟还是飞出屏幕
  40. this.y = 12;
  41. }
  42. if(this.isFly){
  43. //这里为扇动翅膀动画
  44. if(this.fno%2 === 0){
  45. this.wingNo++;
  46. }
  47. if(this.wingNo > 2){
  48. this.wingNo = 0;
  49. }
  50. //当有点击事件时,鸟儿向上飞一段距离
  51. this.y -= 0.4 * (20-this.fno);
  52. if(this.fno === 20){
  53. this.fno = 0;
  54. this.isFly = false;
  55. }
  56. this.deg -= (Math.PI/180)*6;
  57. if(this.deg < -(Math.PI/180)*45){
  58. this.deg = -(Math.PI/180)*45;
  59. }
  60. }else{
  61. this.y += 0.4 * this.fno;
  62. this.deg += (Math.PI/180)*3;
  63. if(this.deg > (Math.PI/180)*90){
  64. this.deg = (Math.PI/180)*90;
  65. }
  66. }
  67. this.fno++;
  68. };
  69. Bird.prototype.fly = function () {
  70. this.fno = 0;
  71. this.isFly = true;
  72. }
  73. })();

Game.js

  1. (function(){
  2. var Game = window.Game = function(){
  3. this.canvas = document.querySelector("canvas");
  4. this.canvas.width = document.documentElement.clientWidth;
  5. this.canvas.height = document.documentElement.clientHeight;
  6. this.init();
  7. this.ctx = this.canvas.getContext('2d');
  8. //这是图片字典
  9. this.imgArr = {};
  10. //这是管子数组
  11. this.pipeArr = [];
  12. //游戏分数:即通过的管子数
  13. this.score = 0;
  14. //记录过多少时间new一个管子
  15. this.time_count = 0;
  16. //这里也需要使用箭头函数,否则this指向window而不是window.Game
  17. this.loadResource(()=>{
  18. this.startGame();
  19. });
  20. };
  21. //初始化画布宽高,适配移动端不同机型
  22. Game.prototype.init = function(){
  23. if(this.canvas.width < 320){
  24. this.canvas.width = 320;
  25. }else if(this.canvas.width > 414){
  26. this.canvas.width = 414;
  27. }
  28. if(this.canvas.height < 568){
  29. this.canvas.height = 568;
  30. }else if(this.canvas.height > 823){
  31. this.canvas.height = 823;
  32. }
  33. };
  34. //加载资源
  35. Game.prototype.loadResource = function(callback){
  36. //定义已加载的资源数
  37. var resource_count = 0;
  38. var xhr = new XMLHttpRequest();
  39. //这里需使用箭头函数,否则将出现this指向错误,
  40. xhr.onreadystatechange = ()=>{
  41. //此时的this指向window.Game
  42. if(xhr.readyState === 4 && xhr.status === 200){
  43. var obj = JSON.parse(xhr.responseText);
  44. for(let i=0;i<obj[0]["imgs"].length;i++){
  45. this.imgArr[obj[0]["imgs"][i].name] = new Image();
  46. this.imgArr[obj[0]["imgs"][i].name].src = obj[0]["imgs"][i].url;
  47. this.imgArr[obj[0]["imgs"][i].name].onload = ()=> {
  48. resource_count++;
  49. this.ctx.clearRect(0,0,this.canvas.width,this.canvas.height);
  50. this.ctx.save();
  51. let text = "正在加载资源"+ resource_count +"/"+obj[0]["imgs"].length;
  52. this.ctx.textAlign = "center";
  53. this.ctx.font = "20px 微软雅黑";
  54. this.ctx.beginPath();
  55. this.ctx.fillText(text,this.canvas.width/2,this.canvas.height*(1-0.618));
  56. this.ctx.restore();
  57. //当加载图片的数量等于图片资源的数量时,说明图片资源加载完毕,执行回调函数
  58. if(resource_count === obj[0]["imgs"].length){
  59. callback();
  60. }
  61. }
  62. }
  63. }
  64. };
  65. xhr.open('GET',"./resource.json");
  66. xhr.send();
  67. };
  68. //主线程:游戏开始
  69. Game.prototype.startGame = function () {
  70. //实例化场景管理器
  71. this.sm = new SceneManager();
  72. this.timer = setInterval(()=>{
  73. this.time_count += 20;
  74. this.sm.render();
  75. this.sm.update();
  76. },20)
  77. };
  78. })();

Land.js

  1. //大地类
  2. (function () {
  3. var Land = window.Land = function () {
  4. this.image = game.imgArr["Land2"];
  5. this.speed = 0;
  6. };
  7. Land.prototype.render = function () {
  8. game.ctx.drawImage(this.image,this.speed,game.canvas.height*0.618+99);
  9. game.ctx.drawImage(this.image,this.image.width+this.speed,game.canvas.height*0.618+99);
  10. };
  11. Land.prototype.update = function () {
  12. //每次更新判断鸟是否着地,着地则结束游戏
  13. if(game.bird.B > game.canvas.height*0.618+80){
  14. game.bird.isDie = true;
  15. }
  16. //近处的物体速度快,故大地的速度比远处的树林和白云快
  17. this.speed-=2;
  18. if(-this.speed > 640){
  19. this.speed = 0;
  20. }
  21. }
  22. })();

Pipe.js

  1. (function () {
  2. var Pipe = window.Pipe = function () {
  3. this.Pipe1 = game.imgArr["Pipe1"];
  4. this.Pipe2 = game.imgArr["Pipe2"];
  5. this.height = game.canvas.height*0.618+99;
  6. //两个管子之间的空隙高度
  7. this.interspace = 150;
  8. //上面的管子随机高度
  9. this.randomHeight = 140 + parseInt(Math.random()*180);
  10. //下面的管子的高度等于this.height-this.randomHeight-this.interspace
  11. this.Pipe2_height = this.height-this.randomHeight-this.interspace;
  12. this.speed = 0;
  13. //该变量用于碰撞检测
  14. this.L = 0;
  15. //该变量用于判断鸟是否已经通过管子,防止重复加分
  16. this.alreadyPass = false;
  17. //将自己推入数组
  18. game.pipeArr.push(this);
  19. };
  20. Pipe.prototype.render = function () {
  21. this.L = game.canvas.width+10+this.speed;
  22. //当下面的管子高度小于40时,重新取值
  23. while (this.Pipe2_height < 40){
  24. this.randomHeight = 140 + parseInt(Math.random()*180);
  25. this.Pipe2_height = this.height-this.randomHeight-this.interspace;
  26. }
  27. game.ctx.drawImage(this.Pipe1, 0, this.Pipe1.height-this.randomHeight, 52, this.randomHeight, this.L, 0, 52, this.randomHeight);
  28. game.ctx.drawImage(this.Pipe2, 0, 0, 52, this.Pipe2_height, this.L, this.randomHeight+this.interspace, 52, this.Pipe2_height);
  29. };
  30. Pipe.prototype.update = function () {
  31. //每次更新判断管子是否碰到鸟clearInterval(game.timer)
  32. if(this.L < game.bird.R && this.L+52 > game.bird.L){
  33. if(this.randomHeight > game.bird.T || this.randomHeight + this.interspace < game.bird.B){
  34. game.bird.isDie = true;
  35. }
  36. }
  37. this.speed -= 2;
  38. //判断鸟是否通过管子,真则加分
  39. if(game.bird.L > game.canvas.width+this.speed+52 && !this.alreadyPass){
  40. game.score++;
  41. this.alreadyPass = true;
  42. }
  43. //当管子移出画布时,将其从数组中移除
  44. if(-this.speed > game.canvas.width + 60){
  45. game.pipeArr.shift();
  46. this.speed = 0;
  47. }
  48. }
  49. })();

SceneManager.js

  1. (function () {
  2. var sm = window.SceneManager = function () {
  3. //sm的场景编号
  4. this.sceneNo = 1;
  5. //实例化背景
  6. game.bg = new Background();
  7. //实例化大地
  8. game.land = new Land();
  9. //实例化鸟
  10. game.bird = new Bird();
  11. //Flash和Logo图片的Y值
  12. this.FlashY = 0;
  13. this.LogoY = game.canvas.height;
  14. this.bindEvent();
  15. //这两个参数用于控制爆炸效果
  16. this.i = 0;
  17. this.j = 0;
  18. //这个参数用于控制鸟死亡下落的重力
  19. this.g = 1;
  20. };
  21. sm.prototype.update = function () {
  22. switch (this.sceneNo) {
  23. case 1:
  24. this.FlashY += 4;
  25. this.LogoY -= 4;
  26. if(this.FlashY > game.canvas.height * (1-0.618)){
  27. this.FlashY = game.canvas.height * (1-0.618);
  28. }
  29. if(this.LogoY < game.canvas.height * (1-0.618)+100){
  30. this.LogoY = game.canvas.height * (1-0.618)+100
  31. }
  32. break;
  33. case 2:
  34. //判断鸟是否死亡,真则切换到场景3
  35. if(game.bird.isDie){
  36. this.enter(3)
  37. }
  38. break;
  39. case 3:
  40. this.g ++;
  41. if(game.bird.y < game.canvas.height*0.618+99){
  42. game.bird.y += 0.4 * this.g;
  43. }else{
  44. this.enter(4);
  45. }
  46. break;
  47. case 4:
  48. if(game.time_count%100 === 0){
  49. this.i++;
  50. if(this.i > 3 && this.j === 0){
  51. this.i = 0;
  52. this.j = 1;
  53. }else if(this.i > 3 && this.j === 1){
  54. this.i = 3;
  55. this.j = 1;
  56. this.enter(5);
  57. }
  58. }
  59. break;
  60. case 5:
  61. this.FlashY += 4;
  62. if(this.FlashY > game.canvas.height * (1-0.618)){
  63. this.FlashY = game.canvas.height * (1-0.618);
  64. }
  65. break;
  66. }
  67. };
  68. sm.prototype.render = function () {
  69. //清屏
  70. game.ctx.clearRect(0,0,game.canvas.width,game.canvas.height);
  71. switch (this.sceneNo) {
  72. case 1:
  73. //场景1:游戏开始界面
  74. game.bg.render();
  75. game.land.render();
  76. game.bg.update();
  77. game.land.update();
  78. game.ctx.drawImage(game.imgArr["Flash"],0,0,204,69,game.canvas.width / 2 - 102,this.FlashY,204,69);
  79. game.ctx.drawImage(game.imgArr["Logo"],game.canvas.width / 2 - 160,this.LogoY);
  80. break;
  81. case 2:
  82. //场景2:游戏开始
  83. //每隔两秒new一个管子
  84. if(game.time_count > 2000){
  85. game.time_count = 0;
  86. new Pipe();
  87. }
  88. game.bg.render();
  89. game.land.render();
  90. game.bg.update();
  91. game.land.update();
  92. //遍历管子数组,更新并渲染
  93. for (let i=0;i<game.pipeArr.length;i++){
  94. game.pipeArr[i].update();
  95. game.pipeArr[i].render();
  96. }
  97. game.bird.update();
  98. game.bird.render();
  99. //绘制游戏分数
  100. game.ctx.save();
  101. game.ctx.font = "26px 微软雅黑";
  102. game.ctx.textBaseline = "top";
  103. game.ctx.beginPath();
  104. game.ctx.fillText("分数:",0,15);
  105. game.ctx.restore();
  106. var number = game.score.toString();
  107. for(let i=0; i<number.length; i++){
  108. game.ctx.drawImage(game.imgArr["Number"],28*parseInt(number[i]),0,28,36,28*i+75,10,28,36);
  109. }
  110. break;
  111. case 3:
  112. //场景3:小鸟撞地死亡
  113. //每隔两秒new一个管子
  114. if(game.time_count === 2000){
  115. game.time_count = 0;
  116. new Pipe();
  117. }
  118. game.bg.render();
  119. game.land.render();
  120. //遍历管子数组,更新并渲染
  121. for (let i=0;i<game.pipeArr.length;i++){
  122. game.pipeArr[i].render();
  123. }
  124. game.bird.render();
  125. //绘制游戏分数
  126. game.ctx.save();
  127. game.ctx.font = "26px 微软雅黑";
  128. game.ctx.textBaseline = "top";
  129. game.ctx.beginPath();
  130. game.ctx.fillText("分数:",0,15);
  131. game.ctx.restore();
  132. var number = game.score.toString();
  133. for(let i=0; i<number.length; i++){
  134. game.ctx.drawImage(game.imgArr["Number"],28*parseInt(number[i]),0,28,36,28*i+75,10,28,36);
  135. }
  136. break;
  137. case 4:
  138. //游戏结束:鸟撞管子死亡动画
  139. //每隔两秒new一个管子
  140. if(game.time_count === 2000){
  141. game.time_count = 0;
  142. new Pipe();
  143. }
  144. game.bg.render();
  145. game.land.render();
  146. //遍历管子数组,更新并渲染
  147. for (let i=0;i<game.pipeArr.length;i++){
  148. game.pipeArr[i].render();
  149. }
  150. //绘制游戏分数
  151. game.ctx.save();
  152. game.ctx.font = "26px 微软雅黑";
  153. game.ctx.textBaseline = "top";
  154. game.ctx.beginPath();
  155. game.ctx.fillText("分数:",0,15);
  156. game.ctx.restore();
  157. var number = game.score.toString();
  158. for(let i=0; i<number.length; i++){
  159. game.ctx.drawImage(game.imgArr["Number"],28*parseInt(number[i]),0,28,36,28*i+75,10,28,36);
  160. }
  161. //当鸟的y值大于地面的高度,则产生爆炸效果
  162. if(game.bird.y > game.canvas.height*0.618+99){
  163. game.ctx.drawImage(game.imgArr["Bomb"],128*this.i,128*this.j,128,128,game.bird.x-34,game.bird.y-34,34*2,34*2);
  164. }
  165. break;
  166. case 5:
  167. //游戏结束:GameOver
  168. if(game.time_count === 2000){
  169. game.time_count = 0;
  170. new Pipe();
  171. }
  172. game.bg.render();
  173. game.land.render();
  174. //遍历管子数组,更新并渲染
  175. for (let i=0;i<game.pipeArr.length;i++){
  176. game.pipeArr[i].render();
  177. }
  178. //绘制游戏分数
  179. game.ctx.save();
  180. game.ctx.font = "26px 微软雅黑";
  181. game.ctx.textBaseline = "top";
  182. game.ctx.beginPath();
  183. game.ctx.fillText("分数:",0,15);
  184. game.ctx.restore();
  185. var number = game.score.toString();
  186. for(let i=0; i<number.length; i++){
  187. game.ctx.drawImage(game.imgArr["Number"],28*parseInt(number[i]),0,28,36,28*i+75,10,28,36);
  188. }
  189. //GameOver图标往下移动到屏幕中间
  190. game.ctx.drawImage(game.imgArr["Flash"],204,0,204,69,game.canvas.width / 2 - 102,this.FlashY,204,69);
  191. }
  192. };
  193. //进入某个场景时需要做的操作
  194. sm.prototype.enter = function (sceneNo) {
  195. switch (sceneNo) {
  196. case 1:
  197. this.sceneNo = 1;
  198. this.FlashY = 0;
  199. this.LogoY = game.canvas.height;
  200. game.bird = new Bird();
  201. //实例化背景
  202. game.bg = new Background();
  203. //实例化大地
  204. game.land = new Land();
  205. game.pipeArr = [];
  206. game.time_count = 0;
  207. game.score = 0;
  208. //这两个参数用于控制爆炸效果
  209. this.i = 0;
  210. this.j = 0;
  211. //这个参数用于控制鸟死亡下落的重力
  212. this.g = 1;
  213. break;
  214. case 2:
  215. this.sceneNo = 2;
  216. //sm的场景编号
  217. break;
  218. case 3:
  219. this.sceneNo = 3;
  220. break;
  221. case 4:
  222. this.sceneNo = 4;
  223. break;
  224. case 5:
  225. this.sceneNo = 5;
  226. this.FlashY = 0;
  227. break;
  228. }
  229. };
  230. //
  231. sm.prototype.bindEvent = function () {
  232. //当点击canvas时,通过判断在哪个场景的哪个位置来做出相应的事件
  233. game.canvas.onclick = () => {
  234. switch (this.sceneNo) {
  235. case 1:
  236. game.bird.isDie = false;
  237. game.bird.x = game.canvas.width*(1-0.618);
  238. game.bird.y = game.canvas.height*0.618*(1-0.618);
  239. this.enter(2);
  240. break;
  241. case 2:
  242. game.bird.fly();
  243. break;
  244. case 3:
  245. break;
  246. case 4:
  247. break;
  248. case 5:
  249. this.enter(1);
  250. break;
  251. }
  252. }
  253. }
  254. })();

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家自动化/article/detail/323589
推荐阅读
相关标签
  

闽ICP备14008679号