日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

bom实现方块移动_从0开始实现一个俄罗斯方块

發(fā)布時(shí)間:2023/12/2 编程问答 27 豆豆
生活随笔 收集整理的這篇文章主要介紹了 bom实现方块移动_从0开始实现一个俄罗斯方块 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

寫在前面得話:

這篇文章主要記錄了我是怎么一步一步寫出俄羅斯方塊,整個(gè)代碼用的函數(shù)編程,主要是為了讓一些不熟悉es6, 面向?qū)ο髮懛ǖ?新手能更容易看明白,全部得代碼中都是一些js的基礎(chǔ)知識(shí),很容易理解。要說有點(diǎn)麻煩的,那就是游戲過程中的各種檢測(cè)。但是只要你多思考,你就能理解代碼為什么要那樣寫,你也可以實(shí)現(xiàn)這個(gè)游戲。(當(dāng)然也許你有更好的實(shí)現(xiàn)方法)。

預(yù)覽地址:

俄羅斯方塊?blog.cwlserver.top

1,先理清游戲邏輯

  • 游戲場景:場景大小為 10*18,
  • 下落時(shí)間:初始方塊每隔1秒,會(huì)下落一格。隨著游戲進(jìn)行時(shí)間得增加,方塊下落時(shí)間間隔會(huì)縮短。
  • 操作方法:方向鍵得 上下左右 分別控制方塊得, 變形,加速下落,左移,右移。
  • 方塊類型:一共7種類型得方塊。每次隨機(jī)出現(xiàn)一種, 每種方塊由數(shù)個(gè) 1*1大小得小方塊組成
  • 方塊下落:當(dāng)方塊落到底, 或者下一格已經(jīng)被占,方塊停止下落,然后會(huì)有一個(gè)新的方塊出現(xiàn)
  • 方塊左右移動(dòng):方塊左右移動(dòng)時(shí),如果左,右是墻或者是已經(jīng)被占,方塊將不能移動(dòng)。
  • 方塊變形:方塊逆時(shí)針旋轉(zhuǎn)90°,變形時(shí)需要判斷方塊是否可以變形。
  • 游戲會(huì)有下一個(gè)方塊得提示
  • 消行:當(dāng)一行被填滿時(shí),這一行將被消除
  • 計(jì)分規(guī)則: 消1行得2分,2行4分,3行8分,4行16分
  • 游戲結(jié)束: 當(dāng)方塊下落到底,并且方塊超出游戲場景時(shí),判定游戲結(jié)束

2,分步實(shí)現(xiàn)游戲中得功能

html結(jié)構(gòu)

<div id="box"><canvas id="canvas" width="300" height="540"></canvas><div class="scorebox"><p>游戲已進(jìn)行: <span id="game-time">00:00:00</span></p><br><p>當(dāng)前得分: <span id="score">0</span></p><br><p>下一個(gè)方塊:</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>

構(gòu)建場景

因?yàn)閳鼍按笮∈?0x18,所以我決定用一個(gè) 10x18得二維數(shù)組來模擬場景,這樣方便和方塊做碰撞檢測(cè)。

//定義列數(shù) var ROW = 10; //定義行數(shù) 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....]... ] */

構(gòu)建小方塊

小方塊我同樣使用二維數(shù)組來構(gòu)建

//定義各種方塊得數(shù)組, 一共7種不同得方塊,數(shù)組中的1,2,3,4..這些數(shù)字主要是為了每個(gè)方塊設(shè)置不同的顏色 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]] }; //定義方塊得顏色,每個(gè)數(shù)字對(duì)應(yīng)一種顏色 var aColor = ['', '#fff', '#0000FF', '#00FF00', '#CC00FF', '#CCFFFF','#FFFF33','#99FFFF']; //將data中得key放到一個(gè)字符串中 方便隨機(jī)調(diào)用 var sKey = 'os5ltj|'; //定義當(dāng)前方塊, 當(dāng)前方塊默認(rèn)null; var cur = null; //因?yàn)橛螒蛑袝?huì)有下一個(gè)方塊得提示, 所以這里要提前聲明一下 var next = null; //定義一個(gè)生成方塊得函數(shù) function createBox(){//首先創(chuàng)建提示方塊if(!next){//從skey中隨機(jī)取出一個(gè)鍵名var rnd = Math.floor(Math.random()*sKey.length);//根據(jù)key取得方塊數(shù)組var box = data[sKey[rnd]];//每一個(gè)方塊都有,x, y, box 這三個(gè)屬性next = {//方塊初始在場景中間位置,方塊左移 x--, 右移 x++;x: Math.floor((ROW-box[0].length)/2), //方塊在垂直方向得位置,剛好在場景外, y++ 方塊下落y: -box[0].length,//方塊得數(shù)組box: box};}//當(dāng)前方塊不存在時(shí), 創(chuàng)建當(dāng)前方塊if(!cur){//直接下一個(gè)方塊變成當(dāng)前這個(gè)cur = next;//然后再重新生成下一個(gè)next = {x: Math.floor((ROW-box[0].length)/2),y: -box[0].length,box: data[sKey[Math.floor(Math.random()*sKey.length)]]}} }

