cocos2dx 製作一個簡單的三消遊戲
阿新 • • 發佈:2019-01-06
很多時候在學習或者嘗試做遊戲的時候總是會無疾而終,現在把自己的收穫和做出來的示例記下來給自己一個督促吧。
平臺環境: cocos2dx(3.8)+lua vs2012
關於三消遊戲的認識
基礎的三消遊戲是在一個二維空間上,放置不同的元素,通過滑動消除相鄰的幾個相同元素,來獲得積分或過關。當然完整的上線專案會有很多玩法,像是一次消除多個會產生一個炸彈,或者地圖上有各種的障礙,甚至有的遊戲像best friends是通過連線臨近相同元素來進行消除。
期望的效果
這次的一個小嚐試期望做出的效果只是在一個二維的空間中,隨機幾種不同顏色的元素進行填充,接受玩家的滑動操作,能夠檢測相鄰三個以上的相同元素進行消除,並且判斷當無法有滑動消除的情況時,會隨機交換幾個元素的位置重新繼續。暫時不包含其他的玩法。
系統設計
需要的物件
- 二維介面中的元素,我命名為物件SXBlock,本身儲存有所處的行和列,擁有一個sprite,並且實現了sprite需要的各個動畫效果;
- 遊戲世界,在cocos2dx中我以一個layer實現,儲存了二維的陣列儲存所有SXBlock,同時監聽玩家的輸入,控制遊戲狀態的轉換,同時遊戲的大部分演算法都在這裡實現。
遊戲中的各個狀態
我把整個三消流程分為四個狀態:
- 填充:當二維陣列有空位時,其他的元素往下移,同時從上方隨機出新的元素掉落進行填充。
- 檢測是否滿足消除條件並消除。
- 接受玩家輸入,並表現輸入動畫:只有在此狀態下玩家的操作才有效果。
- 檢測是否無法再消除,是的話隨機交換幾個元素的位置。
狀態之間的轉換如下圖示:
演算法需求
在實現過程中,考慮到的一些核心的演算法總共有三個:
1. 當前玩家滑動操作是否有效
根據玩家輸入獲得的兩個相鄰元素,判斷交換位置後是否能夠達到消除條件,lua程式碼實現如下:
function UIMainLayer:FunGetRowSameBlocks( row, col )
local ltabBlocks = {}
local block = self:FunGetBlock( row, col )
if not block then return {} end
table.insert( ltabBlocks, block )
for i = col - 1, 1, -1 do
if block:FunSameType( self:FunGetBlock( row, i ) ) then
table.insert( ltabBlocks, self:FunGetBlock( row, i ) )
else
break
end
end
for i = col + 1, NUM_COL do
if block:FunSameType( self:FunGetBlock( row, i ) ) then
table.insert( ltabBlocks, self:FunGetBlock( row, i ) )
else
break
end
end
return ltabBlocks
end
function UIMainLayer:FunGetColSameBlocks( row, col )
local ltabBlocks = {}
local block = self:FunGetBlock( row, col )
if not block then return {} end
table.insert( ltabBlocks, block )
for i = row - 1, 1, -1 do
if block:FunSameType( self:FunGetBlock( i, col ) ) then
table.insert( ltabBlocks, self:FunGetBlock( i, col ) )
else
break
end
end
for i = row + 1, NUM_ROW do
if block:FunSameType( self:FunGetBlock( i, col ) ) then
table.insert( ltabBlocks, self:FunGetBlock( i, col ) )
else
break
end
end
return ltabBlocks
end
-- 判斷
local ltabRowSame1 = self:FunGetRowSameBlocks( block1.mvarRow, block1.mvarCol )
local ltabRowSame2 = self:FunGetRowSameBlocks( block2.mvarRow, block2.mvarCol )
local ltabColSame1 = self:FunGetColSameBlocks( block1.mvarRow, block1.mvarCol )
local ltabColSame2 = self:FunGetColSameBlocks( block2.mvarRow, block2.mvarCol )
if #ltabRowSame1 >= 3 or #ltabRowSame2 >= 3 or #ltabColSame1 >= 3 or #ltabColSame2 >= 3 then
-- 達到條件可以交換
else
-- 未達到
end
2. 空間填滿元素後是否結束,即無法再消除;
符合的條件如下所示,當如下相鄰位置元素型別一樣時,可以消除
判斷邏輯如下:
function UIMainLayer:FunCheckIsFixCon3R2C( row, col )
-- 5 6
-- 3 4
-- 1 2
local ltabType = {}
for r = row, row + 2 do
for c = col, col + 1 do
table.insert( ltabType, self:FunGetBlock( r, c ):FunGetType() )
end
end
if not (ltabType[1] == ltabType[3] and ltabType[1] == ltabType[6]) then
if not (ltabType[1] == ltabType[4] and ltabType[1] == ltabType[5]) then
if not (ltabType[2] == ltabType[3] and ltabType[2] == ltabType[5]) then
if not (ltabType[1] == ltabType[4] and ltabType[1] == ltabType[6]) then
if not (ltabType[2] == ltabType[3] and ltabType[2] == ltabType[6]) then
if not (ltabType[2] == ltabType[4] and ltabType[2] == ltabType[5]) then
return false
end
end
end
end
end
end
return true
end
function UIMainLayer:FunCheckIsFixCon2R3C( row, col )
-- 4 5 6
-- 1 2 3
local ltabType = {}
for r = row, row + 1 do
for c = col, col + 2 do
table.insert( ltabType, self:FunGetBlock( r, c ):FunGetType() )
end
end
if not (ltabType[3] == ltabType[4] and ltabType[3] == ltabType[5]) then
if not (ltabType[2] == ltabType[4] and ltabType[2] == ltabType[6]) then
if not (ltabType[1] == ltabType[5] and ltabType[1] == ltabType[6]) then
if not (ltabType[2] == ltabType[3] and ltabType[2] == ltabType[4]) then
if not (ltabType[1] == ltabType[3] and ltabType[1] == ltabType[5]) then
if not (ltabType[1] == ltabType[2] and ltabType[1] == ltabType[6]) then
return false
end
end
end
end
end
end
return true
end
function UIMainLayer:FunCheckIsEnd()
for r = 1, NUM_ROW - 2 do
for c = 1, NUM_COL - 1 do
if self:FunCheckIsFixCon3R2C( r, c ) then
return false
end
end
end
for r = 1, NUM_ROW - 1 do
for c = 1, NUM_COL - 2 do
if self:FunCheckIsFixCon2R3C( r, c ) then
return false
end
end
end
return true
end
3. 無法消除時,隨機交換幾個元素位置達能夠繼續消除。
程式碼如下示:
function UIMainLayer:FunGetRandomGroups()
local lvarNum = MAX_RANDOM_GROUP_WHEN_END * 2
local ltabIndex = {}
while(#ltabIndex<lvarNum) do
local lvarIndex = math.random(1, NUM_ROW*NUM_COL)
local lvarIsIn = false
for k, v in pairs( ltabIndex ) do
if v == lvarIndex then
lvarIsIn = true
break
end
end
if not lvarIsIn then
table.insert( ltabIndex, lvarIndex )
end
end
local ltabBlocks = {}
for k, v in pairs( ltabIndex ) do
table.insert( ltabBlocks, self.mtabBlocks[math.floor((v-1)/NUM_COL)+1][(v-1)%NUM_COL+1])
end
return ltabBlocks
end
function UIMainLayer:FunRandomSwap()
local function SwapGroup( ltabBlocks, lvarCover )
for i = 1, MAX_RANDOM_GROUP_WHEN_END do
local block1 = ltabBlocks[i*2 - 1]
local block2 = ltabBlocks[i*2]
self.mtabBlocks[block2.mvarRow][block2.mvarCol] = lvarCover and block2 or block1
self.mtabBlocks[block1.mvarRow][block1.mvarCol] = lvarCover and block1 or block2
end
end
local ltabRandomBlocks = self:FunGetRandomGroups()
SwapGroup( ltabRandomBlocks, false )
if not self:FunCheckIsEnd() then
-- 交換位置
return
else
-- 仍然不符合
SwapGroup( ltabRandomBlocks, true )
end
end
總結
這幾天的嘗試,實現了這個簡單的三消demo,但是在玩法上並沒有什麼樂趣…
還有一個可能出現的問題如果總行,列比較少會造成如何隨機都不能消除的bug,導致一直卡在第四個狀態。
暫時到這裡吧,有進一步完善的話後續新增。
完整的可玩demo和lua程式碼在此下載 。