之前学习过c++在cocos2d-x游戏引擎上实现的消灭星星游戏,为了熟悉quick cocos2d-x以及Lua语言,我使用2.1.4版本的quick cocos2d-x重写了消灭星星,不过只是实现了其基本的消除,移动,计分以及过关等功能,一些文字漂移、粒子特效等功能有待以后完善。
该游戏的界面非常简单,只有两个场景的切换,首先是一个开始场景,该场景下有一个背景图片以及一个按钮,点击该按钮可以进入到游戏场景界面。开始场景的主要实现代码如下:
1 function MainScene:ctor() 2 self.bg = display.newSprite("bg_menuscene.jpg", display.cx,display.cy) 3 self:addChild(self.bg) 4 local item ={} 5 item[1]= ui.newImageMenuItem({image = "menu_start.png", imageSelected = "menu_start.png", 6 listener = function() 7 game.enterGameScene() end , x = display.cx, y = display.cy}) 8 local menu = ui.newMenu(item) 9 self:addChild(menu) 10 end
其中菜单按钮用table来实现,在Lua语言中没有其他的数据结构,只有table,利用table可以实现各种数据结构,下面讲解星星的消除算法的时候会详细介绍,这里的按钮事件是game.enterGameScene(),这是在game文件中实现的一个函数,用于进入游戏场景,在game文件中game被申明为一个全局的table变量,可以在其他的文件中使用。
在游戏场景上面添加了一个层,该层用来实现星星矩阵的初始化显示,分数菜单的显示以及触摸事件。我们先看星星矩阵的初始化:
1 local STAR_RES_LIST = {"blue.png","green.png", 2 "orange.png","red.png","purple.png"} 3 4 function MatrixStar:initMatrix() 5 --[[self.STAR[i][j]是一个表,其中i表示星星矩阵的行,j表示列,它包含四个元素 6 self.STAR[i][j][1]表示星星精灵 7 self.STAR[i][j][2]表示该精灵的颜色 8 self.STAR[i][j][3]表示该精灵是否被选中 9 self.STAR[i][j][4]表示该精灵的x轴坐标 10 self.STAR[i][j][5]表示该精灵的y轴坐标 11 ]] 12 math.randomseed(os.time()) 13 for row = 1, ROW do 14 local y = (row-1) * STAR_HEIGHT + STAR_HEIGHT/2 15 self.STAR[row] = {} 16 for col = 1, COL do 17 self.STAR[row][col] = {} 18 local x = (col-1) * STAR_WIDTH + STAR_WIDTH/2 19 local i=math.random(1,5) 20 local star = display.newSprite(STAR_RES_LIST[i]) 21 self.STAR[row][col][1] = star 22 self.STAR[row][col][2] = i 23 self.STAR[row][col][3] = false 24 star:setPosition(x,y) 25 self.STAR[row][col][4] = x 26 self.STAR[row][col][5] = y 27 self:addChild(star) 28 end 29 end 30 end
这里利用随机函数,在for循环中生成了不同颜色的星星精灵表 STAR_RES_LIST记录了六种颜色的星星精灵,使用表self.STAR[i][j]记录了每一个星星的颜色、位置、是否被选中,这些信息用于后面对星星的消除算法,在C++中我们可以使用一个结构体来定义这些变量,而Lua中使用的是table. 分数的菜单使用了3个Label对象来实现的,分别记录了最高分数,目标分数以及当前分数,并且通过设置TAG来方便以后对该Label对象实现分数更新。
1 HSCORETAG = 100 2 LEVELTAG = 101 3 CSCORETAG = 102 4 5 function MatrixStar:setLabel(Hscore,Level,Goal,Cscore) 6 local HscoreUI = ui.newTTFLabel({ 7 text = string.format("HighestScore: %s", tostring(Hscore)), 8 x, y = display.left, display.top, 9 }) 10 HscoreUI:setScale(SCALE) 11 HscoreUI:setPosition(display.right, display.cy) 12 HscoreUI:setPosition(display.cx, display.top - SCALE * HscoreUI:getContentSize().height) 13 self:addChild(HscoreUI) 14 HscoreUI:setTag(HSCORETAG) 15 16 local LevelUI = ui.newTTFLabel({ 17 text = string.format("Level: %s".." ".."Goal: %s", tostring(Level),tostring(Goal)), 18 x, y = display.left, display.top, 19 }) 20 LevelUI:setScale(SCALE) 21 LevelUI:setPosition(display.cx, display.top - SCALE * (HscoreUI:getContentSize().height + 22 LevelUI:getContentSize().height)) 23 self:addChild(LevelUI) 24 LevelUI:setTag(LEVELTAG) 25 26 local CscoreUI = ui.newTTFLabel({ 27 text = string.format("CurrentScore: %s", tostring(Cscore)), 28 x, y = display.left, display.top, 29 }) 30 CscoreUI:setScale(SCALE) 31 CscoreUI:setPosition(display.cx, display.top - SCALE * (HscoreUI:getContentSize().height + 32 LevelUI:getContentSize().height + CscoreUI:getContentSize().height)) 33 self:addChild(CscoreUI) 34 CscoreUI:setTag(CSCORETAG) 35 end
接下来是实现触摸事件,在触摸事件中要将点中的星星和他周围与他颜色相同的星星消除,这里先用一个table记录选中的相同颜色星星个数,每次点击都要先将其设置为空。并且使用一个table来帮助选出周围与触摸星星颜色相同的星星,当触摸到一个星星后,将该星星插入到该表中,然后遍历它四周的星星是否与该星星相同,相同则插入表中。然后将表中的第一个元素从表中移除,接着对表中的元素进行上述操作,值到表为空为止。这个过程实际上就是利用队列来实现一个广度优先算法。具体代码如下:
1 local travel = {} --当作一个队列使用,用于选出周围与触摸星星颜色相同的星星 2 if self.STAR[i][j][1] == nil then 3 return 4 end 5 6 table.insert(travel, {self.STAR[i][j][1], i, j}) 7 while #travel ~= 0 do 8 if i + 1 <= ROW and self.STAR[i][j][3] ~= true and 9 self.STAR[i][j][2] == self.STAR[i + 1][j][2] then 10 table.insert(travel, {self.STAR[i+1][j][1],i+1,j}) 11 end 12 13 if i-1 >= 1 and self.STAR[i][j][3] ~= true and 14 self.STAR[i][j][2] ==self.STAR[i-1][j][2] then 15 table.insert(travel, {self.STAR[i-1][j][1],i-1,j}) 16 end 17 18 if j+1 <= COL and self.STAR[i][j][3] ~= true and 19 self.STAR[i][j][2] ==self.STAR[i][j+1][2] then 20 table.insert(travel, {self.STAR[i][j+1][1],i,j+1}) 21 end 22 23 if j-1 >= 1 and self.STAR[i][j][3] ~= true and 24 self.STAR[i][j][2] ==self.STAR[i][j-1][2] then 25 table.insert(travel, {self.STAR[i][j-1][1],i,j-1}) 26 end 27 28 if self.STAR[i][j][3] ~= true then 29 self.STAR[i][j][3] = true 30 table.insert(self.SELECT_STAR,{self.STAR[i][j][1],i,j}) 31 end 32 33 table.remove(travel,1) --table没有类似双向队列的功能直接删除第一个元素 34 if #travel ~= 0 then 35 i, j = travel[1][2], travel[1][3] --取出表的第一个元素 36 end 37 end
在C++的deque容器可以在O(1)的时间复杂度中将队头元素移除,而这里的table.remove的时间复杂度为O(n),不知道是否有更好的
方法实现。当我们得到选中的星星后便可以更新分数,将选中的星星删除同时更新剩余星星的位置。位置的更新主要涉及垂直方向与水平方向。先看垂直方向,当我们删除部分星星时,这些星星上面的星星自然要掉下来,我们用掉下来的星星信息覆盖已删除星星的信息,并且将掉下来的星星信息设为nil。再看水平方向,当有一列的星星全部删除时,我们要求该列右边的星星能自动向左移动,实现过程与垂直方向类似,源代码如下:
1 function MatrixStar:UpdateMatrix() 2 for i = 1, ROW do 3 for j = 1,COL do 4 if self.STAR[i][j][1] == nil then 5 local up = i 6 local dis = 0 7 while self.STAR[up][j][1] == nil do 8 dis = dis + 1 9 up = up + 1 10 if(up>ROW) then 11 break 12 end 13 end 14 15 for begin_i = i + dis, ROW do 16 if self.STAR[begin_i][j][1]~=nil then 17 self.STAR[begin_i-dis][j][1]=self.STAR[begin_i][j][1] 18 self.STAR[begin_i-dis][j][2]=self.STAR[begin_i][j][2] 19 self.STAR[begin_i-dis][j][3]=self.STAR[begin_i][j][3] 20 local x = (j-1)*STAR_WIDTH + STAR_WIDTH/2 21 local y = (begin_i-dis-1)*STAR_HEIGHT + STAR_HEIGHT/2 22 self.STAR[begin_i-dis][j][4] = x 23 self.STAR[begin_i-dis][j][5] = y 24 self.STAR[begin_i][j][1] = nil 25 self.STAR[begin_i][j][2] = nil 26 self.STAR[begin_i][j][3] = nil 27 self.STAR[begin_i][j][4] = nil 28 self.STAR[begin_i][j][5] = nil 29 end 30 end 31 end 32 end 33 end 34 35 for j = 1, COL do 36 if self.STAR[1][j][1] == nil then 37 local des = 0 38 local right = j 39 while self.STAR[1][right][1] == nil do 40 des = des + 1 41 right = right + 1 42 if right>COL then 43 break 44 end 45 end 46 for begin_i = ROW, 1,-1 do 47 for begin_j = j + des, COL do 48 if self.STAR[begin_i][begin_j][1] ~= nil then 49 self.STAR[begin_i][begin_j-des][1]=self.STAR[begin_i][begin_j][1] 50 self.STAR[begin_i][begin_j-des][2]=self.STAR[begin_i][begin_j][2] 51 self.STAR[begin_i][begin_j-des][3]=self.STAR[begin_i][begin_j][3] 52 local x = (begin_j-des-1)*STAR_WIDTH + STAR_WIDTH/2 53 local y = (begin_i-1)*STAR_HEIGHT + STAR_HEIGHT/2 54 self.STAR[begin_i][begin_j-des][4] = x 55 self.STAR[begin_i][begin_j-des][5] = y 56 self.STAR[begin_i][begin_j][1] = nil 57 self.STAR[begin_i][begin_j][2] = nil 58 self.STAR[begin_i][begin_j][3] = nil 59 self.STAR[begin_i][begin_j][4] = nil 60 self.STAR[begin_i][begin_j][5] = nil 61 end 62 end 63 end 64 end 65 end 66 end
星星的位置信息通过上述代码得到更新,我们通过设置Update事件,在每帧中更新星星的位置,为了有一个移动的效果,我们不是直接使用setPostion到目的位置,而是使用一个速度参数使其移动到目的位置。
1 function MatrixStar:updatePos(posX,posY,i,j) 2 if posY ~= self.STAR[i][j][5] then 3 self.STAR[i][j][1]:setPositionY(self.STAR[i][j][5] - MOVESPEED) 4 if self.STAR[i][j][1]:getPositionY() < self.STAR[i][j][5] then 5 self.STAR[i][j][1]:setPositionY(self.STAR[i][j][5]) 6 local x, y = self.STAR[i][j][1]:getPosition() 7 end 8 end 9 10 if posX ~= self.STAR[i][j][4] then 11 self.STAR[i][j][1]:setPositionX(self.STAR[i][j][4] - MOVESPEED) 12 if self.STAR[i][j][1]:getPositionX() < self.STAR[i][j][4] then 13 self.STAR[i][j][1]:setPositionX(self.STAR[i][j][4]) 14 end 15 end 16 end
整个游戏的基本代码就这些,代码写的比较乱,有待改进,但还是能跑起来,源代码地址:https://github.com/zhulong890816/xinxin