CocosCreator Typescript制作俄羅斯方塊游戲
1.引言
最近開始學(xué)cocos,學(xué)完Typescript語法之后,跑去看cocos的官方文檔,搗鼓了幾天,寫了一個(gè)非常簡單的貪吃蛇,甚至連像樣的碰撞檢測也沒有,自覺無趣,就荒廢了一段時(shí)間。這幾個(gè)星期我又重拾了cocos,就有了實(shí)現(xiàn)俄羅斯方塊的想法。一開始我想著上網(wǎng)找找資料,發(fā)現(xiàn)關(guān)于cocos開發(fā)俄羅斯方塊的文章幾乎寥寥無幾(也有可能是我找的方法不對(duì)),更頭痛的是,我找到的僅有幾個(gè)分享文章的代碼注釋比較少,也可能是我的理解能力不行,后來花了幾天也沒能完全看懂。所以我打算自己嘗試寫寫看,過了兩個(gè)星期,總算是完成了。
在文章的后面,我會(huì)附上整個(gè)cocos的項(xiàng)目文件供大家參考,代碼寫得不好,請大家多多指教。


2.需要解決的幾個(gè)關(guān)鍵問題
1.游戲區(qū)的方塊我們怎么存儲(chǔ)起來
?因?yàn)槎砹_斯方塊是像素游戲,我們可以把每一個(gè)方塊看成一個(gè)像素,那么整個(gè)游戲區(qū)就是一塊像素集合,結(jié)合到cocos內(nèi),我們把每一個(gè)方塊定義成cc.Node型,那么我們的游戲區(qū)就可以使用一個(gè)cc.Node型的二維數(shù)組將方塊保存起來,方便進(jìn)行旋轉(zhuǎn),位移,堆疊,刪除等關(guān)鍵操作。在這里我使用的是一個(gè)20*10的二維數(shù)組。
//整個(gè)游戲區(qū)的格子用二維數(shù)組保存
box: cc.Node[][] = [];
//初始化box二維數(shù)組,這個(gè)數(shù)組的[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.每種類型方塊集合的構(gòu)建
總所周知(),俄羅斯方塊中的方塊有七種,分別是:反Z型、L型、反L型、Z型、條型、T型、方形。

