bom实现方块移动_从0开始实现一个俄罗斯方块
寫在前面得話:
這篇文章主要記錄了我是怎么一步一步寫出俄羅斯方塊,整個代碼用的函數編程,主要是為了讓一些不熟悉es6, 面向對象寫法得 新手能更容易看明白,全部得代碼中都是一些js的基礎知識,很容易理解。要說有點麻煩的,那就是游戲過程中的各種檢測。但是只要你多思考,你就能理解代碼為什么要那樣寫,你也可以實現這個游戲。(當然也許你有更好的實現方法)。
預覽地址:
俄羅斯方塊?blog.cwlserver.top1,先理清游戲邏輯
- 游戲場景:場景大小為 10*18,
- 下落時間:初始方塊每隔1秒,會下落一格。隨著游戲進行時間得增加,方塊下落時間間隔會縮短。
- 操作方法:方向鍵得 上下左右 分別控制方塊得, 變形,加速下落,左移,右移。
- 方塊類型:一共7種類型得方塊。每次隨機出現一種, 每種方塊由數個 1*1大小得小方塊組成
- 方塊下落:當方塊落到底, 或者下一格已經被占,方塊停止下落,然后會有一個新的方塊出現
- 方塊左右移動:方塊左右移動時,如果左,右是墻或者是已經被占,方塊將不能移動。
- 方塊變形:方塊逆時針旋轉90°,變形時需要判斷方塊是否可以變形。
- 游戲會有下一個方塊得提示
- 消行:當一行被填滿時,這一行將被消除
- 計分規則: 消1行得2分,2行4分,3行8分,4行16分
- 游戲結束: 當方塊下落到底,并且方塊超出游戲場景時,判定游戲結束
2,分步實現游戲中得功能
html結構
<div id="box"><canvas id="canvas" width="300" height="540"></canvas><div class="scorebox"><p>游戲已進行: <span id="game-time">00:00:00</span></p><br><p>當前得分: <span id="score">0</span></p><br><p>下一個方塊:</p><br><canvas id="next" width="120" height="120"></canvas><br><p class="btns"><button id="pause">暫停</button><button id="restart">重新開始</button></p></div></div>構建場景
因為場景大小是10x18,所以我決定用一個 10x18得二維數組來模擬場景,這樣方便和方塊做碰撞檢測。
//定義列數 var ROW = 10; //定義行數 var COL = 18; //游戲得分 var SCORE = 0; //游戲場景 var area = new Array(COL); for(var i=0; i<area.length; i++){area[i] = new Array(ROW).fill(0); } /* 最終得到得area是這樣得 area = [[0,0,0,0....][0,0,0,0....][0,0,0,0....]... ] */構建小方塊
小方塊我同樣使用二維數組來構建
//定義各種方塊得數組, 一共7種不同得方塊,數組中的1,2,3,4..這些數字主要是為了每個方塊設置不同的顏色 var data = {'o':[[1, 1],[1, 1]],'s':[[2, 0, 0],[2, 2, 0],[0, 2, 0]],'5':[[0, 0, 3],[0, 3, 3],[0, 3, 0]], 'l':[[4, 0, 0],[4, 0, 0],[4, 4, 0]],'t':[[5, 5, 5],[0, 5, 0],[0, 0, 0]],'j':[[0, 0, 6],[0, 0, 6],[0, 6, 6]],'|':[[0, 7, 0, 0],[0, 7, 0, 0],[0, 7, 0, 0],[0, 7, 0, 0]] }; //定義方塊得顏色,每個數字對應一種顏色 var aColor = ['', '#fff', '#0000FF', '#00FF00', '#CC00FF', '#CCFFFF','#FFFF33','#99FFFF']; //將data中得key放到一個字符串中 方便隨機調用 var sKey = 'os5ltj|'; //定義當前方塊, 當前方塊默認null; var cur = null; //因為游戲中會有下一個方塊得提示, 所以這里要提前聲明一下 var next = null; //定義一個生成方塊得函數 function createBox(){//首先創建提示方塊if(!next){//從skey中隨機取出一個鍵名var rnd = Math.floor(Math.random()*sKey.length);//根據key取得方塊數組var box = data[sKey[rnd]];//每一個方塊都有,x, y, box 這三個屬性next = {//方塊初始在場景中間位置,方塊左移 x--, 右移 x++;x: Math.floor((ROW-box[0].length)/2), //方塊在垂直方向得位置,剛好在場景外, y++ 方塊下落y: -box[0].length,//方塊得數組box: box};}//當前方塊不存在時, 創建當前方塊if(!cur){//直接下一個方塊變成當前這個cur = next;//然后再重新生成下一個next = {x: Math.floor((ROW-box[0].length)/2),y: -box[0].length,box: data[sKey[Math.floor(Math.random()*sKey.length)]]}} }現在想一個問題,有了場景和方塊的數據之后,如何把他們聯系起來?
我的處理方式是這樣的,在方塊下落的過程中,方塊和場景是分開的,方塊的位置和場景是分開刷新的。在下落的過程中我會 檢測方塊和場景是否發生碰撞,如果發生了碰撞,將當前方塊的數組合并到場景的數組中,使方塊變成場景的一部分,同時生成一個新的方塊。看下代碼如何實現
//將當前方塊合并到場景 function mergeBoxArea(){//循環當前方塊for(var i=0; i<cur.box.length; i++){//這里的判斷是為了當方塊的一部分在場景外的時候,將那一部分跳過,只計算在場景中的部分if(i+cur.y>=0){for(var j=0; j<cur.box[i].length){//將當前方塊數組中不為0的項,和 場景中當前位置為0的項合并if(cur.box[i][j] !== 0 && area[i+cur.y][j+cur.x] == 0){//合并的結果, 將場景中當前位置的值設置為方塊對應位置的值area[i+cur.y][j+cur.x] = cur.box[i][j];}}}}//將方塊合并入場景的同時要嘗試 消行var arr = isRemove(area);if(arr.length !== 0){for(var i=0; i<arr.length; i++){area.splice(arr[i], 1)area.unshift(new Array(ROW).fill(0))}//更新得分SCORE+=Math.pow(2, arr.length)scoreEle.innerHTML = SCORE;}; } //碰撞檢測 //垂直方向的碰撞檢測, 需要接受當前方塊做為參數, //作用:檢測方塊下落一格之后和場景的碰撞情況,如果會碰撞返回true,否則返回false; function collide(cur){var box = cur.box;var len = box.length;var x = box.x;//因為是檢測下一個位置,所以要+1;var y = box.y + 1;for(var i=0; i<len; i++){//做碰撞檢測同樣需要將場景外的方塊部分排除掉if(i+y>=0){//方塊的數組都是n*n的所以都用lenfor(var j=0; j<len; j++){//將方塊為0的項不檢測if(box[i][j] !== 0){//第一種碰撞情況:當i+y大于等于場景的高度時,說明方塊出界//第二種碰撞情況:方塊沒有出界,但是場景中的這個位置,被占用了if(i+y>=area.length || (i+y<area.length && area[i+y][j+x] !== 0)){//碰撞了返回 truereturn true;}}}}}//代碼執行到這里時說明沒有碰撞,返回false;return false; } //水平方向的移動限制 //當用鍵盤控制方塊左右移動的時候,需要檢測左右是否是墻,或者方塊,這里檢測的也是下一個位置的碰撞情況 //如果沒有墻或者方塊(不碰撞),返回true //如果碰撞, 返回 false; //接受參數: 當前方塊:cur, 移動方向: dir -1|0|1 function bMove(cur, dir){//當前位置加上方向 就是 下一個位置var x = cur.x+dir;for(var i=0; i<cur.box.length; i++){for(var j=0; j<cur.box[i].length; j++){if(cur[i][j] !== 0){//這里發生碰撞的情況有3中//1.方塊在左邊出界了, 這時 j+x<0//2.方塊在右邊出界了, j+x>= ROW//3.方塊沒有出界,但是場景中的這個位置被占用 area[i+cur.y][j+x]!==0// 加上 i+cur.y>=0 && j+x>=0 && area[i+cur.y] 是為了防止報錯if(j+x<0 || j+x==ROW || ( i+cur.y>=0 && j+x>=0 && area[i+cur.y] && area[i+cur.y][j+x]!==0)){return false;}}}}return true; }如何處理方塊旋轉?
方塊的旋轉比較容易處理,就把二維數組旋轉一下就可以了。但是要注意方塊旋轉的時候也是需要檢測 旋轉的合理性, 可以想象一下,一個長條下落的過程中,如果他的左右兩邊都是方塊,這種情況肯定是不能旋轉的(其它方塊同理)。還有一種情況就是,方塊靠墻下落的時候,旋轉一下之后,有一部分轉到墻里面去了,這種也是不合理的,但是玩游戲的時候,這種情況也能旋轉,所以出現這種情況的時候,我們需要修正一下方塊的位置。 下面看代碼怎么寫
//此函數用于檢測方塊是否能夠旋轉 /* 參數: 當前方塊 cur 返回值: true //方塊可以直接旋轉false //方塊不能旋轉,即使是在嘗試修正位置之后,就是上面說到的左右都是方塊的情況cur.x //當返回 一個數值的時候,說明 將方塊水平移動到這個位置后,可以旋轉, 即上面說的修正位置 */ function bRotate(cur){//在這里復制一個旋轉后的方塊出來,用于檢測var _cur = {x: cur.x, y:cur.y, box: rotateBox(cur.box)};//檢測方塊旋轉之后,水平和垂直方向的碰撞情況, 如果在任意方向會發生碰撞if( collide(_cur) === true || bMove(_cur, 0) === false ){//嘗試水平移動方塊,移動方向是分別向左,向右移動2格for(var i=0; i<2; i++){//方塊靠近左邊的時候,嘗試向右移動,并且檢測移動的合理性if(_cur.x<4 && bMove(_cur, 1)){_cur.x++;}//靠近右側的時候,向左移動,并且檢測移動的合理性if(_cur.x>6 && bMove(_cur, -1)){_cur.x--;}//移動之后再檢測是否碰撞, 如果不會發生碰撞, 返回移動后的位置if(collide(_cur) === false && bMove(_cur, 0)){return _cur.x;}}//代碼執行到這里的時候說明,移動了之后仍會碰撞return false;//如果旋轉之后不會發生碰撞,直接返回true;}else{return true;} } //旋轉數組的函數 function rotateBox(arr){var res = [];for(var i=0; i<arr.length; i++){res.push([]);}//旋轉for(var i=0; i<arr.length; i++){for(var j=0; j<arr[i].length; j++){res[arr.length-1-y][x] = arr[x][y];}}return res; }現在開始處理游戲的刷新, 計算游戲的時間
游戲用 requestAnimationFrame 更新
var timer = null; //記錄一個舊的時間,這里用于輔助計算, 每次刷新的間隔時間 var oldTime = Date.now(); //n 用于累加 raf 的間隔時間 var n = 0; //游戲運行時間 單位 毫秒 var gameTime = 0; //方塊下落的間隔時間 var step = 1000; //游戲是否暫停 var bPause = false; //獲取dom元素 //主場景canvas var canvas = document.getElementById('canvas'); var ctx = canvas.getContext('2d'); //提示下一個方塊 canvas var nextCanvas = document.getElementById('next'); var nextctx = nextCanvas.getContext('2d'); //游戲得分 var scoreEle = document.getElementById('score'); //暫停按鈕 var pauseEle = document.getElementById('pause'); //重新開始按鈕 var restartEle = document.getElementById('restart') //顯示游戲時間 var gameTimeEle = document.getElementById('game-time'); //開啟主循環 timer = requestAnimationFrame(animate); //主循環函數 function animate(){//累加 raf 的間隔時間n+=Date.now()-oldTime;//累加游戲運行時間gameTime+=Date.now()-oldTime;oldTime = Date.now();//方塊要開始下落了if(n>=step){n = 0;//每秒鐘更新一次游戲時間updateGameTime()//根據游戲進行時間提高游戲難度changeDifficulty();//方塊下落之前要先檢測是否會發生碰撞//會發生碰撞if(collide(cur)){//會碰撞,并且此時,如果方塊有一部分在外面,說明游戲結束if(cur.y<0){gameover()//正常的碰撞}else{//將方塊合并入游戲場景mergeBoxArea()//并將cur 設置為nullcur = null;//產生一個新的方塊createBox()}//不會碰撞}else{cur.y++;}}//更新游戲場景drawArea();//畫提示方塊drawNextBox(); timer = requestAnimationFrame(animate); } //更新游戲場景 function drawArea(){ctx.clearRect(0, 0, 300, 540)ctx.save()ctx.scale(30, 30)//ctx.fillStyle = '#fff';//畫游戲場景drawcube(ctx, area) //畫當前方塊drawcube(ctx, cur.box, cur.x, cur.y) ctx.restore(); } //更新提示 function drawNextBox(){nextctx.clearRect(0, 0, 120, 120)nextctx.save()nextctx.scale(30, 30)//畫下一個方塊next&&drawcube(nextctx, next.box) nextctx.restore(); } //畫方塊,接受一個ctx對象,一個數組, 數組的偏移值 function drawcube(ctx, arr, x, y){x = x || 0;y = y || 0;for(var i=0; i<arr.length; i++){for(var j=0; j<arr[i].length; j++){if(arr[i][j] !== 0){//設置方塊的顏色ctx.fillStyle = aColor[arr[i][j]];ctx.fillRect(j+x, i+y, 1, 1)}}} }監聽鍵盤事件,移動方塊
//監聽鍵盤事件 document.addEventListener('keydown', function(ev){if(bPause || !cur){return false;}var keycode = ev.keyCode;switch(keycode){//左case 37://是否能向左移動if(bMove(cur, -1)){cur.x--;}break;//右case 39://是否能向右移動if(bMove(cur, 1)){cur.x++;}break;//下case 40://如果觸底或者落到其它方塊上面if(collide(cur)){if(cur.y<0){gameover()}else{mergeBoxArea()cur = null;createBox()}}else{cur.y++;}break;//上case 38://是否能旋轉 當n為true時可以直接旋轉,當n為數值時需要將方塊x位置移動到此處才能旋轉var rotateRes = bRotate(cur);//可以直接旋轉if(rotateRes === true){cur.box = rotateBox(cur.box);//不能旋轉}else if(rotateRes === false){console.log('不能旋轉')//需要移動之后才能旋轉}else{cur.x = rotateRes;cur.box = rotateBox(cur.box);}break;} })處理游戲結束, 游戲暫停, 游戲重新開始, 消行, 更新游戲得分, 更新游戲運行時間等等
//點擊暫停按鈕 pauseEle.addEventListener('click', function(){var html = this.innerHTML;if(html === '暫停'){pause();this.innerHTML = '繼續';}else{start();this.innerHTML = '暫停';} }) //點擊重新開始 restartEle.addEventListener('click', function(){restart(); }) //暫停游戲 function pause(){cancelAnimationFrame(timer);bPause = true; } //繼續 function start(){timer = requestAnimationFrame(animate);bPause = false; } //重新開始 function restart(){//重置場景for(var i=0; i<area.length; i++){for(var j=0; j<area[i].length; j++){area[i][j] = 0;}}cancelAnimationFrame(timer);timer = requestAnimationFrame(animate);bPause = false;pauseEle.innerHTML = '暫停';//重置游戲時間gameTime = 0;//更新游戲時間updateGameTime();cur = null;//創建第一個方塊createBox(); } //游戲結束 function gameover(){ cancelAnimationFrame(timer);alert('游戲結束, 您一共獲得:'+SCORE+"分")restart() } //檢測是否可以消行,并將可以消除得行 加入結果數組返回出去 function isRemove(area){var arr = [];for(var i=0; i<area.length; i++){var remove = true;//如果數組的一行的每一項都不為0說明可以消除for(var j=0; j<area[i].length; j++){if(area[i][j] == 0){remove = false;}}//儲存消除行的索引if(remove){arr.push(i)}}return arr; } //更新游戲運行時間 function updateGameTime(){var n = gameTime/1000;var h = Math.floor(n/(60*60));n%=60*60;var m = Math.floor(n/60);n%=60;var s = Math.floor(n);h = h<9?'0'+h:''+h;m = m<9?'0'+m:''+m;s = s<9?'0'+s:''+s;gameTimeEle.innerHTML = h+':'+m+':'+s; } //根據游戲時間修改難度(方塊下落間隔時間) function changeDifficulty(){//游戲進行5分鐘 方塊下落間隔為300msif(gameTime>=1000*60*5){step = 300;//游戲運行3分鐘 方塊下落間隔500ms}else if(gameTime>=1000*60*3){step = 500;//游戲運行2分鐘 方塊下落間隔700ms}else if(gameTime>=1000*60*2){step = 700;} }到這里整個游戲差不多就算完了,最后 發一個預覽地址, 最終的預覽demo和現在的代碼并某些細節不是完全一樣
俄羅斯方塊?blog.cwlserver.top總結
以上是生活随笔為你收集整理的bom实现方块移动_从0开始实现一个俄罗斯方块的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 无显示器u盘安装centos_最新版 C
- 下一篇: tez什么意思_传统数仓和大数据数仓的区