上一篇文章我們和大家分享了JS和H5編寫推箱子遊戲,本文主要和大家分享用js和H5標籤編寫經典遊戲:Flappy Bird 簡易版,希望能幫助大家。 聲明:本人初學JS和H5,本文涉及編寫方式以及演算法如有更好地改進,請各位大佬提出建議~ 一.能力需求: JavaScript相關知識,HTML5,json,物件導向想法等。 二.需要完成以下工作: 1.設計遊戲(包括小鳥、柱子等,我們必須遵循一條清晰的邏輯:鳥是在做豎直上拋和自由落體運動,柱子不斷創建並向左移動,將鳥和柱子隔離開,各自動各自的) 2.畫不會扇翅膀的小鳥 3.讓鳥飛起來(自由落體,點擊滑鼠垂直上拋) 4.邏輯部分(柱子向左移動,小鳥碰到柱子遊戲結束等) #5.分數計算(每透過一個柱子分數加1) 6.加背景(圖片,音樂,音效等) 三.開始寫: ##首先我們需要創建4個類別:game.html(用來運行遊戲),game.js(用來寫邏輯部分),paint.js(用來畫物體),pojo.js(用來創建對象,進行封裝) game.html類別: 用HTML5寫,需要加,而且我習慣把margin設定為0 : flappy bird /*设置边距为0*/ body{ margin:0px; overflow:hidden; } /*加背景*/ #can1{ background-image: url(背景.jpg); background-size:100%100%; } window.onload = function (){ run(); }; 登入後複製其實在html檔案裡就呼叫run()方法,裡面就寫標籤和用來調入音訊檔案pojo.js類別:用來存放類別,我們需要兩個類別:小鳥的柱子。在這裡,我們做的是簡易版的,所以小鳥用圓近似代替,這樣的話之後的碰撞檢測好寫一些。其中,確定的值直接寫出來,可變的值不賦值。 書寫格式如下://鸟类(圆代替) function Bird(color){ //鸟的颜色 this.color = color; //鸟巩膜的颜色 this.eyeColor = 'white'; //鸟巩膜的半径 this.eyeRadius = 10; //鸟瞳孔的颜色 this.eyeInsideColor = 'black'; //鸟瞳孔的半径 this.eyeInsideRadius = '2.5'; //鸟初始位置 this.left = 100; this.top = 300; //鸟身体半径 this.size = 25; //鸟的速度 this.v = 0; //向下为+ 向上为- //分数计算 this.score = 0; //是否存活 this.isLive = true; }登入後複製#注意:我們一定要把鳥的所有屬性要考慮到,封裝好在game.js中使用然後我們還需要寫柱子(pillar)類別://柱子类 //上面下面各一个,在一个竖直线上; //上下柱子的间隔固定; //左右两列柱子间隔固定; //柱子的粗细固定; //柱子相对鸟的速度 function Pillar(position, length){ //柱子颜色 this.color = 'lime'; //柱子向左移动速度 this.v = 8; //柱子宽度 this.width = 80; //柱子位置,是每个柱子左上角的位置 this.position = position; //柱子长度 this.length = length; //柱子是否存在,意思一会解释 this.isLive = true; //鸟是否通过当前柱子 this.isPass = false; }登入後複製#接下來我們要寫paint.js類別了,也是比較麻煩的一個類,需要用來畫:#首先,最重要的,我建議每次寫paint.js都放到最前面:清屏(如果不清的話,上一次的畫的還會留下,就成「運動軌跡」了)://清屏 function clearScreen(ctx){ ctx.clearRect(0, 0, 1536, 750); }登入後複製然後,我們畫小鳥,畫鳥之前,我建議用畫圖工具畫出鳥的大致形狀,以便之後用代碼畫,這是我設計的鳥的圖案(翅膀有點複雜,還不知道怎麼畫好,所以在程序裡沒有畫,見諒) 設計出來以後,我們就可以開始在paint.js裡面畫了(我把獲取畫布和創建畫筆寫在了game.js裡面而不是paint.js)://画鸟,同样,所有的变量属性都要考虑到,并且在画巩膜,瞳孔,嘴唇时候都要以变量作为基准!里面的参数需要耐下心来调整。 function paintBird(ctx, left, top, size, color){ ctx.beginPath(); ctx.fillStyle = color; ctx.arc(left, top, size, 0, 2*Math.PI);//left:100 top:300 size:25 ctx.fill(); //画巩膜 ctx.beginPath(); ctx.fillStyle = bird.eyeColor; ctx.arc(left+13,top-8,bird.eyeRadius,0,2*Math.PI); ctx.fill(); //画瞳孔 ctx.beginPath(); ctx.fillStyle = bird.eyeInsideColor; ctx.arc(left+18,top-8,bird.eyeInsideRadius,0,2*Math.PI); ctx.fill(); //画嘴唇 ctx.beginPath(); ctx.strokeStyle = 'black'; ctx.fillStyle = '#FC6747'; ctx.moveTo(left+4, top+3); ctx.lineTo(left+29, top+3); ctx.lineTo(left+33, top+6); ctx.lineTo(left+29, top+9); ctx.lineTo(left+32, top+12); ctx.lineTo(left+29, top+15); ctx.lineTo(left+4, top+15); ctx.lineTo(left+1, top+9); ctx.closePath(); ctx.stroke(); ctx.fill(); ctx.beginPath(); ctx.strokeStyle = 'black'; ctx.moveTo(left+1,top+9); ctx.lineTo(left+29,top+9); ctx.stroke(); //画翅膀,简易版先不画 }登入後複製畫出來就是這樣( 我看著都想笑:) )##然後畫柱子://画柱子,我用了渐变色,虽然调整的并不是很好,但是还能看 function paintPillar(ctx, left, top, width, height){ ctx.beginPath(); var grd=ctx.createLinearGradient(left,top,left+height,top); grd.addColorStop(0,"green"); grd.addColorStop(0.45,"white"); grd.addColorStop(1,"green"); ctx.fillStyle = grd; ctx.fillRect(left, top, width, height); }登入後複製然後我們還要顯示計分://计分 function paintScore(ctx, score){ ctx.fillStyle = 'red'; ctx.font = '40px 黑体'; ctx.fillText('分数:'+score, 1320, 40); }登入後複製到這裡,我們的工作已經做了一半了,接下來就是最棘手的game.js了,需要考慮邏輯,碰撞,移動,計分等諸多問題。 首先介紹如何設計小鳥的自由落體:依照上述方法,寫程式如下://点击鼠标事件 document.onmousedown = function (ev){ //每点击一下出来音效 var oAudio = document.getElementById('audio1'); oAudio.src = '扇翅膀emm.wav'; //每点击一下做一次竖直上抛 jump(bird); } //鸟自由落体 function drop(bird){ bird.top = bird.top+bird.v; bird.v++; } //设置竖直上抛,直接将速度修改为负数即可 function jump(bird){ bird.v = -13; }登入後複製接下來是game.js,方便解釋,我直接在程式碼中邊寫邊解釋://游戏逻辑中枢 //创建鸟的对象 var bird = new Bird('#FBEE30'); //创建柱子对象,把20个柱子放到数组中 var pillar = new Array(20); var count = 0; function run(){ //获取画布 var oCan = document.getElementById('can1'); //创建画笔,2d var ctx = oCan.getContext('2d'); //这timer1是游戏页面显示的东西的线程,每25毫秒刷新一次 var timer1 = setInterval(function (){ //每次画之前先清屏 clearScreen(ctx); //判断鸟是否碰上柱子(是否死亡),死亡则弹框,结束线程 if(!bird.isLive){ alert('游戏结束!你的分数是'+bird.score+'!你好菜啊!') clearInterval(timer1); clearInterval(timer2); } //降落一下 drop(bird); //判断鸟是否通过柱子 for(var i = 0; i < pillar.length; i++){ if(pillar[i] != null && pillar[i].isLive){ //移动柱子 movePillar(pillar[i]); //画柱子 paintPillar(ctx, pillar[i].position.x, pillar[i].position.y, pillar[i].width, pillar[i].length); //计分,如果每个鸟的身体(圆)的左边通过一个柱子的最右边时,加一分 if(pillar[i].position.x+pillar[i].width <= bird.left-bird.size && !pillar[i].isPass){ //这里是因为每次通过上下两个柱子,如果是++的话就会每次加2,所以用+=0.5 bird.score += 0.5; //通过的柱子将isPass设置为true,否则程序将默认鸟通过了所有柱子,就会加每次21 pillar[i].isPass = true; } } } //检验鸟碰到柱子 for(var i = 0; i < pillar.length; i++){ if(pillar[i] != null && pillar[i].isLive){ judgeImpact(bird, pillar[i]); } } //死亡后执行 //画鸟 paintBird(ctx, bird.left, bird.top, bird.size, bird.color); paintScore(ctx, bird.score); }, 25); //这个timer2是创建柱子的线程 var timer2 = setInterval(function (){ //上面的柱子的长度 var l = Math.random()*330+100; //上面柱子,用json传变量 pillar[count++] = new Pillar({x:1536, y:0}, l); //下面柱子 pillar[count++] = new Pillar({x:1536, y:l+200}, 733-l-200); //如果柱子达到20个,自动将第20个设置为第0个,节省了内存,属于算法优化 if(count == 20){ count = 0; } }, 1100); } //点击鼠标事件 document.onmousedown = function (ev){ //每点击一下出来音效 var oAudio = document.getElementById('audio1'); oAudio.src = '扇翅膀emm.wav'; //每点击一下做一次竖直上抛 jump(bird); } //鸟自由落体 function drop(bird){ bird.top = bird.top+bird.v; bird.v++; } //设置竖直上抛,直接将速度修改为负数即可 function jump(bird){ bird.v = -13; } //移动柱子 function movePillar(pillar){ pillar.position.x -= pillar.v; //如果柱子移动出画布,将isLive设置为false if(pillar.position.x < -80){ pillar.isLive = false; } } //检测鸟和柱子碰撞 function judgeImpact(bird, pillar){ //碰到上边框,死亡 if(bird.top < bird.size){ bird.isLive = false; bird.top = bird.size; } //碰到下边框,死亡 else if(bird.top > 733-bird.size){ bird.isLive = false; bird.top = 733-bird.size; } //当小鸟嵌入上下柱子的左边时(检验鸟是否碰到柱子的左边) if(Math.abs(bird.left-pillar.position.x) <= bird.size){ //碰到上面柱子的左边 if(pillar.position.y == 0){ if(bird.top < pillar.length){ bird.isLive = false; bird.left = pillar.position.x-bird.size; } } //碰到下面柱子的左边 else{ if(bird.top > pillar.position.y){ bird.isLive = false; bird.left = pillar.position.x-bird.size; } } } //如果是上面柱子 if(pillar.position.y == 0){ //如果小鸟高度小于上面柱子 if(Math.abs(bird.top-pillar.length) <= bird.size){ //并且小鸟在两个柱子之间,死亡 if(bird.left+bird.size > pillar.position.x && bird.left+bird.size < pillar.position.x+pillar.width+30){ bird.isLive = false; bird.top = pillar.length+bird.size; } } } //如果是下面柱子 else{ //如果小鸟高度大于 下面柱子上方距离顶部的高度 if(Math.abs(bird.top-pillar.position.y) <= bird.size){ //并且小鸟在两个柱子之间,死亡 if(bird.left+bird.size > pillar.position.x && bird.left+bird.size < pillar.position.x+pillar.width+30){ bird.isLive = false; bird.top = pillar.position.y-bird.size; } } } }登入後複製四.總結:1.用canvas,js製作時一定需要以上的幾個類別(當然可以合併,為了辨別每個類別的功能我分成了那四個類別),需要有面向物件的思想,圖畫時候用物件裡面的參數來畫,例如:#paintPillar(ctx, pillar[i].position.x, pillar[i].position.y, pillar[i].width, pillar[i].length);登入後複製 2.关于柱子,我们必须符合这一条逻辑:先创建柱子数组(array),然后创建每个柱子,再画柱子,再移动,四个步骤缺一不可。 3.其中有对于算法的优化:在创建柱子的数组中,我们没有设置很多个柱子(比如说10000,虽然也不可能达到那么高的分),因为这样每25毫秒就要创建那么多柱子,再画和移动,内存占用很高,我们只用建立一个能存放20个柱子的数组,出屏幕的清掉,位置由新的代替,这样就极大程度减少了对内存的需要。 4.关于调试参数(比如说画鸟),要耐心调整,直到满意为止。 5.在检测鸟和柱子的碰幢时候,需要在逻辑上下功夫,建议先在稿纸上画出碰撞条件,再编写程序。