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

一、最後的前言

依舊使用久方法:順藤摸瓜,一點一點發現,打開ChooseLevelScene.lua吧

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
local AdBar = import("..views.AdBar")
local LevelsList = import("..views.LevelsList")
  
local ChooseLevelScene = class("ChooseLevelScene", function()
    return display.newScene("ChooseLevelScene")
end)
  
function ChooseLevelScene:ctor()
    -- 背景
    local bg = display.newSprite("#OtherSceneBg.png")
    -- make background sprite always align top 用於對齊頂部
    bg:setPosition(display.cx, display.top - bg:getContentSize().height / 2)
    self:addChild(bg)
      
    -- 標題
    local title = display.newSprite("#Title.png", display.cx, display.top - 100)
    self:addChild(title)
      
    -- 信息條
    local adBar = AdBar.new()
    self:addChild(adBar)
  
    -- create levels list 創建等級列表
    local rect = cc.rect(display.left, display.bottom + 180, display.width, display.height - 280)
    self.levelsList = LevelsList.new(rect)
    self.levelsList:addEventListener("onTapLevelIcon", handler(self, self.onTapLevelIcon))
    self:addChild(self.levelsList)
  
    -- 後退按鈕
    cc.ui.UIPushButton.new({normal = "#BackButton.png", pressed = "#BackButtonSelected.png"})
        :align(display.CENTER, display.right - 100, display.bottom + 120)
        :onButtonClicked(function()
            app:enterMenuScene()
        end)
        :addTo(self)
end
  
function ChooseLevelScene:onTapLevelIcon(event)
    audio.playSound(GAME_SFX.tapButton)
    app:playLevel(event.levelIndex)
end
  
function ChooseLevelScene:onEnter()
    self.levelsList:setTouchEnabled(true)
end
  
return ChooseLevelScene

不難,唯一需要繼續深入的就是這段代碼:

1
2
3
4
5
-- create levels list 創建等級列表
    local rect = cc.rect(display.left, display.bottom + 180, display.width, display.height - 280)
    self.levelsList = LevelsList.new(rect)
    self.levelsList:addEventListener("onTapLevelIcon", handler(self, self.onTapLevelIcon))
self:addChild(self.levelsList)


其實,想要順着代碼往看下,我們將會把要這個遊戲剩下的五個腳本關聯起來:

1
2
3
4
5
LevelsList.lua
LevelsListCell.lua
PageControl.lua
ScrollView.lua
ScrollViewCell.lua

只要明白了這五個腳本,就懂得了該遊戲的關卡選擇場景是如何建立的,進而整個遊戲也就完整了解了一遍。先讓大家瞭解下我對這最後的五個腳本理解的關係:

  • LevelsList繼承自→PageControl繼承自→ScrollView

  • LevelsListCell繼承自→ScrollViewCell

  • LevelsList在內部使用了LevelsListCell

讓我們先從簡單的講起,就是cell(單元),先明白,該遊戲運用時,一個頁面就是一個cell(單元),如下圖:

56_371891_6b9ab3597cef249.png

包含了1到16有一個看不見的矩形,它就一個cell(單元),你向左滑動一下就會進入下一個cell(單元):17到32


二、cell(單元) 

從ScrollViewCell.lua開始:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
local ScrollViewCell = class("ScrollViewCell", function(contentSize)
    local node = display.newNode() -- 該類基礎只是一個節點
    if contentSize then node:setContentSize(contentSize) end    -- 根據傳入參入確立該節點的ContentSize
    node:setNodeEventEnabled(true)  --設置該節點可以 爲特定事件設置處理函數
    cc(node):addComponent("components.behavior.EventProtocol"):exportMethods()
    return node
end)
  
function ScrollViewCell:onTouch(event, x, y)
end
  
function ScrollViewCell:onTap(x, y)
end
  
-- 退出時調用
function ScrollViewCell:onExit()
    --移除所用的事件響應事件
    self:removeAllEventListeners()
end
  
return ScrollViewCell