現(xiàn)在想一個(gè)問題,有了場景和方塊的數(shù)據(jù)之后,如何把他們聯(lián)系起來?

我的處理方式是這樣的,在方塊下落的過程中,方塊和場景是分開的,方塊的位置和場景是分開刷新的。在下落的過程中我會(huì) 檢測(cè)方塊和場景是否發(fā)生碰撞,如果發(fā)生了碰撞,將當(dāng)前方塊的數(shù)組合并到場景的數(shù)組中,使方塊變成場景的一部分,同時(shí)生成一個(gè)新的方塊。看下代碼如何實(shí)現(xiàn)

//將當(dāng)前方塊合并到場景 function mergeBoxArea(){//循環(huán)當(dāng)前方塊for(var i=0; i<cur.box.length; i++){//這里的判斷是為了當(dāng)方塊的一部分在場景外的時(shí)候,將那一部分跳過,只計(jì)算在場景中的部分if(i+cur.y>=0){for(var j=0; j<cur.box[i].length){//將當(dāng)前方塊數(shù)組中不為0的項(xiàng),和 場景中當(dāng)前位置為0的項(xiàng)合并if(cur.box[i][j] !== 0 && area[i+cur.y][j+cur.x] == 0){//合并的結(jié)果, 將場景中當(dāng)前位置的值設(shè)置為方塊對(duì)應(yīng)位置的值area[i+cur.y][j+cur.x] = cur.box[i][j];}}}}//將方塊合并入場景的同時(shí)要嘗試 消行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;}; } //碰撞檢測(cè) //垂直方向的碰撞檢測(cè), 需要接受當(dāng)前方塊做為參數(shù), //作用:檢測(cè)方塊下落一格之后和場景的碰撞情況,如果會(huì)碰撞返回true,否則返回false; function collide(cur){var box = cur.box;var len = box.length;var x = box.x;//因?yàn)槭菣z測(cè)下一個(gè)位置,所以要+1;var y = box.y + 1;for(var i=0; i<len; i++){//做碰撞檢測(cè)同樣需要將場景外的方塊部分排除掉if(i+y>=0){//方塊的數(shù)組都是n*n的所以都用lenfor(var j=0; j<len; j++){//將方塊為0的項(xiàng)不檢測(cè)if(box[i][j] !== 0){//第一種碰撞情況:當(dāng)i+y大于等于場景的高度時(shí),說明方塊出界//第二種碰撞情況:方塊沒有出界,但是場景中的這個(gè)位置,被占用了if(i+y>=area.length || (i+y<area.length && area[i+y][j+x] !== 0)){//碰撞了返回 truereturn true;}}}}}//代碼執(zhí)行到這里時(shí)說明沒有碰撞,返回false;return false; } //水平方向的移動(dòng)限制 //當(dāng)用鍵盤控制方塊左右移動(dòng)的時(shí)候,需要檢測(cè)左右是否是墻,或者方塊,這里檢測(cè)的也是下一個(gè)位置的碰撞情況 //如果沒有墻或者方塊(不碰撞),返回true //如果碰撞, 返回 false; //接受參數(shù): 當(dāng)前方塊:cur, 移動(dòng)方向: dir -1|0|1 function bMove(cur, dir){//當(dāng)前位置加上方向 就是 下一個(gè)位置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){//這里發(fā)生碰撞的情況有3中//1.方塊在左邊出界了, 這時(shí) j+x<0//2.方塊在右邊出界了, j+x>= ROW//3.方塊沒有出界,但是場景中的這個(gè)位置被占用 area[i+cur.y][j+x]!==0// 加上 i+cur.y>=0 && j+x>=0 && area[i+cur.y] 是為了防止報(bào)錯(cuò)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; }

如何處理方塊旋轉(zhuǎn)?

