您好,登錄后才能下訂單哦!
1 寫在前面
看來《JavsScript高級編程》,想做一個小demo練練自己的手,選擇了貪吃蛇游戲。由于以前都是用c#寫的,將貪吃蛇寫到一個類里面,然后一個一個小方法的拆分,只向外提供需要提供的方法。這樣就可以將貪吃蛇作為一個模塊,任何地方都可以復用的。然而,用js進行編寫的時候,由于不能很好的利用js語言的特性進行模塊化編程,所以第一版的實現完全采用面向過程的方式,將函數中所需要的變量全部聲明為全局變量。雖然這樣也能夠實現功能,但是做不到復用,而且定義非常多的最頂層變量,污染了全局變量。寫完之后,總想將自己寫的重新封裝一次,達到只向外提供必須要提供的變量、或者功能函數接口。查了一些許多資料,對于js的封裝可以采用閉包的方式來進行實現。通過在函數內部聲明局部變量和閉包函數來當做類型的私有變量和函數,然后通過this給對象向外提供需要開發的接口。
2 貪吃蛇組件的使用
2.1 初級示例
示例代碼1如下:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>貪吃蛇組件</title> </head> <body> <canvas width="600" height="600" id="gameScense"></canvas> </body> <script src="SnakeGame.js"></script> <script> var snakeGame = new SnakeGame("gameScense",{ }); snakeGame.startGame(); </script> </html>
首先引入SnakeGame.js組件,然后通過實例化 SnakeGame對象,并向SnakeGame構造函數傳入兩個參數。第一參數是canvas的id,第二個參數游戲配置的對象,如果為空的話,那么采用默認的配置。最后,調用對象的startGame()方法,即可實現貪吃蛇的邏輯。默認的方向控制鍵為上下左右按鍵、暫停為空格,效果如下:
我們可以通過更改實例化時傳入的配置對象來實現對游戲的更多控制。
示例代碼2:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>貪吃蛇組件</title> </head> <body> <canvas width="600" height="600" id="gameScense"></canvas> </body> <script src="SnakeGame.js"></script> <script> var snakeGame = new SnakeGame("gameScense",{ snakeColor:"red", foodColor:"green", scenseColor:"blue", directionKey:[68,83,65,87], }); snakeGame.startGame(); </script> </html>
通過參數的名字可以知道,配置蛇、食物、游戲背景的顏色,以及控制游戲的方向鍵。配置方向順序為【左,下,右,上】。效果如下:
當然還有更加多的配置。還能夠定義分數改變的回調的函數,以及游戲結束時的回調函數等。下面介紹一下配置參數,以及SnakeGame對象共有的方法。
2.2 公有方法
•startGame() : 開始游戲。在該方法內,會初始化各種設置。如,重置分數,蛇身,速度等。
•changeGameStatus():改變游戲狀態,即暫停和開始,SnakeGame對象里面有一個私有變量,作為游戲的狀態變量。
2.3 配置游戲參數的對象gameConfigObj屬性、
gameConfigObj 對象一共該有10個屬性,3個回調函數
屬性
•size : 蛇塊和食物的大小,默認20
•rowCount : 行,默認30行
•colCount : 列,默認30列
•snakeColor : 蛇身顏色,默認green
•foodColor : 食物顏色,默認yellow
•scenseColor : 游戲場景背景色, 默black
•directionKey : 方向鍵, 默認[39, 40, 37, 38] 上下左右
•pasueKey : 暫停鍵, 默認32,空格鍵
•levelCount : 速度等級控制,默認10.
•curSpeed : 初始速度,默認200毫秒
回調函數
•onCountChange : 事件,每一個食物,分數改變,并調用該方法,帶有一個參數(count)
•onGamePause : 事件,游戲狀態改變時,調用該方法,帶有一個參數 1,代表暫停,0 ,代表游戲在進行。
•onGameOver : 事件,游戲結束時,調用該方法。
2.4使用進階
通過上面的屬性我們可以設計一個交互性更加強的程序。代碼如下。
示例3
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>貪吃蛇組件</title> <style type="text/css"> *{ margin:0px; padding:0px; } #gamebd{ width:850px; margin:50px auto; } #gameScense{ background-color:green; float:left; } #gameSet{ margin-left:10px; float:left; } .gameBoxStyle{ margin-bottom:7px; padding:5px 10px; } .gameBoxStyle h4{ margin-bottom:7px; } .gameBoxStyle p{ line-height: 1.7em; } .gameBoxStyle input{ margin-top:7px; background-color: white; border:1px gray solid; padding:3px 9px; margin-right:9px; } .gameBoxStyle input[type=text]{ width:90px; } .gameBoxStyle input:hover{ background-color: #e2fff2; } .gameBoxStyle #txtValue{ color:red; } </style> </head> <body> <div id="gamebd"> <canvas id="gameScense" width="600" height="600"> </canvas> <div id="gameSet"> <div id="gameControl" class="gameBoxStyle"> <h4>游戲控制</h4> <p>方向鍵:上,下,左,右</p> <p>開始/暫停:空格</p> </div> <div id="gameStatus" class="gameBoxStyle"> <h4>游戲狀態</h4> <p>用戶名:<input type="text" placeholder="輸入用戶名:" id="txtUserName" value="游客123"/> </p> <p>當前用戶1得分:<span id="txtValue">0</span></p> <input type="button" value="開始游戲" id="btnStart"/> <input type="button" value="暫停" id="btnPause"/> </div> <div id="game" class="gameBoxStyle"> <h4>游戲記錄</h4> <a href="#" rel="external nofollow" rel="external nofollow" >查看歷史記錄</a> </div> </div> </div> <script src="js/SnakeGame.js"></script> </body> <script src="SnakeGame.js"></script> <script> var btnStart=document.getElementById("btnStart"); var btnPasue=document.getElementById("btnPause"); var gameSnake = new SnakeGame("gameScense",{ snakeColor:"red", onCountChange:function(count){ var txtScore=document.getElementById("txtValue"); txtScore.innerText=count.toString( ); txtScore=null; }, onGamePause:function(status){ if(status){ btnPasue.value = "開始"; }else { btnPasue.value = "暫停" } }, onGameOver:function (status) { alert("游戲結束"); } }); btnStart.onclick=function(event){ if(checkUserName()){ gameSnake.startGame(); btnStart.blur(); } } btnPasue.onclick=function(event) { gameSnake.changeGameStatus(); btnStart.blur(); } function checkUserName(){ var txtUserName = document.getElementById("txtUserName"); if(txtUserName.value.length==0){ alert("用戶名不能為空"); return false; }else { return true; } } </script> </html>
上面的代碼通過設置OnChangeCount、onGamePause、onGameOver,三個回調函數,實現界面與組件的交互。效果如下:
在《JavaScript高級編程》這本書中說道一個模塊模式,但是這種模式是單例模式,也就是閉包最后返回一個字面量的對象。但是我需要在一個頁面中能夠同時開啟兩個貪吃蛇的窗口,兩個游戲通過設置配置不同的方向鍵和按鈕操作,實現兩個人同時一起玩。所以,在實現SnakeGame組件時,沒有采用道格拉斯所說的模塊模式。下面演示一下,如何在一個頁面中,讓兩個人同時一起玩游戲。代碼如下:
示例4
首先建立一個html文件
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Jaume's貪吃蛇</title> <link rel="stylesheet" href="css/gameStyle.css" rel="external nofollow" > </head> <body> <div id="gamebd"> <canvas id="gameScense" width="600" height="600"> </canvas> <canvas id="gameScense1" width="600" height="600" > </canvas> <div id="gameSet"> <div id="gameControl" class="gameBoxStyle"> <h4>游戲控制</h4> <p>方向鍵:上,下,左,右</p> <p>開始/暫停:空格</p> </div> <div id="gameStatus" class="gameBoxStyle"> <h4>游戲狀態</h4> <p>當前用戶1得分:<span id="txtValue">0</span></p> <p>當前用戶2得分:<span id="txtValue1">0</span></p> <input type="button" value="開始游戲" id="btnStart"/> </div> <div id="game" class="gameBoxStyle"> <h4>游戲記錄</h4> <a href="#" rel="external nofollow" rel="external nofollow" >查看歷史記錄</a> </div> </div> </div> <script src="js/SnakeGame.js"></script> <script src="js/UIScript.js"></script> </body> </html>
樣式文件如下:
*{ margin:0px; padding:0px; } #gamebd{ /*width:850px;*/ /*margin:50px auto;*/ width:100%; } #gameScense{ background-color:green; float:left; } #gameSet{ margin-left:10px; float:left; } .gameBoxStyle{ margin-bottom:7px; padding:5px 10px; } .gameBoxStyle h4{ margin-bottom:7px; } .gameBoxStyle p{ line-height: 1.7em; } .gameBoxStyle input{ margin-top:7px; background-color: white; border:1px gray solid; padding:3px 9px; margin-right:9px; } .gameBoxStyle input[type=text]{ width:90px; } .gameBoxStyle input:hover{ background-color: #e2fff2; } .gameBoxStyle #txtValue{ color:red; }
在html中拖入了兩個文件,一個是貪吃蛇組件,另一個是UIScript.js,其中的代碼如下:
/** * Created by tjm on 8/16/2017. */ var btnStart=document.getElementById("btnStart"); var gameSnake = new SnakeGame("gameScense",{ snakeColor:"red", directionKey:[68,83,65,87], pauseKey:81, onCountChange:function(count){ var txtScore=document.getElementById("txtValue"); txtScore.innerText=count.toString( ); txtScore=null; }, onGameOver:function (status) { alert("游戲結束"); } }); var gameSnake1 = new SnakeGame("gameScense1",{ snakeColor:"green", size:20, onCountChange:function(count){ var txtScore=document.getElementById("txtValue1"); txtScore.innerText=count.toString(); txtScore=null; }, onGameOver:function (status) { alert("游戲結束"); } }); btnStart.onclick=function(event){ gameSnake.startGame(); gameSnake1.startGame(); btnStart.blur(); }
實例化兩個SnakeGame對象,一個對象使用默認的上下左右鍵和空格鍵作為方向鍵和暫停鍵,而另一個使用了,W、A、S、D 以及 Q 作為方向鍵和暫停鍵。效果如下:
嗯哼,沒錯,完美實現了。使用SnakeGame這個組件,創建貪吃蛇游戲就是如此的簡單。下面簡單介紹一下,組件的實現方式。
3貪吃蛇組件實現方式
在上一節中就提到過,沒有采用過道哥拉斯的設計模式,下面給出貪吃蛇設計結構。具體的源代碼,可以在后面的鏈接中進行下載。代碼如下:
/** * Created by tjm on 8/18/2017. */ var SnakeGame = function () { /*蛇塊和食物組件類*/ function SnakeBlock(row,col){ this.row=row; this.col=col; } SnakeBlock.prototype.draw = function(graphic,color,size){ graphic.fillStyle=color; graphic.fillRect(size*this.col,size*this.row,size-2,size-2); } SnakeBlock.prototype.clearDraw = function(graphic,color,size){ graphic.fillStyle=color; graphic.fillRect(size*this.col,size*this.row,size,size); } SnakeBlock.prototype.equal = function(snakeBlock){ if(snakeBlock.row==this.row && snakeBlock.col==this.col){ return true; }else{ return false; } } /*貪吃蛇組件類*/ function SnakeGame(gameScenseId, gameConfigObj) { // 私有屬性 var gameScense = document.getElementById(gameScenseId); var graphic = gameScense.getContext("2d"); var count = 0; var snake; var curFood; var runId; var isMoved = false;//方向改變后,如果沒有移動則方向鍵暫時失效。 var gameStatus = false; var curDirection = 1; var size = gameConfigObj.size || 20; var rowCount = gameConfigObj.rowCount || 30; var colCount = gameConfigObj.colCount || 30; var snakeColor = gameConfigObj.snakeColor || "green"; var foodColor = gameConfigObj.foodColor || "yellow"; var scenseColor = gameConfigObj.scenseColor || "black"; var directionKey = gameConfigObj.directionKey || [39, 40, 37, 38]; var pauseKey = gameConfigObj.pauseKey || 32; var levelCount = gameConfigObj.levelCount || 10; var curSpeed = gameConfigObj.curSpeed || 200; //公開事件 var onCountChange = gameConfigObj.onCountChange || null; //帶有一個參數 var onGamePause = gameConfigObj.onGamePause || null; //帶有一個參數 var onGameOver = gameConfigObj.onGameOver || null; //判斷 if(gameScense.width != size*rowCount || gameScense.height != size*colCount){ throw "場景大小不等于行列大小*蛇塊大小"; } //特權方法 this.startGame = startGame; this.changeGameStatus = changeGameStatus; //注冊 dom 鍵盤事件 var preFunc = document.onkeydown; document.onkeydown = function (e) { var key = (e || event).keyCode; handleKeyInput(key); if (typeof preFunc == "function") { preFunc(e); } } //私有方法 /*初始化蛇身*/ function initSnake(){ ··· } /*繪制場景背景色*/ function initScense(){ ··· } /*產生食物*/ function genFood(){ ··· } /*吃食物*/ function eatFood(snakeHead){ ··· } /*判斷游戲是否結束*/ function gameOver(){ ··· } /*蛇移動*/ function snakeMove(){ ··· } function changeSpeed(){ ··· } function handleKeyInput(key){ ··· } function initGame(){ ··· } function triggerEvent(callback,argument){ ··· } function runGame(){ ··· } function pauseGame() { ··· } function changeGameStatus(){ ··· } function startGame(){ ··· } } return SnakeGame; //最后返回一個組件構造函數 }();
上面有一個很重要的地方,就是鍵盤注冊的代碼,單獨列出來分析一下。
var preFunc = document.onkeydown; document.onkeydown = function (e) { var key = (e || event).keyCode; handleKeyInput(key); if (typeof preFunc == "function") { preFunc(e); } }
該段代碼的邏輯是,首先判斷在 document 上是否注冊了onkeydown 事件,如果注冊了該事件,則保存所引用的事件處理程序,然后重置onkeydown事件程序,然后在新的事件處理程序中,調用先前的事件處理程序,這樣就實現了事件觸發后,調用所有監聽該事件處理程序,而不是直接覆蓋。
另外關于貪吃蛇的設計邏輯,可以參看我另外一篇文章,個人覺得講的非常詳細了,文章:基于控制臺實現貪吃蛇游戲
3 小結
通過這次貪吃蛇組件的設計,對 js 的模塊化設計稍微了解了一下,但是,我也不知道上文所實現的貪吃蛇模塊有哪些缺陷,希望有大神看到這篇文章,能給一些指導。當然了,該組件還可以進行進一步的擴展,比如將游戲的方塊,替換成圖片。有興趣的可以從下面的鏈接進行下載,更改后別忘了分享哦。
源碼下載鏈接:https://github.com/StartAction/SnakeGame
以上所述是小編給大家介紹的JavaScript貪吃蛇小組件實例代碼,希望對大家有所幫助,如果大家有任何疑問歡迎給我留言,小編會及時回復大家的!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。