大致介紹
看了一個實現網頁版2048小游戲的視頻,覺得能做出自己以前喜歡玩的小游戲很有意思便自己動手試了試,真正的驗證了這句話-不要以為你以為的就是你以為的,看視頻時覺得看懂了,會寫了,但是自己實現起來會遇到各種問題。比如,在最后判斷游戲是否結束的時候,我寫的語句語法是對的,但就是不執行。最后通過對視頻源碼的分析對比,發現原作者寫的一個setTimeout定時器有額外的意思,本來我以為它就是簡單的一個延時動畫,其實他是在等待另外一個函數執行完畢。-_-||。最后還是很高興能寫出來,也改進了一些源代碼的不足。
這篇博客并不是詳細的講解,只是大致介紹函數的作用,其中實現的細節注釋中有解釋,網上的這個源碼有點亂,如果想看比較整齊的源碼或者視頻的可以QQ聯系我(免費)(找共同學習的伙伴)
思路
這個小游戲可以抽象化分為3層(我覺得這樣能更好理解)
◆最底下的一層是基本的樣式(可見的)
◆中間的層是最主要的,是一個4x4的二維數組,游戲中我們都是對這個二維數組進行操作(不可見的)
◆最上面的一層也是一個4x4的二維數組,它只是根據第二層數組的每個數顯示樣式(可見的)
我們通過最底下的一層顯示最基本的16個小方格,通過鍵盤的按鍵或者手指在屏幕的滑動來操作中間層的數組,最后在通過最上面的一層顯示出數字
基本結構與樣式
基本的結構和樣式都挺簡單,直接看代碼
結構:
<div id="test2048"> <div id="header"> <h1>2048</h1> <a href="javascript:newgame()" >開始新的游戲</a> <p>分數:<span id="score">0</span></p> </div> <div id="container"> <div class="cell" id="cell-0-0"></div> <div class="cell" id="cell-0-1"></div> <div class="cell" id="cell-0-2"></div> <div class="cell" id="cell-0-3"></div> <div class="cell" id="cell-1-0"></div> <div class="cell" id="cell-1-1"></div> <div class="cell" id="cell-1-2"></div> <div class="cell" id="cell-1-3"></div> <div class="cell" id="cell-2-0"></div> <div class="cell" id="cell-2-1"></div> <div class="cell" id="cell-2-2"></div> <div class="cell" id="cell-2-3"></div> <div class="cell" id="cell-3-0"></div> <div class="cell" id="cell-3-1"></div> <div class="cell" id="cell-3-2"></div> <div class="cell" id="cell-3-3"></div> </div> </div>
樣式:
*{ margin: 0; padding: 0;}#test2048{ font-family: Arial; margin: 0 auto; text-align: center;}#header{ margin: 20px;}#header a{ font-family: Arial; text-decoration: none; display: block; color: white; margin: 20px auto; width: 125px; height: 35px; text-align: center; line-height: 40px; background-color: #8f7a66; border-radius: 10px; font-size: 15px;}#header p{ font-family: Arial; font-size: 20px;}#container{ width: 460px; height: 460px; background-color: #bbada0; margin: 0 auto; border-radius: 10px; position: relative; padding: 20px;}.cell{ width: 100px; height: 100px; border-radius: 6px; background-color: #ccc0b3; position: absolute;}從CSS樣式可以看出,我們并沒有對每個格子的位置進行設置,因為如果用CSS給每個格子設置樣式代碼量太大,而且他們的位置有一定的規律,所以我們可以用js循環來完成每個格子樣式的設置
代碼:
// 初始化棋盤格function initialize(){ for(var i=0;i<4;i++){ for(var j=0;j<4;j++){ // 設置棋盤格的位置 var everyCell = $('#cell-'+ i +'-'+ j); everyCell.css({top:getPos(i),left:getPos(j)}); } }}// 獲取位置function getPos(num){ return 20 + num*120;}這樣我們的第一層就好了
效果:

