首先要做一個js的貪喫蛇遊戲我們第一步就是要用面向對象的方法來思考問題
面向對象
貪喫蛇遊戲大家衆所周知,我們來抽象一下貪喫蛇的對象。
首先蛇本身可以單獨看爲一個對象,食物是隨機出現在屏幕上的,不管是最原始的只出現一個食物還是更多拓展版的食物的出現一定是按照某些規則的。所以食物在這裏也是一個對象。
首先食物對象是不與其他對象相關的,我們先來確定食物對象的js。
建立food.js
/* 食物對象需要的屬性,位置x,y,大小,顏色 */
function Food(options){
options=options||{};
this.x=options.x||0;
this.y=options.y||0;
this.width=options.width||20;
this.height=options.height||20;
this.color=options.color||'green';
}
var position="absolute";
/* Food的原型對象
render方法
動態創建div */
Food.prototype.render =function(map){
/* 創建div */
var div = document.createElement('div');
map.appendChild(div);
/* 設置div的樣式 */
div.style.position=position;
div.style.left=this.x+'px';
div.style.top=this.y+'px';
div.style.width=this.width+'px';
div.style.height=this.height+'px';
div.style.backgroundColor=this.color;
}
/* test */
var map=document.getElementById('map');
var food=new Food();
food.render(map);
完善一下index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title></title>
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<div id="map"></div>
<script src="js/food.js"></script>
</body>
</html>
這裏要注意,引入food.js要在div聲明之後,否則可能會出現以下或者不限於以下的問題。
報錯:Uncaught TypeError: Cannot read property ‘appendChild’ of undefined
這個問題出現的原因就是在js加載之前並沒有找到id爲map的div模塊。所以要謹記把js的加載都放在div聲明的後面。
建立一個css
#map {
width: 800px;
height: 600px;
background-color: red;
position: relative;/* 相對 */
}
ok,如果沒什麼問題Google瀏覽器上已經可以顯示出紅配綠了
就是這麼辣眼睛。food 還沒解決完,如何讓food 隨機出現?
隨機設置food位置
生成最大值到最小值之間的隨機數
var Tools = {
getRandom: function (min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
}
ok剛剛我們的x和y的位置都是默認的,所以設置一下x和y但是要在div中(隨機)。
考慮隨機數的邊界問題: 實質上我們是隨機座標,隨機座標0時是可以在map上放置food的,這個比較容易思考。 上邊界max呢,首先map是有自己的寬度和高度的 我們拿寬度先來找x(高度和y同理) 在food.js中定義了food的寬度20px
在css中定義了map的寬度800px 後者比前者即爲map中能橫向放置多少塊food 明眼人可知,可放40塊
但是max可以等於40嗎? 答案是否定的,因爲等於40時,因爲我們是求的座標,要展開food,實際上已經不屬於map的範圍中了。
故max=40-1=39
由此我們得到了新的x和y座標的表示方法:
this.x=Tools.getRandom(0,map.offsetWidth/this.width-1)*this.width;
this.y=Tools.getRandom(0,map.offsetHeight/this.height-1)*this.height;
已經可以隨機顯示了,還有一個重要的遊戲規則:喫掉食物。
所以喫掉之後要隨機顯示,之前就有一步刪除食物的步驟
刪除食物
var elements=[];//存儲食物的元素
隨機生成方法中:
var div = document.createElement('div');
map.appendChild(div);
/* 記錄到數組中 */
elements.push(div);
function remove(){
for(var i=elements.length-1;i>=0;i--){
/* 刪除div 調用父元素的removeChild方法刪除*/
elements[i].parentsNode.removeChild(elements[i]);
/* 刪除數組中的數據
第一個參數從哪個元素開始刪除
第二個參數刪除幾個*/
elements.splice(i,1);
}
}
自調用函數改進
首先自調用函數的目的是爲了創建局部作用域,使得後續不會出現重名調用等問題
(function(){
})()
將之前寫的代碼放入function中即可
此處注意一個小問題
Food方法在function 的局部作用域內,所以外部調用不到
/* window下的函數全局可訪問,創建自調用函數之後,局部作用域使得外部的測試代碼無法調用food方法 */
window.Food=Food;
關於食物對象的面向對象基本結束
構建蛇對象
蛇對象的渲染思路與食物對象同理,需規定蛇對象的方向和身體,身體默認用數組存儲,每一個元素代表蛇身體的一個蛇節。
(function(){
var position ='absolute';
function Snake(options){
options=options||{};
/* 蛇節大小和行進方向 */
this.width =options.width||20;
this.height=options.height||20;
this.derection=options.derection||'right';
/* 蛇身體 */
this.body=[
{x:3,y:2,color:'red'},
{x:2,y:2,color:'blue'},
{x:1,y:2,color:'blue'}
];
}
Snake.prototype.render=function(){
for(var i=0,len =this.body.length;i<len;i++){
var object =this.body[i];
var div=document.createElement('div');
map.appendChild(div);
/* 設置postion的目的是脫離文檔流 在上面新建一個var postion*/
div.style.position=position;
/* 設置大小 */
div.style.width=this.width+'px';
div.style.height=this.height+'px';
/* 設置座標,顏色 */
div.style.left=object.x*this.width+'px';
div.style.top=object.y*this.height+'px';
div.style.backgroundColor=object.color;
}
}
window.Snake=Snake;
})()
啓動js
之前的測試都是在js後加一段測試代碼,把啓動單獨創建一個js來代替測試代碼
(function(){
function Game(map){
/* 依賴於food和snake */
this.food=new Food();
this.snake=new Snake();
this.map=map;
}
Game.prototype.start=function(){
/* 把蛇和食物對象渲染到地圖
開始遊戲的邏輯 */
this.food.render(this.map);
this.snake.render(this.map);
}
window.Game=Game;
})();
var map=document.getElementById('map');
var game =new Game(map);
game.start();
game.js 的作用是代替之前的測試代碼,所以它首先要依賴於前兩個食物和蛇的js,再按照其render方法進行渲染即可。效果與之前相同,不附圖。
控制蛇的移動
Snake.prototype.move =function(){
/* 控制蛇的身體移動 */
/* 蛇節 */
for(var i=this.body.length-1;i>0;i--){
this.body[i].x=this.body[i-1].x;
this.body[i].y=this.body[i-1].y;
}
/* 蛇頭 */
var head =this.body[0];
switch(this.direction){
case'right':
head.x+=1;
break;
case'left':
head.x-=1;
break;
case'top':
head.y-=1;
break;
case'bottom':
head.y+=1;
break;
}
}
蛇在移動過程中,需要刪除蛇移動前的蛇節。
同理food:
/* 私有成員函數 刪除參照food*/
function remove(){
/* 從後往前刪除 */
for(var i=elements.length-1;i>=0;i--){
/* 刪除div */
elements[i].parentNode.removeChild(elements[i]);
elements.splice(i,1);
}
}
讓蛇持續移動
// 私有的函數 讓蛇移動
function runSnake() {
var timerId = setInterval(function () {
// 讓蛇走一格
// 在定時器的function中this是指向window對象的
// this.snake
// 要獲取遊戲對象中的蛇屬性
that.snake.move();
that.snake.render(that.map);
// 2.2 當蛇遇到邊界遊戲結束
// 獲取蛇頭的座標
var maxX = that.map.offsetWidth / that.snake.width;
var maxY = that.map.offsetHeight / that.snake.height;
var headX = that.snake.body[0].x;
var headY = that.snake.body[0].y;
if (headX < 0 || headX >= maxX) {
alert('Game Over');
clearInterval(timerId);
}
if (headY < 0 || headY >= maxY) {
alert('Game Over');
clearInterval(timerId);
}
}, 150);
}
這裏用到了that,在var that; that = this;// 記錄遊戲對象
前面已經定義,用於獲取this。
通過鍵盤控制蛇的移動方向
// 通過鍵盤控制蛇移動的方向
function bindKey() {
// document.onkeydown = function () {};
document.addEventListener('keydown', function (e) {
// console.log(e.keyCode);
// 37 - left
// 38 - top
// 39 - right
// 40 - bottom
switch (e.keyCode) {
case 37:
that.snake.direction = 'left';
break;
case 38:
that.snake.direction = 'top';
break;
case 39:
that.snake.direction = 'right';
break;
case 40:
that.snake.direction = 'bottom';
break;
}
}, false);
}
喫掉食物
/* 判斷蛇頭是否和食物座標重合 需要food 和map 在move方法中添加兩個參數,同時game也需要添加兩個參數*/
var headX=head.x*this.width;
var headY=head.y*this.height;
if(headX === food.x &&headY === food.y){
var last =this.body[this.body.length - 1];
this.body.push({
x:last.x,
y:last.y,
color:last.color
})
github傳送門:https://github.com/qdjiangwenhao/snake