CocosCreator Typescript制作俄羅斯方塊游戲
1.引言
最近開始學cocos,學完Typescript語法之后,跑去看cocos的官方文檔,搗鼓了幾天,寫了一個非常簡單的貪吃蛇,甚至連像樣的碰撞檢測也沒有,自覺無趣,就荒廢了一段時間。這幾個星期我又重拾了cocos,就有了實現俄羅斯方塊的想法。一開始我想著上網找找資料,發(fā)現關于cocos開發(fā)俄羅斯方塊的文章幾乎寥寥無幾(也有可能是我找的方法不對),更頭痛的是,我找到的僅有幾個分享文章的代碼注釋比較少,也可能是我的理解能力不行,后來花了幾天也沒能完全看懂。所以我打算自己嘗試寫寫看,過了兩個星期,總算是完成了。
在文章的后面,我會附上整個cocos的項目文件供大家參考,代碼寫得不好,請大家多多指教。
2.需要解決的幾個關鍵問題
1.游戲區(qū)的方塊我們怎么存儲起來
?因為俄羅斯方塊是像素游戲,我們可以把每一個方塊看成一個像素,那么整個游戲區(qū)就是一塊像素集合,結合到cocos內,我們把每一個方塊定義成cc.Node型,那么我們的游戲區(qū)就可以使用一個cc.Node型的二維數組將方塊保存起來,方便進行旋轉,位移,堆疊,刪除等關鍵操作。在這里我使用的是一個20*10的二維數組。
//整個游戲區(qū)的格子用二維數組保存 box: cc.Node[][] = [];
//初始化box二維數組,這個數組的[0][0]在游戲區(qū)的最左下角 InitBox() { for (let i = 0; i < 20; i++) { this.box[i] = []; for (let j = 0; j < 10; j++) { this.box[i][j] = null; } } //生成不同的方塊集合 this.buildBlock(); }
2.每種類型方塊集合的構建
總所周知(),俄羅斯方塊中的方塊有七種,分別是:反Z型、L型、反L型、Z型、條型、T型、方形。
我們可以發(fā)現,每種方塊集合都是由四個小方塊組成的,我們可以利用這個特點構建統(tǒng)一的構建方法。
為了后續(xù)使用起來方便,我首先定義了每種小方塊的預制體(Prefab)和一個空節(jié)點的預制體,這個預制體所生成的節(jié)點是用來裝后續(xù)構建的方塊節(jié)點的。所以結構上是父與子的關系。
//正方形的子塊 @property(cc.Prefab) block_0: cc.Prefab = null; //Z字型的子塊 @property(cc.Prefab) block_1: cc.Prefab = null; //左L型的子塊 @property(cc.Prefab) block_2: cc.Prefab = null; //右L型的子塊 @property(cc.Prefab) block_3: cc.Prefab = null; //反Z型的子塊 @property(cc.Prefab) block_4: cc.Prefab = null; //長條型的子塊 @property(cc.Prefab) block_5: cc.Prefab = null; //T字型的子塊 @property(cc.Prefab) block_6: cc.Prefab = null; //方塊集合的中心 @property(cc.Prefab) currentBlockCentre = null; //當前的塊 currentBlock: cc.Node = null; //currentBlockCentre的具體實現 currentBlockPart01: cc.Node = null; //四個子塊的具體實現 currentBlockPart02: cc.Node = null; currentBlockPart03: cc.Node = null; currentBlockPart04: cc.Node = null;
關于隨機生成哪種顏色、哪種類型的方塊,我只是簡單的選擇了自帶的Math.random()。
buildBlock() { this.rand = Math.floor(7 * Math.random()); //從七種中隨機選擇一種構建 this.chooseColor(this.rand); this.chooseType(this.rand); }
后面就是根據輸入的rand參數來選擇構建方塊集合的顏色、種類。關于如何構建,具體就是選擇這個方塊集合的中心點——最好選擇在某個子塊的中心,并將position設為(0, 0)。這樣,在后續(xù)的旋轉方面的實現會非常方便。然后選擇好中心點之后,其他的子塊就根據這個中心點來設置position,而cocos中子節(jié)點的position是相對于父節(jié)點的,子節(jié)點如果將position設置為(0, 0),那么子節(jié)點的位置就在父節(jié)點中心點上。
另外,每個子塊的預制體尺寸都是60*60,也就是說游戲區(qū)每個格子之間的間隔是60。
這一段的代碼比較長,我就不詳細給出了。
//選擇方塊集合的顏色 chooseColor(rand) { …… //Z字形方塊的顏色 if (rand == 1) { this.currentBlockPart01 = cc.instantiate(this.block_1); this.currentBlockPart02 = cc.instantiate(this.block_1); this.currentBlockPart03 = cc.instantiate(this.block_1); this.currentBlockPart04 = cc.instantiate(this.block_1); this.currentBlock = cc.instantiate(this.currentBlockCentre); this.node.addChild(this.currentBlock); this.currentBlock.setPosition(30, 510); //將當前生成的方塊集合位置設定在游戲區(qū)的上面,準備后續(xù)的下落 } //左L型方塊的顏色 if (rand == 2) …… } //選擇形狀 chooseType(rand) { …… //創(chuàng)建Z字形 if (rand == 1) { //Z字形左 this.currentBlockPart01.setPosition(-60, 0); this.currentBlockPart01Pos = cc.v2(18, 4); //初始化當前塊的位置,相對于currentBlock //Z字形中 this.currentBlockPart02.setPosition(0, 0); this.currentBlockPart02Pos = cc.v2(18, 5); //Z字形下 this.currentBlockPart03.setPosition(0, -60); this.currentBlockPart03Pos = cc.v2(17, 5); //Z字形右 this.currentBlockPart04.setPosition(60, -60); this.currentBlockPart04Pos = cc.v2(17, 6); } //創(chuàng)建左L型 if (rand == 2) …… }
3.如何將創(chuàng)建的方塊集合和節(jié)點二維數組結合起來
上面的代碼里有這樣的變量:currentBlockPart0XPos,定義了當前可操作方塊集合currentBlock每個子塊currentBlockPart0X在box節(jié)點二維數組中的具體位置。這四個變量非常有用,之后就可以實現當前可操作方塊移動之后,將位置信息保存在box節(jié)點二維數組中。
//當前子塊的位置 currentBlockPart01Pos: cc.Vec2 = null; currentBlockPart02Pos: cc.Vec2 = null; currentBlockPart03Pos: cc.Vec2 = null; currentBlockPart04Pos: cc.Vec2 = null;
之后在每次可操作方塊集合變化后,我們都可以調用下面這兩個方法更新可操作方塊集合在box數組中的位置。
//讀取當前操作方塊集合的位置信息 checkCurrentBlockPos() { this.box[this.currentBlockPart01Pos.x][this.currentBlockPart01Pos.y] = this.currentBlockPart01; this.box[this.currentBlockPart02Pos.x][this.currentBlockPart02Pos.y] = this.currentBlockPart02; this.box[this.currentBlockPart03Pos.x][this.currentBlockPart03Pos.y] = this.currentBlockPart03; this.box[this.currentBlockPart04Pos.x][this.currentBlockPart04Pos.y] = this.currentBlockPart04; } //清除上個位置的當前操作方塊集合位置信息 deleteCurrentBlockPos() { this.box[this.currentBlockPart01Pos.x][this.currentBlockPart01Pos.y] = null; this.box[this.currentBlockPart02Pos.x][this.currentBlockPart02Pos.y] = null; this.box[this.currentBlockPart03Pos.x][this.currentBlockPart03Pos.y] = null; this.box[this.currentBlockPart04Pos.x][this.currentBlockPart04Pos.y] = null; }
4.方塊集合的移動和旋轉
關于移動,遵循大部分俄羅斯方塊游戲的操作方式,左鍵左移,右鍵右移,上鍵旋轉,下鍵下移,還有自動下落。
//自動下落 autoDown() { this.schedule(() => { //一直下落直到碰到下邊界 if (this.isClashBottom()) { this.deleteRow(); //行消除檢測 this.buildBlock(); //創(chuàng)建新的方塊集合 } else if (this.isClashBlockDown()) { //一直下落直到碰到其他方塊 this.isGameOver(); //判斷游戲是否結束 this.deleteRow(); this.buildBlock(); } else { //向下一格 this.currentBlock.y -= 60; this.deleteCurrentBlockPos(); this.currentBlockPart01Pos.x -= 1; this.currentBlockPart02Pos.x -= 1; this.currentBlockPart03Pos.x -= 1; this.currentBlockPart04Pos.x -= 1; this.checkCurrentBlockPos(); } }, 1); } //鍵盤監(jiān)聽 onKeyDown(e) { switch (e.keyCode) { case cc.macro.KEY.left: if (this.isClashLeft()) { //判斷是否撞到左邊界 break; } else if (this.isClashBlockLeft()) { //判斷當前操作塊是否左邊撞到了其他子塊 break; } else { this.currentBlock.x -= 60; this.deleteCurrentBlockPos(); this.currentBlockPart01Pos.y -= 1; this.currentBlockPart02Pos.y -= 1; this.currentBlockPart03Pos.y -= 1; this.currentBlockPart04Pos.y -= 1; this.checkCurrentBlockPos(); break; } case cc.macro.KEY.right: …… case cc.macro.KEY.up: //改變形態(tài) if (this.isClashLeft()) { //判斷是否撞到左邊界 break; } else if (this.isClashRight()) { //判斷是否撞到右邊界 break; } else if (this.isClashBottom()) { //判斷是否撞到下邊界 break; } else if (this.isClashBlockLeft()) { //判斷當前操作塊是否左邊撞到了其他子塊 break; } else if (this.isClashBlockRight()) { //判斷當前操作塊是否右邊邊撞到了其他子塊 break; } else if (this.isClashBlockDown()) { //判斷當前操作塊是否下邊撞到了其他子塊 break; } else { this.deleteCurrentBlockPos(); this.changeShape(); //旋轉變形態(tài) this.checkCurrentBlockPos(); break; } case cc.macro.KEY.down: …… } }
關于旋轉這部分,我其實是取巧了,我特意設置了某些子塊的位置為中心點,正好可以使我這種旋轉操作成立。
圖中灰色圓形指出的子塊則是我設定的中心點。而如果將中心點作為二維坐標原點,可以劃分為八個區(qū)域:y軸上半、y軸下半、x軸左半、x軸右半、第一象限、第二象限、第三象限、第四象限。
以Z型旋轉為例,可以發(fā)現,在四個坐標軸上的子塊x和y都改變了,而在象限上的子塊只是改變了x和y的其中一個,而且是取原來值的相反數。我們這樣實現旋轉,實際上只是子塊的位置改變了,子塊所朝方向并沒有改變。
//旋轉變形態(tài) changeShape() { this.whichPartChange(this.currentBlockPart01, this.currentBlockPart01Pos); this.whichPartChange(this.currentBlockPart02, this.currentBlockPart02Pos); this.whichPartChange(this.currentBlockPart03, this.currentBlockPart03Pos); this.whichPartChange(this.currentBlockPart04, this.currentBlockPart04Pos); } //傳入被判斷的部分 whichPartChange(currentBlockPart: cc.Node, currentBlockPartPos: cc.Vec2) { //修正參數,用于旋轉currentBlockPartPos的位置,從左邊到上邊,上邊到右邊,右邊到下邊,下邊到左邊,在象限中的不需要用到 let modParameterX = Math.abs(currentBlockPart.position.x / 60); let modParameterY = Math.abs(currentBlockPart.position.y / 60); let modParameterMax = Math.max(modParameterX, modParameterY); //y軸上半 if (currentBlockPart.position.x == 0 && currentBlockPart.position.y > 0) { //行- 列+ currentBlockPartPos.x -= modParameterMax; currentBlockPartPos.y += modParameterMax; //旋轉當前塊的位置 currentBlockPart.setPosition(currentBlockPart.position.y, currentBlockPart.position.x); } //x軸左半 else if (currentBlockPart.position.x < 0 && currentBlockPart.position.y == 0) { …… } //y軸下半 else if (currentBlockPart.position.x == 0 && currentBlockPart.position.y < 0) { …… } //x軸右半 else if (currentBlockPart.position.x > 0 && currentBlockPart.position.y == 0) { …… } //第一象限 if (currentBlockPart.position.x > 0 && currentBlockPart.position.y > 0) { //行- if (currentBlockPart.position.x >= 60 && currentBlockPart.position.y >= 60) { currentBlockPartPos.x -= 2; } else { currentBlockPartPos.x -= 1; } //旋轉當前塊的位置 currentBlockPart.setPosition(currentBlockPart.position.x, -currentBlockPart.position.y); } //第二象限 else if (currentBlockPart.position.x < 0 && currentBlockPart.position.y > 0) { …… } //第三象限 else if (currentBlockPart.position.x < 0 && currentBlockPart.position.y < 0) { …… } //第四象限 else if (currentBlockPart.position.x > 0 && currentBlockPart.position.y < 0) { …… } }
5.邊界和方塊檢測
邊界檢測有三種,分別是左邊界檢測、右邊界檢測和下邊界檢測。方塊檢測同樣為三種,分別是當前可操作方塊集合下方檢測、左方檢測和右方檢測。
//判斷是否即將碰撞到左邊界 isClashLeft(): boolean { if (this.currentBlockPart01Pos.y - 1 < 0 || this.currentBlockPart02Pos.y - 1 < 0 || this.currentBlockPart03Pos.y - 1 < 0 || this.currentBlockPart04Pos.y - 1 < 0) { return true; } return false; } //判斷是否即將碰撞到右邊界 isClashRight(): boolean { …… } //判斷是否即將碰撞到下邊界 isClashBottom(): boolean { …… }
//判斷是否即將碰撞到其他方塊(下) isClashBlockDown(): boolean { //向下檢測方塊碰撞 if (this.box[this.currentBlockPart01Pos.x - 1][this.currentBlockPart01Pos.y] != null && !this.isCurrentBlockChild(this.box[this.currentBlockPart01Pos.x - 1][this.currentBlockPart01Pos.y]) || this.box[this.currentBlockPart02Pos.x - 1][this.currentBlockPart02Pos.y] != null && !this.isCurrentBlockChild(this.box[this.currentBlockPart02Pos.x - 1][this.currentBlockPart02Pos.y]) || this.box[this.currentBlockPart03Pos.x - 1][this.currentBlockPart03Pos.y] != null && !this.isCurrentBlockChild(this.box[this.currentBlockPart03Pos.x - 1][this.currentBlockPart03Pos.y]) || this.box[this.currentBlockPart04Pos.x - 1][this.currentBlockPart04Pos.y] != null && !this.isCurrentBlockChild(this.box[this.currentBlockPart04Pos.x - 1][this.currentBlockPart04Pos.y])) { return true; } } //判斷是否即將碰撞到其他方塊(左) isClashBlockLeft() { …… } //判斷是否即將碰撞到其他方塊(右) isClashBlockRight() { …… } //判斷是否是當前操作方塊集合的子塊 isCurrentBlockChild(judgeObj: cc.Node): boolean { for (let i = 0; i < 4; i++) { if (judgeObj === this.currentBlock.children[i]) { return true; } } return false; }
因為每個子塊在對方塊檢測時,都要向左、右或下一格判斷是否存在其他方塊,而有可能判斷的方塊是和自己同一個父類的,所以判斷時還要判斷是否為當前操作方塊集合的子塊。
6.方塊的整行消除
需要注意的是,游戲內方塊如果一列一列看的話,有時會存在鏤空的情況,這時就要考慮鏤空的時候要怎么向下移動一格。所以在rowDown()方法中,在整體下降的時候,如果判斷到同一列上一格是空的,則賦為null,把剛移動到下一格的方塊信息刪除。
//行消除檢測 deleteRow() { for (let i = 0; i < 18; i++) { let count = 0; for (let j = 0; j < 10; j++) { if (this.box[i][j] != null) { count++; } } //如果某一行內都存在方塊 if (count == 10) { for (let j = 0; j < 10; j++) { //方塊刪除 this.box[i][j].removeFromParent(); this.box[i][j] = null; } this.rowDown(i); i--; //因為rowDown(i)后,整體向下了一格,所以i--,否則無法實現多行消除,導致游戲無法正常運行 } } } //全體方塊向下移動一格 rowDown(i: number) { //記錄i值,即被當前被消除行 let k = i; //列遍歷 for (let j = 0; j < 10; j++) { //temp:用于計算當前被消除行上面有多少行的方塊元素(包括中間層存在鏤空) let temp = -1; for (i = k; i < 18; i++) { temp++; if (this.box[i][j] != null) { this.box[i - 1][j] = this.box[i][j]; this.box[i][j].y -= 60; if (this.box[i + 1][j] == null) { this.box[temp + k][j] = null; } } } } }
3.寫在最后
大體上最核心的問題我應該都好好說明了,如果有某些地方不清楚的話,歡迎下載原項目文件:
鏈接: 百度網盤 請輸入提取碼 提取碼: c4ss
到此這篇關于CocosCreator 制作俄羅斯方塊游戲的文章就介紹到這了,更多相關CocosCreator內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
JavaScript簡單實現動態(tài)改變HTML內容的方法示例
這篇文章主要介紹了JavaScript簡單實現動態(tài)改變HTML內容的方法,結合實例形式分析了javascript簡單獲取及修改HTML元素的相關操作技巧,非常簡單易懂,需要的朋友可以參考下2018-12-12JavaScript和JQuery的鼠標mouse事件冒泡處理
這篇文章主要介紹了JavaScript和JQuery的鼠標mouse事件冒泡處理,本文總結出了mouse事件的一些定論,并分別給出了JavaScript和JQuery測試代碼,需要的朋友可以參考下2015-06-06Javascript中獲取瀏覽器類型和操作系統(tǒng)版本等客戶端信息常用代碼
跟蹤一些最基本的客戶端訪問信息,這里將一些公用的代碼總結下來,需要的朋友可以參考下2016-06-06