1. 程式人生 > >用Canvas寫一個簡單的遊戲--別踩白塊兒

用Canvas寫一個簡單的遊戲--別踩白塊兒

來吧 ber -c [] for 輸入 itl event 內部

  第一次寫博客也不知怎麽寫,反正就按照我自己的想法來吧!怎麽說呢?還是不要扯那些多余的話了,直接上正題吧! 第一次用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所需要寫的代碼也不會很多,不過嘛,能少寫就少寫吧。

用Canvas寫一個簡單的遊戲--別踩白塊兒