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

来源:https://www.cnblogs.com/LiQingsong/archive/2018/03/13/8558988.html
-Advertisement-
Play Games

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


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 目標是做一個牛叉的日曆,但是要先一步步來,先看效果圖 簡單一點說就是吧自定義view平分成七個部分,每個部分寫上一個字,然後自定義view可以跟隨手指滑動,併在抬起手指後恢復到初始位置,並且點擊view可以判斷出點的是哪個view(都是用坐標算出來) 代碼也簡單,看一下就ok 萬事開頭難,估計日曆系 ...
  • public class HightLightTextView extends TextView { // 存儲view的寬度 private int mTextViewWidth = 0; // 畫筆 private Paint mPaint; // 線性渲染 private LinearGrad... ...
  • css基本知識 我們先看一個小例子: 我們可以看到我們在div中加了style,裡面有background-color,height等屬性,這樣就使的原本什麼都沒有的div添加了背景色高度等。 css的編寫 在標簽上設置style屬性:width,height,background....... 寫 ...
  • React 進入文件APP.js,首先添加react-redux插件,使用react-redux的Provider模塊提供管道的儲存功能,傳入管道的屬性必須是store。 然而store參數是一個模塊功能,此模塊由redux的三個模塊組成,分別是創造、截獲、組成三個方法,createStore, a ...
  • 一 . ES6數組去重 1.拓展運算符(...)內部使用for...of迴圈, Set數據結構,它類似於數組,其成員的值都是唯一的. var arr = [3,5,6,5,4,6,9] console.log(...new Set(arr)) // [3,5,6,4,9] 2.利用Array.fro ...
  • 屬性: float 浮動 浮動的內容用div包起來,給div設置寬高 clear 清除浮動。 box-sizing 標準模式下的盒模型 content-box:(預設屬性) padding和border不被包含在定義的width和height之內。對象的實際寬度等於設置的width值和border、 ...
  • 1.去重 for(var q = 0;q<jsonArr.length;q++){ for(var e=0;e<jsonArr[q].data.length;e++){ var matchCode = jsonArr[q].data[e]; var chua = jsonArr[q].jsonObj ...
  • 一. 在JavaScript中,一切皆對象,每個對象都有一個原型對象(prototype),而指向該原型對象的內部指針則是__proto__。當我們對對象進行for in 或者for of遍歷時,就會通過__proto__依次遍歷對象關聯的所有對象。這就是原型鏈,遍歷所有對象的父對象直到object ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...