面向對象案例——貪吃蛇遊戲
最近項目上線,近一個星期沒更博了,今天來寫一個經典的遊戲案例——貪吃蛇。在這個簡單的案例裏可以體會javaScript 面向對象開發相關模式,學習使用面向對象的方式分析問題。
1.功能實現
1.1 搭建頁面:放一個容器盛放遊戲場景 div#map,設置樣式
<div class="map" id="map"></div>
1 <style> 2 #map{ 3 background-color: #000; 4 width: 1500px; 5 height: 700px; 6 position: relative; 7 left: 0; 8 top: 0; 9 } 10 </style>
1.2 分析對象:食物對象、蛇對象、遊戲對象
1.3 創建食物對象Food
⑴ 屬性:位置(x,y)、大小(width、height)、顏色(color)
1 // 創建Food的構造函數,並設置屬性 2 function Food(width,height,bgColor) { 3 // 食物的寬度和高度(像素) 4 this.width=width||10; 5this.height=height||10; 6 // 食物的顏色 7 this.bgColor=bgColor||"white"; 8 }
⑵ 方法:render() 隨機創建一個食物對象,並輸出到map上
1 // 通過原型設置render方法,實現隨機產生食物對象,並渲染到map上 2 Food.prototype.render=function (map) { 3 remove(map); 4 // 隨機食物的位置,map.寬度/food.寬度,總共有多少分food的寬度,隨機一下。然後再乘以food的寬度 5 this.x=Math.floor(Math.random()*(map.offsetWidth/this.width))*this.width; 6this.y=Math.floor(Math.random()*(map.offsetHeight/this.height))*this.height; 7 // 動態創建食物對應的div 8 var newDiv=document.createElement("div"); 9 newDiv.style.position="absolute"; 10 newDiv.style.left=this.x+"px"; 11 newDiv.style.top=this.y+"px"; 12 newDiv.style.backgroundColor=this.bgColor; 13 newDiv.style.width=this.width+"px"; 14 newDiv.style.height=this.height+"px"; 15 map.appendChild(newDiv); 16 li.push(newDiv); 17 }
1.4 創建蛇對象Snake
⑴ 屬性:大小(width、height)、顏色(color)、方向(direction)、身體數組對象(body)
1 // Snake構造函數 2 function Snake(width,height,bgColor,direction) { 3 // 設置每一個蛇節的寬度 4 this.width=width||10; 5 this.height=height||10; 6 this.bgColor=bgColor||"white"; 7 // 蛇的運動方向 8 this.direction=direction||"right"; 9 // 蛇的每一部分, 第一部分是蛇頭 10 this.body=[ 11 {x:3,y:1}, 12 {x:2,y:1}, 13 {x:1,y:1} 14 ]; 15 }
⑵ 方法:render() 把蛇渲染到map上
1 // render方法,原理與渲染食物相同 2 Snake.prototype.render=function (map) { 3 remove(map); 4 for (var i = 0; i < this.body.length; i++) { 5 var newDiv=document.createElement("div"); 6 newDiv.style.position="absolute"; 7 newDiv.style.left=this.body[i].x*this.width+"px"; 8 newDiv.style.top=this.body[i].y*this.height+"px"; 9 newDiv.style.width=this.width+"px"; 10 newDiv.style.height=this.height+"px"; 11 newDiv.style.backgroundColor=this.bgColor; 12 map.appendChild(newDiv); 13 list.push(newDiv); 14 } 15 }
1.5 創建遊戲對象Game(用來管理遊戲中的所有對象和開始遊戲)
⑴ 屬性:food、snake、map
1 // Game構造函數 2 function Game(map) { 3 this.map=map; 4 this.snake=new Snake(); 5 this.food=new Food(); 6 that=this; 7 }
⑵ 方法:start() 開始遊戲(繪制所有遊戲對象)
1 // 開始遊戲,渲染食物對象和蛇對象 2 Game.prototype.startGame=function () { 3 this.food.render(this.map); 4 this.snake.render(this.map); 5 autoMove(); 6 keyBind(); 7 }
// 在自調用函數中暴露Game對象 window.Game=Game;
2.遊戲邏輯
2.1 蛇的move方法
⑴ 在蛇對象(snake.js)中,在Snake的原型上新增move方法
⑵ 讓蛇移動起來,把蛇身體的每一部分往前移動一下
⑶ 蛇頭部分根據不同的方向決定 往哪裏移動
1 Snake.prototype.move=function (food,map) { 2 // 讓蛇身體的每一部分往前移動一下 3 for (var i = this.body.length-1; i >0; i-- ){ 4 this.body[i].x=this.body[i-1].x; 5 this.body[i].y=this.body[i-1].y; 6 } 7 // 根據移動的方向,決定蛇頭如何處理 8 switch (this.direction) { 9 case "left": 10 this.body[0].x--; 11 break; 12 case "right": 13 this.body[0].x++; 14 break; 15 case "up": 16 this.body[0].y--; 17 break; 18 case "down": 19 this.body[0].y++; 20 break; 21 default: 22 break; 23 } 24 }
//在game中測試 this.snake.move(this.food, this.map); this.snake.render(this.map);
2.2 讓蛇自己動起來
⑴ 在game.js中 添加autoMove的私有方法,開啟定時器調用蛇的move和render方法,讓蛇動起來(私有方法即不能被外部訪問的方法,使用自調用函數包裹)
1 function autoMove() { 2 var timeId=setInterval(function () { 3 this.snake.move(this.food,this.map); 4 this.snake.render(this.map); 5 // 判斷蛇是否撞墻 6 var snakeHeadX=this.snake.body[0].x*this.snake.width; 7 var snakeHeadY=this.snake.body[0].y*this.snake.width; 8 if(snakeHeadX<0 || snakeHeadY<0 || snakeHeadX>=this.map.offsetWidth || snakeHeadY>=this.map.offsetHeight){ 9 clearInterval(timeId); 10 alert("Game over!"); 11 } 12 }.bind(that),50); 13 }
⑵ 在snake中添加刪除蛇的私有方法,在render中調用
1 function remove(map) { 2 for (var i = 0; i < list.length; i++) { 3 map.removeChild(list[i]); 4 } 5 list.length=0; 6 }
⑶ 在game中通過鍵盤控制蛇的移動方向
1 function keyBind() { 2 window.onkeydown=function (e) { 3 e=e||window.event; 4 e.keyCode= e.keyCode|| e.charCode|| e.which; 5 // console.log(e.keyCode); 6 switch (e.keyCode) { 7 case 37: 8 if(this.snake.direction!="right"){ 9 this.snake.direction="left"; 10 } 11 break; 12 case 38: 13 if (this.snake.direction != "down") { 14 this.snake.direction = "up"; 15 } 16 break; 17 case 39: 18 if (this.snake.direction != "left") { 19 this.snake.direction = "right"; 20 } 21 break; 22 case 40: 23 if (this.snake.direction != "up") { 24 this.snake.direction = "down"; 25 } 26 break; 27 default: 28 break; 29 } 30 }.bind(that); 31 }
⑷ 在start方法中調用keyBind()
2.3 判斷蛇是否吃到食物
在Snake的move方法中添加判斷
1 // 在移動的過程中判斷蛇是否吃到食物 2 var snakeHeadX=this.body[0].x*this.width; 3 var snakeHeadY=this.body[0].y*this.height; 4 var snakeTile=this.body[this.body.length-1]; 5 // 如果蛇頭和食物的位置重合代表吃到食物 6 if(snakeHeadX==food.x && snakeHeadY==food.y){ 7 // 吃到食物,往蛇節的最後加一節 8 this.body.push({ 9 // 食物的坐標是像素,蛇的坐標是幾個寬度,進行轉換 10 x:snakeTile.x, 11 y:snakeTile.y 12 }); 13 // 把現在的食物對象刪除,並重新隨機渲染一個食物對象 14 food.render(map); 15 }
★ ★ 自調用函數的參數
1 (function (window, undefined) { 2 var document = window.document; 3 }(window, undefined))
⑴ 傳入window對象:代碼壓縮的時候,可以把function (window) 壓縮成 function (w)
⑵ 傳入undefined:把undefined作為函數的參數(當前案例沒有使用) ,防止undefined 被重新賦值,因為在有的老版本的瀏覽器中 undefined可以被重新賦值
★ ★ 關於自調用函數的問題
⑴ 如果存在多個自調用函數要用分號分割,否則語法錯誤
1 // 下面代碼會報錯 2 (function () { 3 }()) 4 5 (function () { 6 }()) 7 // 所以代碼規範中會建議在自調用函數之前加上分號 8 // 下面代碼沒有問題 9 ;(function () { 10 }()) 11 12 ;(function () { 13 }())
⑵ 當自調用函數前面有函數聲明時,會把自調用函數作為參數
1 // 所以建議自調用函數前,加上; 2 var a = function () { 3 alert(‘11‘); 4 } 5 6 (function () { 7 alert(‘22‘); 8 }())
面向對象案例——貪吃蛇遊戲