我們可以發(fā)現(xiàn),每種方塊集合都是由四個(gè)小方塊組成的,我們可以利用這個(gè)特點(diǎn)構(gòu)建統(tǒng)一的構(gòu)建方法。
為了后續(xù)使用起來方便,我首先定義了每種小方塊的預(yù)制體(Prefab)和一個(gè)空節(jié)點(diǎn)的預(yù)制體,這個(gè)預(yù)制體所生成的節(jié)點(diǎn)是用來裝后續(xù)構(gòu)建的方塊節(jié)點(diǎn)的。所以結(jié)構(gòu)上是父與子的關(guān)系。
//正方形的子塊
@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;
//當(dāng)前的塊
currentBlock: cc.Node = null; //currentBlockCentre的具體實(shí)現(xiàn)
currentBlockPart01: cc.Node = null; //四個(gè)子塊的具體實(shí)現(xiàn)
currentBlockPart02: cc.Node = null;
currentBlockPart03: cc.Node = null;
currentBlockPart04: cc.Node = null;
關(guān)于隨機(jī)生成哪種顏色、哪種類型的方塊,我只是簡單的選擇了自帶的Math.random()。
buildBlock() {
this.rand = Math.floor(7 * Math.random()); //從七種中隨機(jī)選擇一種構(gòu)建
this.chooseColor(this.rand);
this.chooseType(this.rand);
}
后面就是根據(jù)輸入的rand參數(shù)來選擇構(gòu)建方塊集合的顏色、種類。關(guān)于如何構(gòu)建,具體就是選擇這個(gè)方塊集合的中心點(diǎn)——最好選擇在某個(gè)子塊的中心,并將position設(shè)為(0, 0)。這樣,在后續(xù)的旋轉(zhuǎn)方面的實(shí)現(xiàn)會(huì)非常方便。然后選擇好中心點(diǎn)之后,其他的子塊就根據(jù)這個(gè)中心點(diǎn)來設(shè)置position,而cocos中子節(jié)點(diǎn)的position是相對(duì)于父節(jié)點(diǎn)的,子節(jié)點(diǎn)如果將position設(shè)置為(0, 0),那么子節(jié)點(diǎn)的位置就在父節(jié)點(diǎn)中心點(diǎn)上。
另外,每個(gè)子塊的預(yù)制體尺寸都是60*60,也就是說游戲區(qū)每個(gè)格子之間的間隔是60。
這一段的代碼比較長,我就不詳細(xì)給出了。
//選擇方塊集合的顏色
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); //將當(dāng)前生成的方塊集合位置設(shè)定在游戲區(qū)的上面,準(zhǔn)備后續(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); //初始化當(dāng)前塊的位置,相對(duì)于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é)點(diǎn)二維數(shù)組結(jié)合起來
上面的代碼里有這樣的變量:currentBlockPart0XPos,定義了當(dāng)前可操作方塊集合currentBlock每個(gè)子塊currentBlockPart0X在box節(jié)點(diǎn)二維數(shù)組中的具體位置。這四個(gè)變量非常有用,之后就可以實(shí)現(xiàn)當(dāng)前可操作方塊移動(dòng)之后,將位置信息保存在box節(jié)點(diǎn)二維數(shù)組中。
//當(dāng)前子塊的位置
currentBlockPart01Pos: cc.Vec2 = null;
currentBlockPart02Pos: cc.Vec2 = null;
currentBlockPart03Pos: cc.Vec2 = null;
currentBlockPart04Pos: cc.Vec2 = null;
之后在每次可操作方塊集合變化后,我們都可以調(diào)用下面這兩個(gè)方法更新可操作方塊集合在box數(shù)組中的位置。
//讀取當(dāng)前操作方塊集合的位置信息
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;
}
//清除上個(gè)位置的當(dāng)前操作方塊集合位置信息
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.方塊集合的移動(dòng)和旋轉(zhuǎn)
關(guān)于移動(dòng),遵循大部分俄羅斯方塊游戲的操作方式,左鍵左移,右鍵右移,上鍵旋轉(zhuǎn),下鍵下移,還有自動(dòng)下落。
//自動(dòng)下落
autoDown() {
this.schedule(() => {
//一直下落直到碰到下邊界
if (this.isClashBottom()) {
this.deleteRow(); //行消除檢測
this.buildBlock(); //創(chuàng)建新的方塊集合
} else if (this.isClashBlockDown()) { //一直下落直到碰到其他方塊
this.isGameOver(); //判斷游戲是否結(jié)束
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()) { //判斷當(dāng)前操作塊是否左邊撞到了其他子塊
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()) { //判斷當(dāng)前操作塊是否左邊撞到了其他子塊
break;
} else if (this.isClashBlockRight()) { //判斷當(dāng)前操作塊是否右邊邊撞到了其他子塊
break;
} else if (this.isClashBlockDown()) { //判斷當(dāng)前操作塊是否下邊撞到了其他子塊
break;
} else {
this.deleteCurrentBlockPos();
this.changeShape(); //旋轉(zhuǎn)變形態(tài)
this.checkCurrentBlockPos();
break;
}
case cc.macro.KEY.down:
……
}
}
關(guān)于旋轉(zhuǎn)這部分,我其實(shí)是取巧了,我特意設(shè)置了某些子塊的位置為中心點(diǎn),正好可以使我這種旋轉(zhuǎn)操作成立。
圖中灰色圓形指出的子塊則是我設(shè)定的中心點(diǎn)。而如果將中心點(diǎn)作為二維坐標(biāo)原點(diǎn),可以劃分為八個(gè)區(qū)域:y軸上半、y軸下半、x軸左半、x軸右半、第一象限、第二象限、第三象限、第四象限。


以Z型旋轉(zhuǎn)為例,可以發(fā)現(xiàn),在四個(gè)坐標(biāo)軸上的子塊x和y都改變了,而在象限上的子塊只是改變了x和y的其中一個(gè),而且是取原來值的相反數(shù)。我們這樣實(shí)現(xiàn)旋轉(zhuǎn),實(shí)際上只是子塊的位置改變了,子塊所朝方向并沒有改變。
//旋轉(zhuǎn)變形態(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) {
//修正參數(shù),用于旋轉(zhuǎn)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;
//旋轉(zhuǎn)當(dāng)前塊的位置
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;
}
//旋轉(zhuǎn)當(dāng)前塊的位置
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.邊界和方塊檢測
邊界檢測有三種,分別是左邊界檢測、右邊界檢測和下邊界檢測。方塊檢測同樣為三種,分別是當(dāng)前可操作方塊集合下方檢測、左方檢測和右方檢測。
//判斷是否即將碰撞到左邊界
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() {
……
}
//判斷是否是當(dāng)前操作方塊集合的子塊
isCurrentBlockChild(judgeObj: cc.Node): boolean {
for (let i = 0; i < 4; i++) {
if (judgeObj === this.currentBlock.children[i]) {
return true;
}
}
return false;
}
因?yàn)槊總€(gè)子塊在對(duì)方塊檢測時(shí),都要向左、右或下一格判斷是否存在其他方塊,而有可能判斷的方塊是和自己同一個(gè)父類的,所以判斷時(shí)還要判斷是否為當(dāng)前操作方塊集合的子塊。
6.方塊的整行消除
需要注意的是,游戲內(nèi)方塊如果一列一列看的話,有時(shí)會(huì)存在鏤空的情況,這時(shí)就要考慮鏤空的時(shí)候要怎么向下移動(dòng)一格。所以在rowDown()方法中,在整體下降的時(shí)候,如果判斷到同一列上一格是空的,則賦為null,把剛移動(dòng)到下一格的方塊信息刪除。
//行消除檢測
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++;
}
}
//如果某一行內(nèi)都存在方塊
if (count == 10) {
for (let j = 0; j < 10; j++) {
//方塊刪除
this.box[i][j].removeFromParent();
this.box[i][j] = null;
}
this.rowDown(i);
i--; //因?yàn)閞owDown(i)后,整體向下了一格,所以i--,否則無法實(shí)現(xiàn)多行消除,導(dǎo)致游戲無法正常運(yùn)行
}
}
}
//全體方塊向下移動(dòng)一格
rowDown(i: number) {
//記錄i值,即被當(dāng)前被消除行
let k = i;
//列遍歷
for (let j = 0; j < 10; j++) {
//temp:用于計(jì)算當(dāng)前被消除行上面有多少行的方塊元素(包括中間層存在鏤空)
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.寫在最后
大體上最核心的問題我應(yīng)該都好好說明了,如果有某些地方不清楚的話,歡迎下載原項(xiàng)目文件:
鏈接: 百度網(wǎng)盤 請輸入提取碼 提取碼: c4ss
到此這篇關(guān)于CocosCreator 制作俄羅斯方塊游戲的文章就介紹到這了,更多相關(guān)CocosCreator內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
IE關(guān)閉時(shí)判斷及AJAX注銷案例學(xué)習(xí)
當(dāng)關(guān)閉系統(tǒng)時(shí)會(huì)提示:你確定要退出系統(tǒng)嗎?退出請按'離開此頁'接下來將講解下IE關(guān)閉判斷及AJAX注銷,感興趣的你可不要錯(cuò)過了哈,希望本例對(duì)你學(xué)習(xí)ajax有所幫助2013-02-02
JS加密插件CryptoJS實(shí)現(xiàn)的Base64加密示例
這篇文章主要介紹了JS加密插件CryptoJS實(shí)現(xiàn)的Base64加密,結(jié)合實(shí)例形式分析了CryptoJS進(jìn)行base64加密的簡單實(shí)現(xiàn)技巧,需要的朋友可以參考下2018-08-08
JavaScript簡單實(shí)現(xiàn)動(dòng)態(tài)改變HTML內(nèi)容的方法示例
這篇文章主要介紹了JavaScript簡單實(shí)現(xiàn)動(dòng)態(tài)改變HTML內(nèi)容的方法,結(jié)合實(shí)例形式分析了javascript簡單獲取及修改HTML元素的相關(guān)操作技巧,非常簡單易懂,需要的朋友可以參考下2018-12-12
JavaScript和JQuery的鼠標(biāo)mouse事件冒泡處理
這篇文章主要介紹了JavaScript和JQuery的鼠標(biāo)mouse事件冒泡處理,本文總結(jié)出了mouse事件的一些定論,并分別給出了JavaScript和JQuery測試代碼,需要的朋友可以參考下2015-06-06
Javascript中獲取瀏覽器類型和操作系統(tǒng)版本等客戶端信息常用代碼
跟蹤一些最基本的客戶端訪問信息,這里將一些公用的代碼總結(jié)下來,需要的朋友可以參考下2016-06-06
BootStrapTable 單選及取值的實(shí)現(xiàn)方法
學(xué)習(xí)bootstrapTable 一直沒有找到 單選框的選定的和取值的教程,接下來通過本文給大家分享BootStrapTable 單選及取值的實(shí)現(xiàn)方法,非常不錯(cuò),需要的朋友參考下2017-01-01

