一、承载一切的“硬币板” Board.lua
(1)ctor
承接上篇,游戏核心核心内容与玩法都集中在了这块Board上,我们直接上其ctor分代码先:
local Levels = import("..data.Levels")
local Coin = import("..views.Coin")
local Board = class("Board", function()
return display.newNode()
end)
local NODE_PADDING = 100 --半透明的正方形方块的边长
local NODE_ZORDER = 0 --半透明方块的Z层数
local COIN_ZORDER = 1000 --银币的Z层数
function Board:ctor(levelData)
cc.GameObject.extend(self):addComponent("components.behavior.EventProtocol"):exportMethods()
--从指定的图像文件创建并返回一个批量渲染对象。
--只要绘制的图像在指定的图像文件中,无论绘制多少图像只用了 1 次 OpenGL draw call
--反正就是提高机器效率的一种方法,但之后的精灵添加方式有点改变
self.batch = display.newBatchNode(GAME_TEXTURE_IMAGE_FILENAME)
self.batch:setPosition(display.cx, display.cy)
self:addChild(self.batch)
-- 捕获数据
self.grid = clone(levelData.grid)
self.rows = levelData.rows
self.cols = levelData.cols
-- 硬币的集合,之后创建在硬币板上的硬币都应该存入该集合中(实际上是个 表 数据)
self.coins = {}
-- 属性:正在进行翻转动画的数量,这个属性在下面方法将被用到
self.flipAnimationCount = 0
-- 板的X,Y坐标的起始值 math.floor 向下取整
local offsetX = -math.floor(NODE_PADDING * self.cols / 2) - NODE_PADDING / 2
local offsetY = -math.floor(NODE_PADDING * self.rows / 2) - NODE_PADDING / 2
-- create board, place all coins
-- 创建板,并放置所有的硬币(包括半透明块)
for row = 1, self.rows do
local y = row * NODE_PADDING + offsetY
for col = 1, self.cols do
local x = col * NODE_PADDING + offsetX
-- 每个硬币都有一块 半透明块
local nodeSprite = display.newSprite("#BoardNode.png", x, y)
-- 因为上面用了display.newBatchNode(GAME_TEXTURE_IMAGE_FILENAME)
-- 所以添加精灵的方式有点不同了,把精灵添加到batch中,然后batch会一次性将所有精灵绘制
-- 又因为精灵将加进batch中,而且上面已经把batch的Position设置为场景的中点了
-- 现在相当于场景的中间坐标为(0.0)
self.batch:addChild(nodeSprite, NODE_ZORDER) -- 加入batch
-- 银币是放置在半透明块之上的
local node = self.grid
-- 如果node不是代表“空”,则创建硬币
if node ~= Levels.NODE_IS_EMPTY then
local coin = Coin.new(node)
coin:setPosition(x, y)
coin.row = row
coin.col = col
self.grid = coin -- 用新创建出来的硬币替换grid对应位置的上的值
self.coins#self.coins + 1] = coin -- 将硬币放入集合中
self.batch:addChild(coin, COIN_ZORDER) -- 加入batch
print("("..x,y..")") -- 注意:这句代码是我自己加的!
end
end
end
-- 设置监听器
self:setNodeEventEnabled(true)
self:setTouchEnabled(true)
self:addNodeEventListener(cc.NODE_TOUCH_EVENT, function(event)
return self:onTouch(event.name, event.x, event.y)
end)
end
```
注意点:
1、ctor里使用了批量渲染对象NodeBatch ,使用它有一个好处就绘图提高效率,但在使用时需要注意,之后绘制的一切都应该添加到批量渲染对象中,然后批量渲染对象会一次将它内部的图像绘制到场景中。 同时,相对坐标有点改变,以批量渲染对象为中心(请结合上面的代码注释理解)
2、内置属性,ctor里增加了五个属性:
self.grid = clone(levelData.grid)
self.rows = levelData.rows
self.cols = levelData.cols
self.coins = {}
self.flipAnimationCount = 0
```
其中grid属性用法要格外留意,期初的作用是捕获传入参数里的grid,是像这样的:
grid = {
{1, 0, 0, 1},
{0, 1, 1, 0},
{0, 1, 1, 0},
{1, 0, 0, 1}
}
之后依靠捕获的到参数去创建出不同状态的硬币(正面或者反面)local node = self.grid,
创建后,又将硬币去替换grid对应位置的值 self.grid = coin,请展开想象力,它应该变成这样:
3、布局的方式,因为批量渲染对象的缘故,场景的中心已经相当于坐标(0,0)了,解析太难,所以我上课时手工画了图(我上课可没有睡觉哦:2:):
这幅图解决了很多我想要解释的东西,希望大家看得懂(小正方形里面的除了坐标外,那个数字是绘制的顺序)
其实一开始我也看不太懂,是依靠添加的 print("("..x,y..")") 这句代码去了解到各个方块的坐标,进而明白整个布局的
4、设置触摸监听器
(2)Board.lua 里的方法
1、onTouch
既然设置的触摸监听器,必定有回调函数,代码如下
function Board:onTouch(event, x, y)
if event ~= "began" or self.flipAnimationCount > 0 then return end
local padding = NODE_PADDING / 2
-- 遍历硬币集合里的所有硬币
for _, coin in ipairs(self.coins) do
-- 取得coin的x,y坐标,但还是相对batch而已的坐标值
local cx, cy = coin:getPosition()
-- 将其转化为正常场景的坐标值
cx = cx + display.cx
cy = cy + display.cy
--判断点击位置是否在该硬币的范围内
if x >= cx - padding
and x <= cx + padding
and y >= cy - padding
and y <= cy + padding then
-- 如果是则执行翻转
self:flipCoin(coin, true)
break
end
end
end
```
遍历硬币集合里面的所有硬币,并将硬币的坐标转化为相对场景的坐标,再判断点击的点是否在硬币的范围为,若是的话则执行翻转
2、flipCoin 翻转硬币
-- 翻转硬币,被点击的硬币与它四个方向相邻的硬币都会翻转
function Board:flipCoin(coin, includeNeighbour)
if not coin or coin == Levels.NODE_IS_EMPTY then return end
--属性:正在进行翻转动画的数量 +1
self.flipAnimationCount = self.flipAnimationCount + 1
-- 执行翻转动作(传入参数是一个函数)
coin:flip(function()
self.flipAnimationCount = self.flipAnimationCount - 1
-- 重新设置硬币的Z层数,防止这个硬币之前是被点击过Z层数是+1的
self.batch:reorderChild(coin, COIN_ZORDER)
-- 属性:正在进行翻转动画的数量 为0 证明翻转都结束了
if self.flipAnimationCount == 0 then
--每次翻转完就检测一次游戏是否结束
self:checkLevelCompleted()
end
end)
-- 如果includeNeighbour为true才会使四个方向的硬币翻转
if includeNeighbour then
-- 播放特效声
audio.playSound(GAME_SFX.flipCoin)
-- 改变点击中的硬币的Z层数,向上加一层,是为四周执行放大效果的硬币将其遮掩
self.batch:reorderChild(coin, COIN_ZORDER + 1)
-- 延迟0.25s才执行
self:performWithDelay(function()
--这四个flipCoin就没有设置includeNeighbour这个参数了
self:flipCoin(self:getCoin(coin.row - 1, coin.col)) --下
self:flipCoin(self:getCoin(coin.row + 1, coin.col)) --上
self:flipCoin(self:getCoin(coin.row, coin.col - 1)) --左
self:flipCoin(self:getCoin(coin.row, coin.col + 1)) --右
end, 0.25)
end
end
```
3、getCoin 得到硬币
-- 得到指定行列的硬币
function Board:getCoin(row, col)
-- 注意:在ctor中,已经把创建的Coin与在grid对应的值替换了,所以可以用这种方法得到
if self.grid then
return self.grid
end
end
```
4、checkLevelCompleted 检测关卡完成
-- 检测函数,检测该是否关卡通关,所有硬币coin的isWhite属性为true时通关
function Board:checkLevelCompleted()
local count = 0
-- 遍历所有硬币
for _, coin in ipairs(self.coins) do
if coin.isWhite then count = count + 1 end
end
-- 当count的数值与硬币集合里的值相等时证明游戏完成
if count == #self.coins then
-- completed
self:setTouchEnabled(false)
self:dispatchEvent({name = "LEVEL_COMPLETED"})
end
end
```
这样,Board.lua 基本就了解完了
二、Board.lua的使用
回到PlayLevelScene.lua,找到下面使用Board.lua的代码:
self.board = Board.new(Levels.get(levelIndex))
self.board:addEventListener("LEVEL_COMPLETED", handler(self, self.onLevelCompleted))
self:addChild(self.board)
```
使用很简单,但其中调用到一个方法我们还没讲到就是:onLevelCompleted
-- 游戏结束响应函数
function PlayLevelScene:onLevelCompleted()
-- 播放特效声
audio.playSound(GAME_SFX.levelCompleted)
-- 胜利横幅
local dialog = display.newSprite("#LevelCompletedDialogBg.png")
dialog:setPosition(display.cx, display.top + dialog:getContentSize().height / 2 + 40)
self:addChild(dialog)
-- 胜利横幅的动作
transition.moveTo(dialog, {time = 0.7, y = display.top - dialog:getContentSize().height / 2 - 40, easing = "BOUNCEOUT"})
end
```
当关卡完成通关时,就为这个方法,一个下降的胜利横幅:
下篇我们再继续该游戏的最后一部分:ChooseLevelScene