方塊的旋轉(zhuǎn)比較容易處理,就把二維數(shù)組旋轉(zhuǎn)一下就可以了。但是要注意方塊旋轉(zhuǎn)的時(shí)候也是需要檢測(cè) 旋轉(zhuǎn)的合理性, 可以想象一下,一個(gè)長條下落的過程中,如果他的左右兩邊都是方塊,這種情況肯定是不能旋轉(zhuǎn)的(其它方塊同理)。還有一種情況就是,方塊靠墻下落的時(shí)候,旋轉(zhuǎn)一下之后,有一部分轉(zhuǎn)到墻里面去了,這種也是不合理的,但是玩游戲的時(shí)候,這種情況也能旋轉(zhuǎn),所以出現(xiàn)這種情況的時(shí)候,我們需要修正一下方塊的位置。 下面看代碼怎么寫

//此函數(shù)用于檢測(cè)方塊是否能夠旋轉(zhuǎn) /* 參數(shù): 當(dāng)前方塊 cur 返回值: true //方塊可以直接旋轉(zhuǎn)false //方塊不能旋轉(zhuǎn),即使是在嘗試修正位置之后,就是上面說到的左右都是方塊的情況cur.x //當(dāng)返回 一個(gè)數(shù)值的時(shí)候,說明 將方塊水平移動(dòng)到這個(gè)位置后,可以旋轉(zhuǎn), 即上面說的修正位置 */ function bRotate(cur){//在這里復(fù)制一個(gè)旋轉(zhuǎn)后的方塊出來,用于檢測(cè)var _cur = {x: cur.x, y:cur.y, box: rotateBox(cur.box)};//檢測(cè)方塊旋轉(zhuǎn)之后,水平和垂直方向的碰撞情況, 如果在任意方向會(huì)發(fā)生碰撞if( collide(_cur) === true || bMove(_cur, 0) === false ){//嘗試水平移動(dòng)方塊,移動(dòng)方向是分別向左,向右移動(dòng)2格for(var i=0; i<2; i++){//方塊靠近左邊的時(shí)候,嘗試向右移動(dòng),并且檢測(cè)移動(dòng)的合理性if(_cur.x<4 && bMove(_cur, 1)){_cur.x++;}//靠近右側(cè)的時(shí)候,向左移動(dòng),并且檢測(cè)移動(dòng)的合理性if(_cur.x>6 && bMove(_cur, -1)){_cur.x--;}//移動(dòng)之后再檢測(cè)是否碰撞, 如果不會(huì)發(fā)生碰撞, 返回移動(dòng)后的位置if(collide(_cur) === false && bMove(_cur, 0)){return _cur.x;}}//代碼執(zhí)行到這里的時(shí)候說明,移動(dòng)了之后仍會(huì)碰撞return false;//如果旋轉(zhuǎn)之后不會(huì)發(fā)生碰撞,直接返回true;}else{return true;} } //旋轉(zhuǎn)數(shù)組的函數(shù) function rotateBox(arr){var res = [];for(var i=0; i<arr.length; i++){res.push([]);}//旋轉(zhuǎn)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; }

現(xiàn)在開始處理游戲的刷新, 計(jì)算游戲的時(shí)間

游戲用 requestAnimationFrame 更新

var timer = null; //記錄一個(gè)舊的時(shí)間,這里用于輔助計(jì)算, 每次刷新的間隔時(shí)間 var oldTime = Date.now(); //n 用于累加 raf 的間隔時(shí)間 var n = 0; //游戲運(yùn)行時(shí)間 單位 毫秒 var gameTime = 0; //方塊下落的間隔時(shí)間 var step = 1000; //游戲是否暫停 var bPause = false; //獲取dom元素 //主場景canvas var canvas = document.getElementById('canvas'); var ctx = canvas.getContext('2d'); //提示下一個(gè)方塊 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') //顯示游戲時(shí)間 var gameTimeEle = document.getElementById('game-time'); //開啟主循環(huán) timer = requestAnimationFrame(animate); //主循環(huán)函數(shù) function animate(){//累加 raf 的間隔時(shí)間n+=Date.now()-oldTime;//累加游戲運(yùn)行時(shí)間gameTime+=Date.now()-oldTime;oldTime = Date.now();//方塊要開始下落了if(n>=step){n = 0;//每秒鐘更新一次游戲時(shí)間updateGameTime()//根據(jù)游戲進(jìn)行時(shí)間提高游戲難度changeDifficulty();//方塊下落之前要先檢測(cè)是否會(huì)發(fā)生碰撞//會(huì)發(fā)生碰撞if(collide(cur)){//會(huì)碰撞,并且此時(shí),如果方塊有一部分在外面,說明游戲結(jié)束if(cur.y<0){gameover()//正常的碰撞}else{//將方塊合并入游戲場景mergeBoxArea()//并將cur 設(shè)置為nullcur = null;//產(chǎn)生一個(gè)新的方塊createBox()}//不會(huì)碰撞}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) //畫當(dāng)前方塊drawcube(ctx, cur.box, cur.x, cur.y) ctx.restore(); } //更新提示 function drawNextBox(){nextctx.clearRect(0, 0, 120, 120)nextctx.save()nextctx.scale(30, 30)//畫下一個(gè)方塊next&&drawcube(nextctx, next.box) nextctx.restore(); } //畫方塊,接受一個(gè)ctx對(duì)象,一個(gè)數(shù)組, 數(shù)組的偏移值 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){//設(shè)置方塊的顏色ctx.fillStyle = aColor[arr[i][j]];ctx.fillRect(j+x, i+y, 1, 1)}}} }

