手把手教你使用CanvasAPI打造一款拼圖游戲
一、canvas簡(jiǎn)介
canvas是HTML5提供的一種新標(biāo)簽,雙標(biāo)簽;
HTML5 canvas標(biāo)簽元素用于圖形的繪制,通過腳本 (通常是JavaScript)來完成;
canvas標(biāo)簽只是圖形容器,必須使用腳本來繪制圖形;
Canvas是一個(gè)矩形區(qū)域的畫布,可以用JavaScript在上面繪畫;
二、案例目標(biāo)
我們今天的目標(biāo)是使用HTML5畫布技術(shù)制作一款拼圖小游戲,要求將圖像劃分為3*3的9塊方塊并打亂排序,用戶可以移動(dòng)方塊拼成完整圖片。
效果如下所示:
三、程序流程
3.1 HTML靜態(tài)頁(yè)面布局
- <div id="container">
- <!--頁(yè)面標(biāo)題-->
- <h3>HTML5畫布綜合項(xiàng)目之拼圖游戲</h3>
- <!--水平線-->
- <hr />
- <!--游戲內(nèi)容-->
- <!--游戲時(shí)間-->
- <div id="timeBox">
- 共計(jì)時(shí)間:<span id="time">00:00:00</span>
- </div>
- <!--游戲畫布-->
- <canvas id="myCanvas" width="300" height="300" style="border:1px solid">
- 對(duì)不起,您的瀏覽器不支持HTML5畫布API。
- </canvas>
- <!--游戲按鈕-->
- <div>
- <button onclick="restartGame()">
- 重新開始
- </button>
- </div>
- </div>
效果如下所示:
我們可以看到頁(yè)面的大致結(jié)構(gòu)是已經(jīng)顯現(xiàn)出來了,就是骨架已經(jīng)搭建好了,現(xiàn)在我們要使用css強(qiáng)化樣式;
3.2 CSS打造頁(yè)面樣式
整體背景設(shè)置
- body {
- background-color: silver;/*設(shè)置頁(yè)面背景顏色為銀色*/
- }
游戲界面樣式設(shè)置
- #container {
- background-color: white;
- width: 600px;
- margin: auto;
- padding: 20px;
- text-align: center;
- box-shadow: 10px 10px 15px black;
- }
游戲時(shí)間面板樣式設(shè)置
- #timeBox {
- margin: 10px 0;
- font-size: 18px;
- }
游戲按鈕樣式設(shè)置
- button {
- width: 200px;
- height: 50px;
- margin: 10px 0;
- border: 0;
- outline: none;
- font-size: 25px;
- font-weight: bold;
- color: white;
- background-color: lightcoral;
- }
鼠標(biāo)懸浮時(shí)的按鈕樣式設(shè)置
- button:hover {
- background-color: coral;
- }
設(shè)置好界面整體樣式之后我們得到完整的界面,如下所示:
可以看到整體的靜態(tài)界面已經(jīng)搭建出來了
3.3 js構(gòu)建交互效果
3.3.1 對(duì)象的獲取以及圖片的設(shè)置
目標(biāo)對(duì)象的獲取
- var c = document.getElementById('myCanvas'); //獲取畫布對(duì)象
- var ctx = c.getContext('2d'); //獲取2D的context對(duì)象
聲明拼圖的圖片素材來源
- var img = new Image();
- img.src = "image/pintu.jpg";
- img.onload = function() { //當(dāng)圖片加載完畢時(shí)
- generateNum(); //打亂拼圖的位置
- drawCanvas(); //在畫布上繪制拼圖
- }
3.3.2 初始化拼圖
需要將素材圖片分割成3行3列的9個(gè)小方塊,并打亂順序放置在畫布上;
為了在游戲過程中便于查找當(dāng)前的區(qū)域該顯示圖片中的哪一個(gè)方塊,首先為原圖片上的9個(gè)小方塊區(qū)域進(jìn)行編號(hào);
定義初始方塊位置
- var num = [[00, 01, 02], [10, 11, 12], [20, 21, 22]];
打亂拼圖的位置
- function generateNum() { //循環(huán)50次進(jìn)行拼圖打亂
- for (var i = 0; i < 50; i++) {
- //隨機(jī)抽取其中一個(gè)數(shù)據(jù)
- var i1 = Math.round(Math.random() * 2);
- var j1 = Math.round(Math.random() * 2);
- //再隨機(jī)抽取其中一個(gè)數(shù)據(jù)
- var i2 = Math.round(Math.random() * 2);
- var j2 = Math.round(Math.random() * 2);
- //對(duì)調(diào)它們的位置
- var temp = num[i1][j1];
- num[i1][j1] = num[i2][j2];
- num[i2][j2] = temp;
- }
- }
繪制拼圖
自定義名稱的drawCanvas()方法用于在畫布上繪制亂序后的圖片;
- function drawCanvas() {
- //清空畫布
- ctx.clearRect(0, 0, 300, 300);
- //使用雙重for循環(huán)繪制3x3的拼圖
- for (var i = 0; i < 3; i++) {
- for (var j = 0; j < 3; j++) {
- if (num[i][j] != 22) {
- //獲取數(shù)值的十位數(shù),即第幾行
- var row = parseInt(num[i][j] / 10);
- //獲取數(shù)組的個(gè)位數(shù),即第幾列
- var col = num[i][j] % 10;
- //在畫布的相關(guān)位置上繪圖
- ctx.drawImage(img, col * w, row * w, w, w, j * w, i * w, w, w); // w:300 / 3 = 100(小圖寬度)
- }
- }
- }
- }
如下所示:
3.3.3 事件綁定
監(jiān)聽鼠標(biāo)監(jiān)聽事件
- c.onmousedown = function(e) {
- var bound = c.getBoundingClientRect(); //獲取畫布邊界
- var x = e.pageX - bound.left; //獲取鼠標(biāo)在畫布上的坐標(biāo)位置(x,y)
- var y = e.pageY - bound.top;
- var row = parseInt(y / w); //將x和y換算成幾行幾列
- var col = parseInt(x / w);
- if (num[row][col] != 22) { //如果當(dāng)前點(diǎn)擊的不是空白區(qū)域
- detectBox(row, col); //移動(dòng)點(diǎn)擊的方塊
- drawCanvas(); //重新繪制畫布
- var isWin = checkWin(); //檢查游戲是否成功
- if (isWin) { //如果游戲成功
- clearInterval(timer); //清除計(jì)時(shí)器
- ctx.drawImage(img, 0, 0); //繪制完整圖片
- ctx.font = "bold 68px serif"; //設(shè)置字體為加粗、68號(hào)字,serif
- ctx.fillStyle = "red"; //設(shè)置填充色為紅色
- ctx.fillText("游戲成功!", 20, 150); //顯示提示語句
- }
- }
- }
點(diǎn)擊方塊移動(dòng)
- function detectBox(i, j) {
- //如果點(diǎn)擊的方塊不在最上面一行
- if (i > 0) {
- //檢測(cè)空白區(qū)域是否在當(dāng)前方塊的正上方
- if (num[i-1][j] == 22) {
- //交換空白區(qū)域與當(dāng)前方塊的位置
- num[i-1][j] = num[i][j];
- num[i][j] = 22;
- return;
- }
- }
- //如果點(diǎn)擊的方塊不在最下面一行
- if (i < 2) {
- //檢測(cè)空白區(qū)域是否在當(dāng)前方塊的正下方
- if (num[i+1][j] == 22) {
- //交換空白區(qū)域與當(dāng)前方塊的位置
- num[i+1][j] = num[i][j];
- num[i][j] = 22;
- return;
- }
- }
- //如果點(diǎn)擊的方塊不在最左邊一列
- if (j > 0) {
- //檢測(cè)空白區(qū)域是否在當(dāng)前方塊的左邊
- if (num[i][j - 1] == 22) {
- //交換空白區(qū)域與當(dāng)前方塊的位置
- num[i][j - 1] = num[i][j];
- num[i][j] = 22;
- return;
- }
- }
- //如果點(diǎn)擊的方塊不在最右邊一列
- if (j < 2) {
- //檢測(cè)空白區(qū)域是否在當(dāng)前方塊的右邊
- if (num[i][j + 1] == 22) {
- //交換空白區(qū)域與當(dāng)前方塊的位置
- num[i][j + 1] = num[i][j];
- num[i][j] = 22;
- return;
- }
- }
- }
3.3.4 游戲計(jì)時(shí)
自定義函數(shù)getCurrentTime()用于進(jìn)行游戲計(jì)時(shí);
- function getCurrentTime() {
- s = parseInt(s); //將時(shí)分秒轉(zhuǎn)換為整數(shù)以便進(jìn)行自增或賦值
- m = parseInt(m);
- h = parseInt(h);
- s++; //每秒變量s先自增1
- if (s == 60) {
- s = 0; //如果秒已經(jīng)達(dá)到60,則歸0
- m++; //分鐘自增1
- }
- if (m == 60) {
- m = 0; //如果分鐘也達(dá)到60,則歸0
- h++; //小時(shí)自增1
- }
- //修改時(shí)分秒的顯示效果,使其保持兩位數(shù)
- if (s < 10)
- s = "0" + s;
- if (m < 10)
- m = "0" + m;
- if (h < 10)
- h = "0" + h;
- time.innerHTML = h + ":" + m + ":" + s; //將當(dāng)前計(jì)時(shí)的時(shí)間顯示在頁(yè)面上
- }
在JavaScript中使用setInterval()方法每隔1秒鐘調(diào)用getCurrentTime()方法一次,以實(shí)現(xiàn)更新效果;
- var timer = setInterval("getCurrentTime()", 1000)
3.3.5 游戲成功與重新開始
游戲成功判定與顯示效果的實(shí)現(xiàn)
自定義函數(shù)checkWin()用于進(jìn)行游戲成功判斷;
- function restartGame() {
- clearInterval(timer); //清除計(jì)時(shí)器
- s = 0; //時(shí)間清零
- m = 0;
- h = 0;
- getCurrentTime(); //重新顯示時(shí)間
- timer = setInterval("getCurrentTime()", 1000);
- generateNum(); //重新打亂拼圖順序
- drawCanvas(); //繪制拼圖
- }
如果成功則使用clearInterval()方法清除計(jì)時(shí)器。然后在畫布上繪制完整圖片,并使用fillText()方法繪制出“游戲成功”的文字圖樣;
- if (isWin) { //如果游戲成功
- clearInterval(timer); //清除計(jì)時(shí)器
- ctx.drawImage(img, 0, 0); //繪制完整圖片
- ctx.font = "bold 68px serif"; //設(shè)置字體為加粗、68號(hào)字,serif
- ctx.fillStyle = "red"; //設(shè)置填充色為紅色
- ctx.fillText("游戲成功!", 20, 150); //顯示提示語句
- }
3.4 最終效果演示
靜態(tài)效果如上所示,至于游戲成功這里伙計(jì)們可以自行操作;
四、總結(jié)
本次案例我們使用HTML5的新特性canvas畫布標(biāo)簽打造了簡(jiǎn)單的9宮格拼圖游戲,總體來說沒有特別的復(fù)雜,主要是圖片的分割方塊移動(dòng)事件的綁定,以及重新游戲的初始化操作,明確了游戲邏輯之后其實(shí)代碼的編寫其實(shí)不難。感興趣的小伙伴可以去嘗試一下。