第一次寫博客也不知怎麼寫,反正就按照我自己的想法來吧!怎麼說呢?還是不要扯那些多餘的話了,直接上正題吧! 第一次用canvas寫游戲,所以挑個簡單實現點的來乾:別踩白塊兒,其他那些怎麼操作的那些就不用再扯了,大家應該都懂,不懂的看到黑色的就點就是了,扯多了我打字手也累,大概。鏈接給你們:http:/ ...
第一次寫博客也不知怎麼寫,反正就按照我自己的想法來吧!怎麼說呢?還是不要扯那些多餘的話了,直接上正題吧! 第一次用canvas寫游戲,所以挑個簡單實現點的來乾:別踩白塊兒,其他那些怎麼操作的那些就不用再扯了,大家應該都懂,不懂的看到黑色的就點就是了,扯多了我打字手也累,大概。鏈接給你們:http://nowtd.cn/white/
咱不是理論派,所以在這裡不會扯多少理論。
首先看看html的結構吧
1 <header class="container"> 2 <article class="game-info"> 3 <span class="show sorce rounded">分數:<span class="num">0</span></span> 4 <span class="time">時間: <span class="num">0</span>s</span> 5 </article> 6 </header> 7 <main class="container"> 8 <canvas id="game-body">你的瀏覽器不支持canvas!!!</canvas> 9 </main> 10 <section class="control container"> 11 <button class="btn rounded" id="game-play">開始</button> 12 <button class="btn rounded" id="game-over">結束</button> 13 </section>
很簡單,只有一個canvas和一些按鈕 and 用作顯示分數那些的span
css的內容就不貼了,反正只是給它作一下簡單佈局而已,重要的還是js怎麼去實現它的。
首先先看看怎麼調用吧
1 <script> 2 (function () { 3 let wb = new WhiteBlock({ 4 canvas: '#game-body', 5 play: '#game-play', 6 over: '#game-over', 7 sorce: '.sorce > .num', 8 time: '.time > .num', 9 width: 330, 10 height: 450, 11 col: 4, 12 row: 4, 13 }); 14 })(); 15 </script>
就這麼點東西:
canvas: 畫布,
play: 開始按鈕,
over: 結束按鈕,
sorce: 顯示成績,
time: 顯示時間,
white: 畫布的寬,
height: 畫布的高,
col: 一列多少個磚塊
row: 一共多少行
內部的一些參數
1 this.speedLevel = [4, 5, 6, 7, 8]; 2 3 // 成績 4 this.sorceNumber = 0; 5 this.Time = 0; 6 7 //定時器 8 this.timer = null; 9 10 // 是否已開始游戲 11 this.isPlay = false; 12 // 是否已結束游戲 13 this.isOver = false; 14 15 // 畫布參數 16 this.width = null; 17 this.height = null; 18 this.canvas = null; 19 this.ctx = null; 20 21 // 磚塊 22 this.blockQueue = []; 23 this.blockWidth = null; 24 this.blockHeight = null; 25 this.col = 4; 26 this.row = 6; 27 // 速度 28 this.offset = this.speedLevel[0];
這裡要說的大概就只有speedLevel和this.blockQueue了:
speedLevel: 輸入一個規定速度的數組,然後根據成績的提升來獲取相應的速度值
1 changeSpeed() { 2 if (this.sorceNumber < (this.speedLevel.length * 10)) { 3 let num = Math.floor(this.sorceNumber / 10); 4 if (this.speedLevel[num]) { 5 this.offset = this.speedLevel[num]; 6 } else { 7 this.offset = this.speedLevel[this.speedLevel.length - 1]; 8 } 9 10 } 11 }
當成績超過一定值之後,就以speedLevel的最後一個值為速度,不再加速。
this.blockQueue:生成一組隊列做點陣來繪製磚塊,分別由createBlock, addBlock,removeBlock,fillBlock這四個方法來操控,drawing方法會遍歷this.blockQueue
1 // 創建磚塊 2 createBlock() { 3 let len = this.col, 4 i = 0, 5 list = [], 6 rand = Math.floor(Math.random() * len); // 隨機黑塊 7 8 for (; i < len; i ++) { 9 list.push(rand === i ? 1 : 0); 10 } 11 return list; 12 } 13 14 // 添加磚塊隊列 15 addBlock(i) { 16 this.blockQueue.unshift({top: -((i + 1) * this.blockHeight), data: this.createBlock()}); 17 } 18 19 // 移除磚塊隊列 20 removeBlock() { 21 (this.blockQueue.length > 0) && this.blockQueue.pop(); 22 }29 30 fillBlock() { 31 let len = this.row + 1, // 多加一隊,以免剛好一屏 32 i = 0; 33 34 for (; i < len; i ++) { 35 this.addBlock(i); 36 } 37 } 38 39 drawing() { 40 this.ctx.clearRect(0, 0, this.width, this.height); 41 this.each(this.blockQueue, (index, item) => { 42 let top = item['top'], 43 block = item['data'], 44 ctx = this.ctx; 45 46 this.each(block, (index, item) => { 47 ctx.fillStyle = parseInt(item) === 1 ? '#000' : '#fff'; 48 ctx.beginPath(); 49 ctx.fillRect(index * this.blockWidth, top, this.blockWidth, this.blockHeight); 50 }); 51 }); 52 }
因為個人喜歡從數組第一個值迭代,所以這裡把隊列反轉過來操作了
而每個磚塊的寬高都由col和row決定:
1 getBlockWidthAndHeight() { 2 this.blockWidth = this.width / this.col; 3 this.blockHeight = this.height / this.row; 4 return this; 5 }
把磚塊繪製好了之後就是要讓磚塊跑起來了,我讓每一隊磚塊都加一個offset值,this,offset為一個速度值
1 this.offset = this.speedLevel[num];
1 runPlay() { 2 this.each(this.blockQueue, (index, item) => { 3 item['top'] += this.offset; 4 }); 5 this.drawing(); 6 this.timer = setTimeout(this.runPlay.bind(this), 20); 7 // 修改時間 8 this.changeTime(); 9 }
runplay方法設定為20毫秒重繪一次,這個20是隨便寫上去的,至於是否合理就暫時不管了
點擊開始按鈕觸發事件, 開始游戲
1 playGame(e) { 2 if (this.isOver) { 3 // 重新開始 4 this.replay(); 5 console.log(this); 6 } 7 // 讓磚塊跑起來 8 if (!this.isPlay) { 9 // 檢測是否有黑塊到底 10 this.checkState(); 11 // 讓磚塊跑進來 12 this.runPlay(); 13 this.play.html('暫停'); 14 this.isPlay = true; 15 } else { 16 // 暫停游戲 17 this.puaseGame(); 18 this.play.html('繼續'); 19 } 20 }
暫停游戲其實就是清除定時器
1 puaseGame() { 2 clearTimeout(this.timer); 3 this.isPlay = false; 4 }
判斷輸贏: 怎麼個輸贏法就不講了,只說實現。
1 checkState() { 2 let i = this.blockQueue.length - 1, 3 item = this.blockQueue[i], 4 top = item['top'], 5 data = item['data']; 6 if ((this.height) <= top) { 7 this.checkBlock(data) ? (() => { 8 this.again(); 9 requestAnimationFrame(this.checkState.bind(this)); 10 })() : this.puaseGame(); 12 requestAnimationFrame(this.checkState.bind(this)); 13 } 14 }
每次運行都取出隊列中最後的一隊,並判斷它的top值是否大於或小於畫布的高,如果最前面的一隊(數組最後一個)的top大於畫布高,同時遍歷這個隊列,如果有一個值為1(黒塊),則游戲失敗,並調用gameOver方法。
否則,即說明當前隊列安全通過,就調用again方法,這個方法里移除隊首,併在隊尾添加一隊,這樣不斷迴圈生成磚塊。
點擊判斷輸贏,這裡有人可能想到了用getImageData來獲取顏色值來判斷。這樣做對於這個只有黒和白的游戲來說的確是很方便的,但是這裡我沒用獲取顏色值來做(最開始是那樣打算來著),因為這裡不僅要判斷輸贏,當點擊到了黒塊之後也要修改黒塊的顏色的,最終還是逃不出要判斷當前點擊的是哪個磚塊這一步,所以這裡用碰撞檢測來做就好了,也節省了一個方法的代碼量(但其實在這裡一個方法內的代碼量也不會很多),下麵是代碼:
1 isWin(e) { 2 let x = e.offsetX || e.originalEvent.layerX, 3 y = e.offsetY || e.originalEvent.layerY, 4 data = this.blockQueue; 5 6 this.each(data, (index, item) => { 7 let top = item['top']; 8 if (y > top && y < (data[index + 1] ? data[index + 1]['top'] : top + this.blockHeight)) { 9 // 判斷點擊的列中的黑塊是否被點中 10 this.checkCloumn(x, item['data'], index); 11 } 12 }); 13 } 14 15 checkCloumn(x, data, i) { 16 this.each(data, (index, item) => { 17 let left = index * this.blockWidth; 18 19 if (x > left && x < (left + this.blockWidth)) { 20 if (item === 1) { 21 this.blockQueue[i]['data'][index] = 0; 22 // 記錄成績 23 this.addSorce(); 24 this.drawing(); 25 } else { 26 this.gameOver(); 27 this.puaseGame(); 28 } 29 } 30 }); 31 }
點擊結束按鈕就調用一個replay方法,重置那些配置參數
1 replay() { 2 // 清空磚塊隊列 3 this.blockQueue = []; 4 // 重置數據 5 this.offset = this.speedLevel[0]; 6 this.sorceNumber = 0; 7 this.Time = 0; 8 this.isPlay = false; 9 this.timer = null; 10 this.play.html('開始'); 11 this.sorce.html(0); 12 this.time.html(0); 13 //重新填充隊列 14 this.fillBlock(); 15 // 重新維制 16 this.drawing(); 17 18 this.isOver = false; 19 }
接下來成績和時間那個就簡單了,剩下就是處理游戲結束後的消息提示了,由於無論是有黒塊到了底下還是點擊了白塊,都會調用一個gameOver方法,
1 gameOver() { 2 this.isOver = true; 3 this.play.html('開始'); 4 // 顯示游戲結束提示 5 this.showMess(); 6 }
我在這個方法里調用了一個showMess方法,而我寫到最後不想寫這個了,要寫的話一開始就會寫好這塊了,所心只是簡單地彈出一個警告框算了
1 showMess() { 2 alert('游戲已結束!'); 3 }
到哪天想要弄個好看點的提示時,只要重寫這個方法就好了。
嘛,反正到這裡算是結束了。總感覺有點一直在貼代碼的感覺,哈哈!!
這是源碼地址: https://gitee.com/nowtd/test/tree/master/white
第一次寫博客,不清楚寫得怎樣?各位請多多指教!!!
補充一點: 這個demo引入了JQ,用來獲取dom和綁定事件,修改屬性那些,但現在感覺好像不引入JQ所需要寫的代碼也不會很多,不過嘛,能少寫就少寫吧。