Quick-cocos2d-x-3.2中示例Coinfilp解析(四)

一、承载一切的“硬币板” 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

顶起来,呵呵

支持,:2::2::2::2:

楼主好用心,还画了图解

因为我觉得手工画图比我用电脑制图块点,就偷懒了 :14:

:2::2:

图应该是这样的吧

没错,你才是对的,我抄写的顺序错了。谢谢

楼主的文章写得真心不错,很多时候我也打算写来着,但写到一半就感觉没法动笔了,很多东西懂了但就是不知道怎么描述出来,总是找不到点,以后还要多向楼主学习

其实写了前两篇后就是靠必须写完它的压力继续写下去的,都是边理解边写的,有压力才有动力。如果是之前,我应该都不去理会那些排列的具体数值之类,看过就算了