ScrollViewCell.lua並不複雜,因爲它只是作爲一個“骨架”存在,很多功能並沒有實現,所以需要我們再去擴展,所以有了LevelsListCell.lua,一定有人覺得爲什麼不直接寫一個LevelsListCell.lua就夠了呢?反正我們就擴展一個類而已,幹嘛要“骨架”呢?  這我回答不好,只能說這樣編程遵循了良好的設計模式,對以後程序的改良有很大的好處。


回到正題,進入LevelsListCell.lua:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
local ScrollViewCell = import("..ui.ScrollViewCell")
  
-- 繼承自ScrollViewCell
local LevelsListCell = class("LevelsListCell", ScrollViewCell)
  
  
function LevelsListCell:ctor(size, beginLevelIndex, endLevelIndex, rows, cols)
      
    local rowHeight = math.floor((display.height - 340) / rows)   -- 每行的高度
    local colWidth = math.floor(display.width * 0.9 / cols)       -- 每列的寬度
      
    -- 使用了批量渲染對象,在Board.lua也用過
    local batch = display.newBatchNode(GAME_TEXTURE_IMAGE_FILENAME)
    self:addChild(batch)
    self.pageIndex = pageIndex
    -- button集合
    self.buttons = {}
  
    -- 第一個將要加入的button的X,Y座標
    local startX = (display.width - colWidth * (cols - 1)) / 2
    local y = display.top - 220
      
    -- 捕獲傳入參數beginLevelIndex
    local levelIndex = beginLevelIndex
  
    -- 添加button,每個cell(單元)是16個按鈕哦
    for row = 1, rows do
        local x = startX
        for column = 1, cols do
            -- button的一系列處理
            local icon = display.newSprite("#LockedLevelIcon.png", x, y)
            batch:addChild(icon)
            icon.levelIndex = levelIndex
            self.buttons[#self.buttons + 1] = icon 
              
            -- 等級標籤
            local label = cc.ui.UILabel.new({
                UILabelType = 1,
                text  = tostring(levelIndex),
                font  = "UIFont.fnt"
            })
            :align(cc.ui.TEXT_ALIGN_CENTER, x, y - 4)
            self:addChild(label)
              
            -- 處理完一個button後,重置數據,爲添加下一個button做準備           
            x = x + colWidth
            levelIndex = levelIndex + 1
            if levelIndex > endLevelIndex then break end
        end
  
        y = y - rowHeight
        if levelIndex > endLevelIndex then break end
    end
  
    -- add highlight level icon
    self.highlightButton = display.newSprite("#HighlightLevelIcon.png")
    self.highlightButton:setVisible(false)
    self:addChild(self.highlightButton,100)
end
  
-- 觸摸響應函數
function LevelsListCell:onTouch(event, x, y)
    if event == "began" then
        local button = self:checkButton(x, y)
        if button then
            self.highlightButton:setVisible(true)
            self.highlightButton:setPosition(button:getPosition())
        end
    elseif event ~= "moved" then
        self.highlightButton:setVisible(false)
    end
end
  
-- 觸摸敲中button的響應函數
function LevelsListCell:onTap(x, y)
    local button = self:checkButton(x, y)
    if button then
        self:dispatchEvent({name = "onTapLevelIcon", levelIndex = button.levelIndex})
    end
end
  
-- 獲取傳入參數X,Y座標所對應的button
function LevelsListCell:checkButton(x, y)
    local pos = cc.p(x, y)
    -- 遍歷所有的button
    for i = 1, #self.buttons do
        local button = self.buttons<i>
        -- 如果傳入的點在該button的方位內,則返回該button
        if cc.rectContainsPoint(button:getBoundingBox(), pos) then
            return button
        end
    end
    return nil
end
  
return LevelsListCell

擴展後的Cell,在ctor中對Cell的視覺界面進行了排版和佈局,同時還有兩個響應函數:onTouch(event, x, y)和onTap(x, y);


這裏我要特別解釋一句代碼: self:dispatchEvent({name = "onTapLevelIcon", levelIndex = button.levelIndex}) 意思就委派一個Event,其中name屬性是必須,作爲事件的名字,但要觸發該事件時就需要用到,而其他屬性可以任意。這句代碼結束後就相當LevelsListCell 這個類有了一個Event,可以看做一個表event={{name = "onTapLevelIcon", levelIndex = button.levelIndex}}


三、滾動控件ScrollView.lua

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
local ScrollView = class("ScrollView"
-- 創建一個控件基礎的節點
function(rect)
    if not rect then rect = cc.rect(0, 0, 0, 0) end
    local node = display.newClippingRegionNode(rect)  -- 剪裁區域的節點
    node:setNodeEventEnabled(true)
    cc(node):addComponent("components.behavior.EventProtocol"):exportMethods()
    return node
end)
  
ScrollView.DIRECTION_VERTICAL   = 1    -- 垂直方向
ScrollView.DIRECTION_HORIZONTAL = 2    -- 水平方向
  
  
-- 參數:區域,拖動方向
function ScrollView:ctor(rect, direction)
    assert(direction == ScrollView.DIRECTION_VERTICAL or direction == ScrollView.DIRECTION_HORIZONTAL,
           "ScrollView:ctor() - invalid direction")
  
    self.dragThreshold = 40                 -- 拖動最小臨界值
    self.bouncThreshold = 140               -- 拖動到能翻頁的最小臨界值   
    self.defaultAnimateTime = 0.4           -- 默認動畫時間
    self.defaultAnimateEasing = "backOut"   -- 默認的ease
  
    self.direction = direction    -- 滾動方向
    self.touchRect = rect         -- 觸摸矩形
    self.offsetX = 0              -- X軸偏移量
    self.offsetY = 0              -- Y軸偏移量
    self.cells = {}               -- 單元集合
    self.currentIndex = 0         -- 當前索引(對應self.cells)
  
    -- 爲self的節點事件cc.NODE_EVENT 設置處理函數(返回一個id表示註冊成功)
    self:addNodeEventListener(cc.NODE_EVENT, function(event)
        if event.name == "enter" then
            self:onEnter()
        elseif event.name == "exit" then
            self:onExit()
        end
    end)
  
    -- create container layer  創建一個容器層
    self.view = display.newLayer()
    self:addChild(self.view)   
    -- 爲self.view的觸摸事件cc.NODE_TOUCH_EVENT 設置處理函數
    self.view:addNodeEventListener(cc.NODE_TOUCH_EVENT, function(event)
        return self:onTouch(event.name, event.x, event.y)
    end)
end
  
---- 一些簡單的對外接口函數
  
-- 得到當前索引所指向的單元
function ScrollView:getCurrentCell()
    if self.currentIndex > 0 then
        return self.cells[self.currentIndex]
    else
        return nil
    end
end
  
-- 得到當前索引
function ScrollView:getCurrentIndex()
    return self.currentIndex
end
  
-- 設置當前索引
function ScrollView:setCurrentIndex(index)
    self:scrollToCell(index)
end
  
-- 添加單元
function ScrollView:addCell(cell)
    self.view:addChild(cell)
    self.cells[#self.cells + 1] = cell
    self:reorderAllCells()
    self:dispatchEvent({name = "addCell", count = #self.cells})
end
  
-- 根據索引插入一個單元
function ScrollView:insertCellAtIndex(cell, index)
    self.view:addChild(cell)
    table.insert(self.cells, index, cell)
    self:reorderAllCells()
    self:dispatchEvent({name = "insertCellAtIndex", index = index, count = #self.cells})
end
  
-- 根據索引移除一個單元
function ScrollView:removeCellAtIndex(index)
    local cell = self.cells[index]
    cell:removeSelf()
    table.remove(self.cells, index)
    self:reorderAllCells()
    self:dispatchEvent({name = "removeCellAtIndex", index = index, count = #self.cells})
end
  
-- 得到容器層
function ScrollView:getView()
    return self.view
end
  
-- 得到可觸摸矩形
function ScrollView:getTouchRect()
    return self.touchRect
end
  
-- 設置可觸摸矩形
function ScrollView:setTouchRect(rect)
    self.touchRect = rect
    self:dispatchEvent({name = "setTouchRect", rect = rect})
end
  
-- 得到剪裁後的矩形
function ScrollView:getClippingRect()
    return self:getClippingRegion()
end
  
-- 設置剪裁後的矩形
function ScrollView:setClippingRect(rect)
    self:setClippingRegion(rect)
    -- 委派事件 ,名字:setClippingRect
    self:dispatchEvent({name = "setClippingRect", rect = rect})
end
  
-- 滾動cell(單元)  傳入參數,index當前頁碼, 後面三個爲跳轉Cell時執行動作的參數
function ScrollView:scrollToCell(index, animated, time, easing)
    -- cell(單元)總數
    local count = #self.cells
    if count < 1 then
        self.currentIndex = 0
        return
    end
      
    -- 對index進行判斷與處理
    if index < 1 then
        index = 1
    elseif index > count then
        index = count
    end
    self.currentIndex = index
      
    -- 根據滾動方向,設置偏移量
    local offset = 0
    for i = 2, index do
        local cell = self.cells[i - 1]
        local size = cell:getContentSize()
        if self.direction == ScrollView.DIRECTION_HORIZONTAL then
            offset = offset - size.width
        else
            offset = offset + size.height
        end
    end
  
    self:setContentOffset(offset, animated, time, easing)
    -- 委派事件
    self:dispatchEvent({name = "scrollToCell", animated = animated, time time, easing = easing})
end
  
-- 檢測容器層self.view是否允許觸摸
function ScrollView:isTouchEnabled()
    return self.view:isTouchEnabled()
end
  
-- 設置容器層是否允許觸摸響應(默認是false
function ScrollView:setTouchEnabled(enabled)
    self.view:setTouchEnabled(enabled)
    -- self:setTouchEnabled(enabled)
    self:dispatchEvent({name = "setTouchEnabled", enabled = enabled})
end
  
  
---- events 事件處理函數
  
-- Began
function ScrollView:onTouchBegan(x, y)
    self.drag = {
        currentOffsetX = self.offsetX,
        currentOffsetY = self.offsetY,
        startX = x,
        startY = y,
        isTap = true,
    }
  
    local cell = self:getCurrentCell()
    cell:onTouch(event, x, y)
  
    return true
end
  
-- Moved
function ScrollView:onTouchMoved(x, y)
    local cell = self:getCurrentCell()
    if self.direction == ScrollView.DIRECTION_HORIZONTAL then
        if self.drag.isTap and math.abs(x - self.drag.startX) >= self.dragThreshold then
            self.drag.isTap = false
            cell:onTouch("cancelled", x, y)
        end
  
        if not self.drag.isTap then
            self:setContentOffset(x - self.drag.startX + self.drag.currentOffsetX)
        else
            cell:onTouch(event, x, y)
        end
    else
        if self.drag.isTap and math.abs(y - self.drag.startY) >= self.dragThreshold then
            self.drag.isTap = false
            cell:onTouch("cancelled", x, y)
        end
  
        if not self.drag.isTap then
            self:setContentOffset(y - self.drag.startY + self.drag.currentOffsetY)
        else
            cell:onTouch(event, x, y)
        end
    end
end
  
-- Ended  裏面調用了onTouchCancelled(x, y)與onTouchEndedWithTap(x, y)
function ScrollView:onTouchEnded(x, y)
    if self.drag.isTap then
        self:onTouchEndedWithTap(x, y)
    else
        self:onTouchEndedWithoutTap(x, y)
    end
    self.drag = nil
end
  
-- Cancelled  
function ScrollView:onTouchCancelled(x, y)
    self.drag = nil
end
  
-- 觸摸結束時,結束點敲中按鈕
function ScrollView:onTouchEndedWithTap(x, y)
    local cell = self:getCurrentCell()
    cell:onTouch(event, x, y)
    cell:onTap(x, y)      -- 該函數的啓動將會觸發許多函數!!                  
end
  
-- 觸摸結束時,結束點沒有敲中按鈕
function ScrollView:onTouchEndedWithoutTap(x, y)
    error("ScrollView:onTouchEndedWithoutTap() - inherited class must override this method")
end
  
  
  
-- onTouch  根據事件調用 Began、Moved、Ended與Cancelled
function ScrollView:onTouch(event, x, y)
    if self.currentIndex < 1 then return end
  
    if event == "began" then
        -- 判斷起始的觸摸點是否在可觸摸矩形中,若不在則直接返回不做任何操作
        if not cc.rectContainsPoint(self.touchRect, cc.p(x, y)) then return false end
        return self:onTouchBegan(x, y)
    elseif event == "moved" then
        self:onTouchMoved(x, y)
    elseif event == "ended" then
        self:onTouchEnded(x, y)
    else -- cancelled
        self:onTouchCancelled(x, y)
    end
end
  
  
  
---- private methods 私有方法
  
-- 重新整理所有的單元
function ScrollView:reorderAllCells()
    -- 1、設置每個Cell(單元)的position,注意cell的錨點在(0,0)
    local count = #self.cells
    local x, y = 0, 0
    local maxWidth, maxHeight = 0, 0
    for i = 1, count do
        local cell = self.cells<i>
        cell:setPosition(x, y)
        -- 根據滾動方向確定下個cell的position
        if self.direction == ScrollView.DIRECTION_HORIZONTAL then
            local width = cell:getContentSize().width
            if width > maxWidth then maxWidth = width end
            x = x + width
        else
            local height = cell:getContentSize().height
            if height > maxHeight then maxHeight = height end
            y = y - height
        end
    end
      
    -- 2、重置數據
    if count > 0 then
        if self.currentIndex < 1 then
            self.currentIndex = 1
        elseif self.currentIndex > count then
            self.currentIndex = count
        end
    else
        self.currentIndex = 0
    end
  
    -- 3、添加完所有Cell後,根據滾動方向,設置好容器層的大小
    local size
    if self.direction == ScrollView.DIRECTION_HORIZONTAL then
        size = cc.size(x, maxHeight)
    else
        size = cc.size(maxWidth, math.abs(y))
    end
    self.view:setContentSize(size)
end
  
-- 根據偏移量觸發動作
function ScrollView:setContentOffset(offset, animated, time, easing)
    local ox, oy = self.offsetX, self.offsetY
    local x, y = ox, oy
    if self.direction == ScrollView.DIRECTION_HORIZONTAL then
        self.offsetX = offset
        x = offset
  
        local maxX = self.bouncThreshold
        local minX = -self.view:getContentSize().width - self.bouncThreshold + self.touchRect.width
        if x > maxX then
            x = maxX
        elseif x < minX then
            x = minX
        end
    else
        self.offsetY = offset
        y = offset
  
        local maxY = self.view:getContentSize().height + self.bouncThreshold - self.touchRect.height
        local minY = -self.bouncThreshold
        if y > maxY then
            y = maxY
        elseif y < minY then
            y = minY
        end
    end
  
    -- 根據傳入的動作參數執行動作
    if animated then
        transition.stopTarget(self.view)
        transition.moveTo(self.view, {
            x = x,
            y = y,
            time time or self.defaultAnimateTime,
            easing = easing or self.defaultAnimateEasing,
        })
    else
        -- 如果animated這項爲空,就沒有動作了,直接使用setPosition跳轉到下一個Cell
        self.view:setPosition(x, y)
    end
end
  
-- onExit
function ScrollView:onExit()
    -- 移除所有指定類型的事件處理函數
    self:removeAllEventListeners()
end
  
return ScrollView

該腳本代碼很多,可能比較麻煩看,但基本可以爲四大部分:

  • ctor,在裏面設置了基本屬性;

  • 對外接口函數,大部分爲對屬性進行操作的簡單接口函數;

  • events 事件處理函數;

  • private methods 私有方法;

剩下的需要大家看着註釋慢慢理解了。


四、PageControl

PageControl繼承自ScrollView,並且改動很小,只是重寫一個方法,就是:onTouchEndedWithoutTap(x, y),原本該方法只在未點中時輸出信息,但重寫後會根據觸摸偏移量進行翻頁動作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
local ScrollView = import(".ScrollView")
  
-- 繼承自ScrollView
local PageControl = class("PageControl", ScrollView)
  
-- 重新定義了觸摸點未敲中button時的函數,會根據觸摸的偏移量執行翻頁的動作
function PageControl:onTouchEndedWithoutTap(x, y)
    local offsetX, offsetY = self.offsetX, self.offsetY
    local index = 0
    local count = #self.cells
    if self.direction == ScrollView.DIRECTION_HORIZONTAL then
        offsetX = -offsetX
        local x = 0
        local i = 1
        while i <= count do
            local cell = self.cells<i>
            local size = cell:getContentSize()
            if offsetX < x + size.width / 2 then
                index = i
                break
            end
            x = x + size.width
            i = i + 1
        end
        if i > count then index = count end
    else
        local y = 0
        local i = 1
        while i <= count do
            local cell = self.cells<i>
            local size = cell:getContentSize()
            if offsetY < y + size.height / 2 then
                index = i
                break
            end
            y = y + size.height
            i = i + 1
        end
        if i > count then index = count end
    end
  
    self:scrollToCell(index, true)
end
  
return PageControl


五、最終的成品LevelsList

LevelsList繼承自PageControl ,重寫了兩個方法:ctor和scrollToCell,與其說是重寫不如說是爲他們增加了功能,兩個方法都首先執行了父類的方法,在ctor增加了按鈕的佈局添加與指示燈,在scrollToCell中增加了指示燈的動作效果。

這就是指示燈:

56_371891_d9ebb258befb5e3.png

同時,擴增了一個方法onTapLevelIcon,作用是委派事件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
local LevelsListCell = import(".LevelsListCell")
local Levels = import("..data.Levels")
  
local PageControl = import("..ui.PageControl")
  
-- 繼承自PageControl
local LevelsList = class("LevelsList", PageControl)
  
LevelsList.INDICATOR_MARGIN = 46 -- 指示燈的間隔
  
function LevelsList:ctor(rect)
    --先執行方法定義好的方法
    LevelsList.super.ctor(self, rect, PageControl.DIRECTION_HORIZONTAL)
  
    -- 1、add cells
    -- 默認是4行4列,橫的是行,豎的是列
    local rows, cols = 4, 4
    -- 如果分辨率的高超過1000,而變成5行
    if display.height > 1000 then rows = rows + 1 end
  
    -- 總頁數 :7頁,也就是7個cell
    local numPages = math.ceil(Levels.numLevels() / (rows * cols))
    local levelIndex = 1
  
    -- 使用for循環添加7個cell
    for pageIndex = 1, numPages do
        -- 每個cell最後一個關卡的等級
        local endLevelIndex = levelIndex + (rows * cols) - 1
        -- 如果cell的最後一個關卡等級大於Levels.lua裏設定好的100個等級話的,那麼endLevelIndex就爲100,也就是第7個cell
        if endLevelIndex > Levels.numLevels() then
            endLevelIndex = Levels.numLevels()
        end
        -- 創建cell
        local cell = LevelsListCell.new(cc.size(display.width, rect.height), levelIndex, endLevelIndex, rows, cols)
        cell:addEventListener("onTapLevelIcon", function(event) return self:onTapLevelIcon(event) end)
        self:addCell(cell)
          
        --重置數據,爲下個循環添加cell做準備
        levelIndex = endLevelIndex + 1
    end
  
  
    -- 2、add indicators 添加指示燈
    local x = (self:getClippingRect().width - LevelsList.INDICATOR_MARGIN * (numPages - 1)) / 2
    local y = self:getClippingRect().y + 20
  
    self.indicator_ = display.newSprite("#LevelListsCellSelected.png")
    self.indicator_:setPosition(x, y)
    self.indicator_.firstX_ = x
  
    for pageIndex = 1, numPages do
        local icon = display.newSprite("#LevelListsCellIndicator.png")
        icon:setPosition(x, y)
        self:addChild(icon)
        x = x + LevelsList.INDICATOR_MARGIN
    end
  
    self:addChild(self.indicator_)
      
end
  
-- 重寫scrollToCell方法,添加指示燈的動作效果
function LevelsList:scrollToCell(index, animated, time)
    --1、先執行方法定義好的方法
    LevelsList.super.scrollToCell(self, index, animated, time)
  
    --2、指示燈動作效果代碼
    transition.stopTarget(self.indicator_)
    local x = self.indicator_.firstX_ + (self:getCurrentIndex() - 1) * LevelsList.INDICATOR_MARGIN
    if animated then
        time time or self.defaultAnimateTime
        transition.moveTo(self.indicator_, {x = x, time time / 2})
    else
        self.indicator_:setPositionX(x)
    end
end
  
-- 點中圖標是委派一個事件
function LevelsList:onTapLevelIcon(event)
    self:dispatchEvent({name = "onTapLevelIcon", levelIndex = event.levelIndex})
end
  
return LevelsList


六、使用

回到最初的代碼:

1
2
3
4
local rect = cc.rect(display.left, display.bottom + 180, display.width, display.height - 280)
    self.levelsList = LevelsList.new(rect)
    self.levelsList:addEventListener("onTapLevelIcon", handler(self, self.onTapLevelIcon))
self:addChild(self.levelsList)

使用很簡單了,只要傳入一個矩形參數就足夠了,其他的東西在LevelsListCell和LevelsList裏都已經擴展好了。


讓我們看一下這句代碼self.levelsList:addEventListener("onTapLevelIcon", handler(self, self.onTapLevelIcon))找到self.onTapLevelIcon:

1
2
3
4
function ChooseLevelScene:onTapLevelIcon(event)
          audio.playSound(GAME_SFX.tapButton)
          app:playLevel(event.levelIndex)
end

這個方法就把我們之前第四篇所講的一切聯繫上了,進入遊戲進行場景了!


七、再講講dispatchEvent

先說清楚,這個地方完全了看着這個遊戲代碼自我理解,不太能講得明白,大家可以參考這篇文章:《解析Quick-Cocos2d-x中自定義事件


還是繼續看這句代碼:

1
self.levelsList:addEventListener("onTapLevelIcon", handler(self, self.onTapLevelIcon))

問題來了,事件"onTapLevelIcon"哪裏來的呢?回到levelsList.lua發現:

1
2
3
4
-- 點中圖標是委派一個事件
function LevelsList:onTapLevelIcon(event)
    self:dispatchEvent({name = "onTapLevelIcon", levelIndex = event.levelIndex})
end

並且在ctor中發現了使用它的代碼:

1
cell:addEventListener("onTapLevelIcon", function(event) return self:onTapLevelIcon(event) end)

意思就是LevelsList裏的onTapLevelIcon(event)函數一定執行,就會觸發名爲"onTapLevelIcon"的Event,同時返回一個參數event={ name = "onTapLevelIcon",levelIndex = event.levelIndex}}。


但在這裏,我們又發現了Cell裏也有一個"onTapLevelIcon"事件,好吧,乖乖回去LevelsListCell

1
2
3
4
5
6
7
-- 觸摸敲中button的響應函數
function LevelsListCell:onTap(x, y)
    local button = self:checkButton(x, y)
    if button then
        self:dispatchEvent({name = "onTapLevelIcon", levelIndex = button.levelIndex})
    end
end

而這個onTap(x, y)是在何時被調用的呢?答案就在ScrollView:

1
2
3
4
5
6
-- 觸摸結束時,結束點敲中按鈕
function ScrollView:onTouchEndedWithTap(x, y)
    local cell = self:getCurrentCell()
    cell:onTouch(event, x, y)
    cell:onTap(x, y)      -- 該函數的啓動將會觸發許多函數!!                  
end

總結起來就當觸摸結束時,結束點敲中按鈕,將會觸發事件,層層遞進,最終進入遊戲場景!


最後附上帶註釋的源碼:mycoinflip.zip


來源網址:http://www.cocoachina.com/bbs/read.php?tid=235416

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章