監(jiān)聽鍵盤事件,移動(dòng)方塊

//監(jiān)聽鍵盤事件 document.addEventListener('keydown', function(ev){if(bPause || !cur){return false;}var keycode = ev.keyCode;switch(keycode){//左case 37://是否能向左移動(dòng)if(bMove(cur, -1)){cur.x--;}break;//右case 39://是否能向右移動(dòng)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://是否能旋轉(zhuǎn) 當(dāng)n為true時(shí)可以直接旋轉(zhuǎn),當(dāng)n為數(shù)值時(shí)需要將方塊x位置移動(dòng)到此處才能旋轉(zhuǎn)var rotateRes = bRotate(cur);//可以直接旋轉(zhuǎn)if(rotateRes === true){cur.box = rotateBox(cur.box);//不能旋轉(zhuǎn)}else if(rotateRes === false){console.log('不能旋轉(zhuǎn)')//需要移動(dòng)之后才能旋轉(zhuǎn)}else{cur.x = rotateRes;cur.box = rotateBox(cur.box);}break;} })

處理游戲結(jié)束, 游戲暫停, 游戲重新開始, 消行, 更新游戲得分, 更新游戲運(yùn)行時(shí)間等等

//點(diǎn)擊暫停按鈕 pauseEle.addEventListener('click', function(){var html = this.innerHTML;if(html === '暫停'){pause();this.innerHTML = '繼續(xù)';}else{start();this.innerHTML = '暫停';} }) //點(diǎn)擊重新開始 restartEle.addEventListener('click', function(){restart(); }) //暫停游戲 function pause(){cancelAnimationFrame(timer);bPause = true; } //繼續(xù) 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 = '暫停';//重置游戲時(shí)間gameTime = 0;//更新游戲時(shí)間updateGameTime();cur = null;//創(chuàng)建第一個(gè)方塊createBox(); } //游戲結(jié)束 function gameover(){ cancelAnimationFrame(timer);alert('游戲結(jié)束, 您一共獲得:'+SCORE+"分")restart() } //檢測(cè)是否可以消行,并將可以消除得行 加入結(jié)果數(shù)組返回出去 function isRemove(area){var arr = [];for(var i=0; i<area.length; i++){var remove = true;//如果數(shù)組的一行的每一項(xiàng)都不為0說明可以消除for(var j=0; j<area[i].length; j++){if(area[i][j] == 0){remove = false;}}//儲(chǔ)存消除行的索引if(remove){arr.push(i)}}return arr; } //更新游戲運(yùn)行時(shí)間 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; } //根據(jù)游戲時(shí)間修改難度(方塊下落間隔時(shí)間) function changeDifficulty(){//游戲進(jìn)行5分鐘 方塊下落間隔為300msif(gameTime>=1000*60*5){step = 300;//游戲運(yùn)行3分鐘 方塊下落間隔500ms}else if(gameTime>=1000*60*3){step = 500;//游戲運(yùn)行2分鐘 方塊下落間隔700ms}else if(gameTime>=1000*60*2){step = 700;} }

到這里整個(gè)游戲差不多就算完了,最后 發(fā)一個(gè)預(yù)覽地址, 最終的預(yù)覽demo和現(xiàn)在的代碼并某些細(xì)節(jié)不是完全一樣

俄羅斯方塊?blog.cwlserver.top

總結(jié)

以上是生活随笔為你收集整理的bom实现方块移动_从0开始实现一个俄罗斯方块的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。