現在構造第二層,即構建一個4x4的值全部為0的數組,由于在構造第二層時,有兩層循環,所以我們可以在構造第一層時也能構造第二層
第三層是用js生成16個格子,它和第一層的16個格子一一對應
代碼:
// 數字格function numFormat(){ for(var i=0;i<4;i++){ for(var j=0;j<4;j++){ $('#container').append('<div class="number" id="number-'+ i +'-'+ j +'"></div>') // 設置數字格的位置,樣式 var everyNumber = $('#number-'+ i +'-'+ j); if(checkerboard[i][j] == 0){ everyNumber.css({ width:'0px', height:'opx', top:getPos(i) + 50, left:getPos(j) + 50 }) }else{ everyNumber.css({ width:'100px', height:'100px', top:getPos(i), left:getPos(j),backgroundColor:getBackgroundColor(checkerboard[i][j]), color:getColor(checkerboard[i][j]) }); everyNumber.text(checkerboard[i][j]); } } }}// 獲取相應數字的背景顏色function getBackgroundColor(number){ switch (number) { case 2:return "#eee4da";break; case 4:return "#ede0c8";break; case 8:return "#f2b179";break; case 16:return "#f59563";break; case 32:return "#f67c5f";break; case 64:return "#f65e3b";break; case 128:return "#edcf72";break; case 256:return "#edcc61";break; case 512:return "#9c0";break; case 1024:return "#33b5e5";break; case 2048:return "#09c";break; case 4096:return "#a6c";break; case 8192:return "#93c";break; }}// 設置相應數字的文字顏色function getColor(number){ if (number <= 4) { return "#776e65" } return "white";}初始化
在每次游戲重新開始時,都會在隨機的位置出現兩個隨機的數字,我們寫一個在隨機位置出現一個隨機數的函數,只要調用兩次就可以實現了
代碼:
// 隨機的在一個位置上產生一個數字function randomNum(){ // 隨機產生一個坐標值 var randomX = Math.floor(Math.random() * 4); var randomY = Math.floor(Math.random() * 4); // 隨機產生一個數字(2或4) var randomValue = Math.random() > 0.5 ? 2 : 4; // 在數字格不為0的地方生成一個隨機數字 while(true){ if(checkerboard[randomX][randomY] == 0){ break; }else{ var randomX = Math.floor(Math.random() * 4); var randomY = Math.floor(Math.random() * 4); } } // 將隨機產生的數字顯示在隨機的位置上 checkerboard[randomX][randomY] = randomValue; // 動畫 randomNumAnimate(randomX,randomY,randomValue);}// 隨機產生數字的動畫function randomNumAnimate(randomX,randomY,randomValue){ var randomnum = $('#number-'+ randomX +'-'+ randomY); randomnum.css({ backgroundColor:getBackgroundColor(randomValue), color:getColor(randomValue), }) .text(randomValue) .animate({ width:'100px', height:'100px', top:getPos(randomX), left:getPos(randomY) },50);}基本操作
我們通過switch循環,來根據用戶不同的輸入進行不同的操作
代碼:
// 獲取鍵盤事件,檢測不同的按鍵進行不同的操作$(document).keydown(function(event){ switch(event.keyCode){ case 37://左 if(canMoveLeft(checkerboard)){ // 如果可以向左移動 MoveLeft(); // 向左移動 setTimeout(function(){ randomNum(); },200); // 隨機產生一個數字 } break; case 38://上 if(canMoveUp(checkerboard)){ // 如果可以向上移動 MoveUp(); // 向上移動 setTimeout(function(){ randomNum(); },200); // 隨機產生一個數字 } break; case 39://右 if(canMoveRight(checkerboard)){ // 如果可以向右移動 MoveRight(); // 向右移動 setTimeout(function(){ randomNum(); },200); // 隨機產生一個數字 } break; case 40://下 if(canMoveDown(checkerboard)){ // 如果可以向下移動 MoveDown(); // 向下移動 setTimeout(function(){ randomNum(); },200); // 隨機產生一個數字 } break; default: break; }});由于數字格的移動只有左、上、右、下四種方式,并且他們都是大同小異的,所以就拿向左移動為例,
向左移動,我們首先需要判斷它是否能向左移動,能向左移動有兩種情況
第一種:當前格子的左邊的格子是空的即值為0
第二種:當前格子的值和左邊格子的值相同
由于向左移動,所以第一列的格子不可能向左移動,所以不需要判斷
代碼:
// 判斷是否可以向左移動function canMoveLeft(checkerboard){ for(var i=0;i<4;i++){ for(var j=1;j<4;j++){ if(checkerboard[i][j] != 0){ // 如果這個數字格它左邊的數字格為空或者左邊的數字格和它相等,則可以向左移動 if(checkerboard[i][j-1] == 0 || checkerboard[i][j] == checkerboard[i][j-1]){ return true; } } } } return false;}判斷能否向左移動后,我們就要對可以移動的格子進行移動,這里需要特別注意,向哪個方向移動就要先從哪個方向開始判斷
代碼:
// 向左移動function MoveLeft(){ for(var i=0;i<4;i++){ for(var j=1;j<4;j++){ if(checkerboard[i][j] != 0){ for(var k=0;k<j;k++){ if(checkerboard[i][k] == 0 && noMiddleNumRow(i,k,j,checkerboard)){ moveAnimation(i,j,i,k); checkerboard[i][k] = checkerboard[i][j]; checkerboard[i][j] = 0; }else if(checkerboard[i][k] == checkerboard[i][j] && noMiddleNumRow(i,k,j,checkerboard) && !hasConflicted[i][k]){ moveAnimation(i,j,i,k); checkerboard[i][k] += checkerboard[i][j]; checkerboard[i][j] = 0; } } } } } // 設置刷新的時間是為了讓運動的動畫走完在進行更新數字格,否則數字格運動的動畫將會被打斷 setTimeout(function(){ numFormat(); },200);}// 判斷中間的數字格是否為0(行)function noMiddleNumRow(row,col1,col2,checkerboard){ for(var i=col1+1;i<col2;i++){ if(checkerboard[row][i] != 0){ return false; } } return true;}將上、右、下四個方向寫完以后,游戲基本的操作就已經完成了。
游戲分數和判斷游戲結束
游戲的分數是每個相加的數的和,所以我們在每個數相加的時候更新分數就可以了
代碼:
// 更新分數score += checkerboard[k][j];updateScore(score);
// 設置分數function updateScore(num){ $('#score').text(num);}判斷游戲是否結束很簡單,用我們之前定義的方法就可以實現
代碼:
// 判斷游戲是否結束function wheGameOver(checkerboard){ if(!canMoveLeft(checkerboard) && !canMoveUp(checkerboard) && !canMoveRight(checkerboard) && !canMoveDown(checkerboard) ){ showGameOver(); }}// 顯示游戲結束function showGameOver(){ $('#container').append("<div id='gameover'><p>最終得分</p><span>"+ score +"</span><a href='javascript:resert();'>重新開始游戲</a></div> ")}// 重新開始游戲function resert(){ $('#gameover').remove(); newgame();}最后優化
1、游戲中會出現一次移動,一個數會被累加很多次
在原游戲中,每個數在每次操作中只能累加一次,所以我們在定義一個4x4的值為false的數組,與中間層的數組一一對應,專門用來防止一個數的多次累加,如果是false則可以累加,并將值改為false,否則不可以累加
2、結束死循環
由于在設置隨機數的時候用到了一個死循環,但是在游戲結束后,該循環還在,所以我們在死循環中在添加一個條件,如果游戲結束就跳出循環
3、最后的結束游戲提示不執行
case 37://左 if(canMoveLeft(checkerboard)){ // 如果可以向左移動 MoveLeft(); // 向左移動 setTimeout(function(){ wheGameOver(checkerboard) },300); // 判斷游戲是否結束,這里設置延時是因為要等到隨機產生數字后再進行判斷,如果不加 // 延時,則最后一次的判斷因為canMoveLeft(checkerboard)為false就不會再執行了 setTimeout(function(){ randomNum(); },200); // 隨機產生一個數字 } break;從代碼中可以看出,判斷游戲是否結束是在隨機產生一個數字前執行的,所以在判斷游戲結束時,總是有一個空的格子,所以代碼執行后認為游戲沒有結束,但是當這個隨機數字產生后,所有的格子不能移動,當我們按鍵時,if條件不通過,判斷游戲是否結束的函數不能執行。所以我們要給判斷游戲結束的函數設置定時器,讓他在隨機產生一個數字后再進行判斷
4、在移動端可以執行
由于原作者沒有寫有關移動端的操作,所以我在網上找的判斷移動端觸屏手機滑動位置的代碼,加入了游戲的事件就可以執行了
//返回角度 function GetSlideAngle(dx, dy) { return Math.atan2(dy, dx) * 180 / Math.PI; } //根據起點和終點返回方向 1:向上,2:向下,3:向左,4:向右,0:未滑動 function GetSlideDirection(startX, startY, endX, endY) { var dy = startY - endY; var dx = endX - startX; varresult = 0; //如果滑動距離太短 if(Math.abs(dx) < 2 && Math.abs(dy) < 2) { returnresult; } var angle = GetSlideAngle(dx, dy); if(angle >= -45 && angle < 45) { result = 4; }else if (angle >= 45 && angle < 135) { result = 1; }else if (angle >= -135 && angle < -45) { result = 2; } else if ((angle >= 135 && angle <= 180) || (angle >= -180 && angle < -135)) { result = 3; } return result; } //滑動處理 var startX, startY; document.addEventListener('touchstart',function (ev) { startX = ev.touches[0].pageX; startY = ev.touches[0].pageY; }, false); document.addEventListener('touchend',function (ev) { var endX, endY; endX = ev.changedTouches[0].pageX; endY = ev.changedTouches[0].pageY; var direction = GetSlideDirection(startX, startY, endX, endY); switch(direction) { case 0: //沒滑動 break; case 1: if(canMoveUp(checkerboard)){ // 如果可以向上移動 MoveUp(); // 向上移動 setTimeout(function(){ wheGameOver(checkerboard) },300); // 判斷游戲是否結束 setTimeout(function(){ randomNum(); },200); // 隨機產生一個數字 } break; case 2: if(canMoveDown(checkerboard)){ // 如果可以向下移動 MoveDown(); // 向下移動 setTimeout(function(){ wheGameOver(checkerboard) },300); // 判斷游戲是否結束 setTimeout(function(){ randomNum(); },200); // 隨機產生一個數字 } break; case 3: if(canMoveLeft(checkerboard)){ // 如果可以向左移動 MoveLeft(); // 向左移動 setTimeout(function(){ wheGameOver(checkerboard) },300); // 判斷游戲是否結束,這里設置延時是因為要等到隨機產生數字后再進行判斷,如果不加 // 延時,則最后一次的判斷因為canMoveLeft(checkerboard)為false就不會再執行了 setTimeout(function(){ randomNum(); },200); // 隨機產生一個數字 } break; case 4: if(canMoveRight(checkerboard)){ // 如果可以向右移動 MoveRight(); // 向右移動 setTimeout(function(){ wheGameOver(checkerboard) },300); // 判斷游戲是否結束 setTimeout(function(){ randomNum(); },200); // 隨機產生一個數字 } break; default: } }, false);總結
總體來說這個游戲實現起來并不是太難,就是許多小的操作集合起來
以上就是本文的全部內容,希望本文的內容對大家的學習或者工作能帶來一定的幫助,同時也希望多多支持武林網!
新聞熱點
疑難解答