您好,本文主要描述如何用原生JS以面向過程的思想來編寫一個貪吃蛇小游戲。
1. 準備工作
首先,我們需要新建一個文件夾,文件夾里創建3個文件,分別是 snake.html
來存放html代碼,snake.css
存放css樣式表代碼,snake.js
存放js代碼。
1.1 編寫snake.html
首先我們在body標簽里面建立一個地圖div,在地圖模型中嵌入蛇模型(包括蛇頭與蛇身),并賦予蛇頭id名方便我們后面JS操控蛇頭div,而一條蛇有很多個蛇身模型div,所以我們給所有蛇身賦予相同的class名,最后還有食物模型div也是內嵌在地圖里的。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>貪吃蛇</title>
<!-- 引入snake.css文件 -->
<link rel="stylesheet" type="text/css" href="snake.css"/>
</head>
<body>
<!-- 地圖 -->
<div id="wrap">
<!-- 蛇 -->
<div id="head"></div>
<div class="body" style="left:25px;top:0;">1</div>
<div class="body" style="left:50px;top:0;">2</div>
<!-- 食物 -->
<div class="food"></div>
</div>
<!-- 引入snake.js文件 -->
<script src="snake.js" type="text/javascript"></script>
</body>
</html>
1.2 編寫snake.css
現在我們在瀏覽器中打開snake.html
文件是一片空白的,因為還沒有給各元素賦予樣式。那么事不宜遲,趕緊開始css樣式表的編寫吧!首先,給各元素定位設置,如地圖設置相對定位relative,蛇與食物設置絕對定位absolute。由于邊幅有限,這里我只設置比較簡單的樣式,各位有興趣的話可以給各個元素賦予背景圖片,讓游戲更真實(笑。
#wrap{
width: 500px;
height: 500px;
border: 1px solid #000;
position: relative;
margin: auto
}
#head{
width: 25px;
height: 25px;
background: red;
position: absolute;
top: 0;
left: 50px;
z-index: 1;
}
.body{
width: 25px;
height: 25px;
position: absolute;
background: forestgreen;
}
.food{
width: 25px;
height: 25px;
background: yellow;
position: absolute;
}
2. 編寫snake.js
經過一番準備后,我們現在開始進行js的編寫。
2.1 獲取所有元素
我們先要獲取元素,才能操作元素。
//以id獲取地圖元素
var wrap = document.getElementById('wrap');
//以id獲取蛇頭元素
var head = document.getElementById('head');
//以class名獲取所有蛇身元素(是一個數組)
var body = document.getElementsByClassName('body');
//以class名獲取食物元素(只有一個)
var food = document.getElementsByClassName('food')[0];
2.2 設置所需變量
var box = 25;//蛇的移動步長
var boombol = true;//用于判斷是否吃到食物
var timer = null;//讓蛇不斷移動的定時器
var runbol = true;//判斷短時間內的多次按鍵
// 原始方向為向右
var l = 1;
var t = 0;
2.3 綁定鍵盤事件
利用鍵盤方向鍵改變蛇的移動方向,通過檢測keyCode的值來確定按鍵,如 37代表 ←
、38代表 ↑
、39代表→
、40代表↓
。
需要達到的要求如下:
1??蛇在向右走的時候,按左方向鍵或按右方向鍵時依舊往右走,其他三個按鍵同理
2??多次快速按鍵無效
document.onkeyup = function(e){
var ee = e||window.event;
// 當移動函數沒有執行完畢時按下鍵盤直接返回,使按鍵無效
if(!runbol){
return;
}
// 把 l改變為負值 移動時相乘就會為負則向左走
// 同時 t要置為0 不然會斜著走
// 其他按鍵也是同理
switch(e.keyCode){
case 37:{
// 把移動布爾值置否
runbol = false;
// 如果蛇正在往右走時按下左鍵 break接著無效
if(l == 1){break;}
l = -1;t = 0;break;
}
case 38:{
runbol = false;
if(t == 1){break;}
l = 0;t = -1;break;
}
case 39:{
runbol = false;
if(l == -1){break;}
l = 1;t = 0;break;
}
case 40:{
runbol = false;
if(t == -1){break;}
l = 0;t = 1;break;
}
}
}
2.4 設置蛇的移動函數
蛇移動有兩種情況:
1??蛇吃到食物
2??蛇沒有吃到食物
游戲結束判定有兩種情況:
1??蛇頭撞到墻壁
2??蛇頭撞到自己的身體
3??整條蛇的長度等于地圖大小的通關慶祝??
function move(){
// 獲取頭部的左和上偏移量
var headX = head.offsetLeft;
var headY = head.offsetTop;
// 直接寫蛇頭下一個移動位置 用于判斷撞墻
var nextx = headX + box * l;
var nexty = headY + box * t;
// 判斷撞墻
if(nextx<0||nextx>475 ||nexty<0||nexty>475){
gameOver();
}
// 判斷撞到蛇身 利用循環遍歷每一個body
// 并判斷當頭的下一次移動位置為其中一個重合時即跟撞墻一樣結束游戲
for(var i = 0;i<body.length;++i){
if(nextx==body[i].offsetLeft && nexty==body[i].offsetTop){
gameOver();
}
}
// 經過判斷后蛇才移動
head.style.left = nextx + 'px';
head.style.top = nexty + 'px';
// 如果吃到食物 創造一個蛇身 位置為頭部的偏移量
// 數組中的位置為蛇頭之后
// 最后把布爾值恢復
if(!boombol){
var div = document.createElement('div');
div.className = 'body';
div.style.left = headX + 'px';
div.style.top = headY + 'px';
wrap.insertBefore(div,body[0]);
boombol = true;
}
// 沒有吃到食物
// 移動最后一個蛇身div為頭部偏移量 并在數組中把它插入到頭部后面
else{
body[body.length-1].style.left = headX + 'px';
body[body.length-1].style.top = headY + 'px';
wrap.insertBefore(body[body.length-1],body[0]);
}
// 判斷是否吃到食物
if(head.offsetLeft==food.offsetLeft&&head.offsetTop==food.offsetTop){
// 吃到就改變食物的位置
creatFood();
// 吃到則把布爾值置為否,在下次執行移動函數進入if語句
boombol = false;
}
// 移動完應該把布爾值置為真
// 表示移動函數執行完畢 可以接受下一個按鍵命令(第一次寫錯)
runbol = true;
}
function gameOver(){
// 撞到蛇身或墻壁 清除定時器 并彈窗 return
clearInterval(timer);
alert('Game Over');
return;
}
2.5 設置隨機食物位置函數
需要達到的要求如下:
1??隨機位置不能在蛇的身上
故遍歷蛇數組 如食物出現在蛇身上 則再調用一次創造食物函數
則產生函數遞歸 利用布爾值決定是否移動食物 出口為break
里面移動了一次 外面的就不移動
2??隨機位置不能越出地圖
因為食物本來就有大小,如果最大值設置500則食物有可能會出現在525,超出了外框,所以位置隨機數最大為地圖大小減去食物大小
function creatFood(){
var bol = true;
var x = rnd(475,0);
var y = rnd(475,0);
// parseInt(x/25)拿到在隨機數相對于25的位數,保證食物在格子上
var foodx = parseInt(x/25)*25;
var foody = parseInt(y/25)*25;
for(var i =0;i<snake.length-1;++i){
//循環判斷食物是否出現在蛇身
if(foodx==snake[i].offsetLeft&& foody==snake[i].offsetTop){
creatFood();
bol = false;
break;
}
}
if(bol){
food.style.left = foodx + 'px';
food.style.top = foody + 'px';
}
}
// 隨機函數
function rnd(max,min){
return Math.round(Math.random()*(max-min)+min);
}
2.6 最后
1??調用 creatFood()函數
2??啟動定時器,定時器內調用函數為蛇移動函數move(),定時器時間間隔設置為200毫秒**
creatFood();
timer = setInterval(move,200);
3 總結
面向過程式的編程如果要加功能或者修改,步驟之間的循序可能都要進行大規模調整,如本例中要加入其他食物,使蛇吃到食物后產生不同的效果(加速,蛇身加兩節等)就比較難做到了。
所以我們此時需要學習面向對象編程,因為其結構更加清晰,更容易擴展與良好的封裝性。
當我們都兩種編程思想都學會了,在面對需求時就可以根據他們的特點選擇更好的解決方案啦。(其實還有其他n個編程思想)
最后,感謝您的閱讀!