Quick-Cocos2d-x-3.2中示例Coinfilp解析

前提:本文筆者使用是的Quick-Cocos2d-x v3.2-RC0和Cocos Code IDE進行學習的。

首先,用 play3 新建一個工程(名字大家就任意起吧)

1412911045745011.png

工程建立完成後,大家進去自己Quick-Cocos2d-x v3.2-RC0所在目錄,然後進入...\quick\samples\coinflip 中,將當中的兩個文件夾res和src複製下來。

(其中,res存放遊戲資源,src存放lua代碼)

1412911081454553.png

然後進入剛纔我們新建的工程中,將當中的 res 和src 替換爲剛纔複製的res和src。

接着使用play3運行我們新建的工程,是不是與示例中的coinflip一樣了?

打開Cocos Code IDE,將該工程導入,就可以進行代碼的查看和修改了!


一、基礎的main.lua、config.lua 和MyApp.lua

先打開src下的main.lua

1
2
3
4
5
6
7
8
9
function __G__TRACKBACK__(errorMessage)
    print("----------------------------------------")
    print("LUA ERROR: " .. tostring(errorMessage) .. "\n")
    print(debug.traceback("", 2))
    print("----------------------------------------")
end
  
-- 啓動後執行MyApp腳本, 並執行當中的 run() 方法
require("app.MyApp").new():run()

每個新建的工程的main.lua都一樣,不需要改動,我們只要知道它是程序lua腳本的啓動文件就夠了。接着我們沿着最後一句代碼 require("app.MyApp").new():run() ,打開MyApp.lua, 觀察當中的run()方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function MyApp:run()
    -- 設置資源搜索路徑
    cc.FileUtils:getInstance():addSearchPath("res/")
      
    --創建圖片緩存
    --在config中
    --GAME_TEXTURE_DATA_FILENAME  = "AllSprites.plist"
    --GAME_TEXTURE_IMAGE_FILENAME = "AllSprites.png"
    display.addSpriteFrames(GAME_TEXTURE_DATA_FILENAME, GAME_TEXTURE_IMAGE_FILENAME)
  
    -- 預加載音頻文件
    for k, v in pairs(GAME_SFX) do
        audio.preloadSound(v)
    end
  
    -- 進入場景
    self:enterMenuScene()
end

/////////////////////////////////

GAME_TEXTURE_DATA_FILENAME

GAME_TEXTURE_IMAGE_FILENAME

GAME_SFX

都是config.lua中定義得全局變量

//////////////////



在run()方法當中主要做了三件事:

1、設置資源搜索路徑;

2、加載遊戲資源;

3、進入主場景;

也許你會說在這個腳本文件裏找不到  GAME_TEXTURE_DATA_FILENAME 和  GAME_TEXTURE_IMAGE_FILENAME 相關代碼,你應該打開config.lua ,所有的 配置信息 和 宏定義 都在裏面(個人覺得統一寫在config.lua中更方便查找,比C++更容易讀懂)


同時MyApp.lua中還封裝了所有場景的切換方法,更加方便了管理和後續的編寫

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function MyApp:enterMenuScene()
    self:enterScene("MenuScene", nil, "fade", 0.6, display.COLOR_WHITE)
end
  
function MyApp:enterMoreGamesScene()
    self:enterScene("MoreGamesScene", nil, "fade", 0.6, display.COLOR_WHITE)
end
  
function MyApp:enterChooseLevelScene()
    self:enterScene("ChooseLevelScene", nil, "fade", 0.6, display.COLOR_WHITE)
end
  
function MyApp:playLevel(levelIndex)
    self:enterScene("PlayLevelScene", {levelIndex}, "fade", 0.6, display.COLOR_WHITE)
end


二、第一個場景MenuScene

瞭解了基礎的幾個腳本後,我們跟着 self:enterMenuScene() 這個句代碼,進入第一個場景MenuScene,場景界面如下

1412911200791580.png

打開src\app\scenes\MenuScene.lua

1
2
local AdBar = import("..views.AdBar")
local BubbleButton = import("..views.BubbleButton")

開頭兩句其實作用是跟C++裏的導入是一樣的,因此,很明顯該場景中將會用到這個兩個自定義的類。


往下看

1
2
3
local MenuScene = class("MenuScene", function()
    return display.newScene("MenuScene")
end)

class方法有兩個參數,第一個參數是類名。第二參數可以通過兩種形式傳遞參數,一種是傳入一個函數,另一種方式是傳入一個Quick的類或者是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
function MenuScene:ctor()
      
    -- 1、背景的添加
    self.bg = display.newSprite("#MenuSceneBg.png", display.cx, display.cy)
    self:addChild(self.bg)
  
    -- 2、信息條的添加
    self.adBar = AdBar.new()
    self:addChild(self.adBar)
  
    -- 3、“更過遊戲”按鈕
    self.moreGamesButton = BubbleButton.new({
            image = "#MenuSceneMoreGamesButton.png",
            sound = GAME_SFX.tapButton,
            prepare = function()
                audio.playSound(GAME_SFX.tapButton)
                self.moreGamesButton:setButtonEnabled(false)
            end,
            listener = function()
                app:enterMoreGamesScene()
            end,
        })
        :align(display.CENTER, display.left + 150, display.bottom + 300)
        :addTo(self)
      
    -- 4、“開始”按鈕
    self.startButton = BubbleButton.new({
            image = "#MenuSceneStartButton.png",
            sound = GAME_SFX.tapButton,
            prepare = function()
                audio.playSound(GAME_SFX.tapButton)  -- 播放特效聲
            self.startButton:setButtonEnabled(false)  -- 先關閉menu功能,這樣防止在這個menu item還沒做完動作又被玩家點上別的按鈕上了
            end,
            listener = function()
                app:enterChooseLevelScene()  -- 該方法在MyApp.lua中
            end,
        })
        :align(display.CENTER, display.right - 150, display.bottom + 300) -- 設置錨點,X座標,Y座標
        :addTo(self) -- 添加到該場景中
  
end

ctor() 相當於構造函數 或者說 是Cococs2d-x裏的init(),一旦new,就會調用ctor() ,

在該ctor()中初始化的該場景的界面佈置:

1、背景的添加;

2、信息條的添加;

3、“更過遊戲”按鈕;

4、“開始”按鈕;


1、背景的添加

其實十分簡單,創建一精靈,將其位置設置在場景的中心點(錨點已經默認爲精靈的中心點了),然後將其添加進場景就OK了。

需要注意一點的就是在Quick中使用圖片,如果使用的圖片是以#開頭的話表示是從SpriteFrameCache中讀取,如果沒有使用#開頭的話表示是直接從文件讀取。(還記得在MyApp.lua中我們已經加載了圖片緩存了嗎?)

附:display.width和display.height表示屏幕寬度

    display.cx和display.cy表示屏幕的x軸中間位置和y軸中間位置

    display.left和display.right表示屏幕的最左邊和最右邊(x軸座標爲0和display.width的點)

    display.top和display.bottom表示屏幕的頂部和底部(y軸座標爲0和display.height的點)

    display.CENTER、display.LEFT_TOP、display.CENTER_TOP等分別表示node的錨點位置。


2、信息條的添加

打開src\app\views\AdBar.lua  觀察下面代碼

1
2
3
4
5
6
7
8
9
10
11
12
-- 信息條(界面最下方那條)
-- 一個進行了二次封裝的精靈
-- align:錨點,X座標,Y座標
local AdBar = {}
  
function AdBar.new()
    local sprite = display.newSprite("#AdBar.png")
    sprite:align(display.BOTTOM_CENTER, display.cx, display.bottom)
    return sprite
end
  
return AdBar

在AdBar中,一旦調用了new() 方法,將自動創建並返回一個設置好精靈幀、錨點、X座標和Y座標的精靈。

(二次封裝的目的除了外部的方便調用外,最大的動能就是代碼的複用!所以可以預測到後面的編寫中必定將會繼續用到這個信息條)


3、4、兩個按鈕的添加,都是使用自定義的 汽包按鈕類 BubbleButton (我直譯,錯了請見諒,六級還沒過T.T) ,將這個自定義按鈕這個篇是講不完了,但大家先觀察好代碼,記住創建氣泡按鈕傳入的參數是個表table ,表裏的元素有四個:
{ imgae = XXX, sound = XXX, prepare = XXX, listener = XXX }

  • image是一張圖片,

  • sound是一個音頻文件,

  • prepare和listener都是一個方法。

使用代碼(MeunScene.lua中的):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
-- 3、“更過遊戲”按鈕
    self.moreGamesButton = BubbleButton.new({
            image = "#MenuSceneMoreGamesButton.png",
            sound = GAME_SFX.tapButton,
            prepare = function()
                audio.playSound(GAME_SFX.tapButton)
                self.startButton:setButtonEnabled(false)
            end,
            listener = function()
                app:enterMoreGamesScene()
            end,
        })
        :align(display.CENTER, display.left + 150, display.bottom + 300)
        :addTo(self)

現在我們來正式瞭解下這個自定義的按鈕。

打開src\app\views\BubbleButton.lua, 看看代碼,這個就不貼代碼,貼的太多就磨滅大家的看帖動力了。還貼圖更有動力,大家也用play3多看看效果,感覺更爽

56_371891_4498f3cf8a79e00.png

粗略一看之後是不是覺得代碼很多很亂很煩啊?其中仔細看看就會發現這個腳本里面只有一個方法,就是 new(param)

而其作用很明顯:對參入的參數進行捕獲,創建一個PushButton, 並封裝進動作效果

整個方法內代碼很多,頭一次看會很亂,我先隱藏一部分代碼(爲了方便理解,我改動了一點點順序,但對程序沒有絲毫影響)

大家就會發現該方法其實只幹了4件事情,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function BubbleButton.new(params)//5.3 命名參數
    -- 1、創建PushButton,只設置了normal情況下圖片(既是傳入參數中的image的值)
    local button =  cc.ui.UIPushButton.new({normal = params.image}) 
     
    -- 2、將傳入的回調函數先用listener變量保存起來,    
    local listener = params.listener
      
    -- 3、重新定義了傳入參數中 params.listener的執行內容
    params.listener = function(tag)    
      
    -- 4、設置按鈕點擊響應事件
    button:onButtonClicked(function(tag)
      
    -- 返回該按鈕
    return button
end

1、創建按鈕:沒什麼好講的,大家這麼聰明肯定都懂得。但是還是建議大家多看看Quick的框架源碼,裏面的有中文註釋,很好理解,對學習Quick幫助極大,路徑就在Quick-Cocos2d-x v3.2-RC0所在目錄的quick-cocos2d-x-3.2rc0\quick\framework 裏。

2、捕獲傳入參數中的listener元素:由本地變量listener保存起來(我好討厭名字一樣啊,有時老不小心就看錯!)。爲什麼要找個本地變量來存儲呢?等下你就知道了

3、重新定義傳入參數中的元素listener:有人也許會問:要重新定義,原來傳入的有什麼意思呢? 往上看看第2點,現在知道爲什麼要本地變量listener保存了吧。   現在我們把第3點的代碼展開,看看裏面做些什麼事情

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
--  3、重新定義了參數表中 params.listener的執行內容
    params.listener = function(tag)        
        -- (1)如果參數中 prepare的值不爲空,則先執行params.prepare()
        if params.prepare then
            params.prepare()
        end
//

if conditions then

    then-part

end;

//
  
        -- (2)zoom1和zoom2是兩個效果函數,主要是move和scale兩個動作,
        local function zoom1(offset, time, onComplete)
            local x, y = button:getPosition()
            local size = button:getContentSize()
            size.width = 200
            size.height = 200
  
            local scaleX = button:getScaleX() * (size.width + offset) / size.width
            local scaleY = button:getScaleY() * (size.height - offset) / size.height
  
            transition.moveTo(button, {y = y - offset, time time})
            transition.scaleTo(button, {
                scaleX     = scaleX,
                scaleY     = scaleY,
                time       time,
                onComplete = onComplete,
            })
        end
  
        local function zoom2(offset, time, onComplete)
            local x, y = button:getPosition()
            local size = button:getContentSize()
            size.width = 200
            size.height = 200
  
            transition.moveTo(button, {y = y + offset, time time / 2})
            transition.scaleTo(button, {
                scaleX     = 1.0,
                scaleY     = 1.0,
                time       time,
                onComplete = onComplete,
            })
        end
  
    --  (3)動作效果方法的組合使用
        --  設置按鈕的點擊功能無效,防止在還沒做完動作又被玩家點上該按鈕
        button:setButtonEnabled(false)
  
        -- 執行動作效果,    一系列的縮放效果之後,再開啓按鈕功能,最後執行之前的回調函數,這樣一個動畫按鈕就新鮮出爐了。
        zoom1(40, 0.08, function()
            zoom2(40, 0.09, function()
                zoom1(20, 0.10, function()
                    zoom2(20, 0.11, function()
                        button:setButtonEnabled(true)   --  動作效果結束後,開啓按鈕的點擊功能
                        listener(tag)                                    --  執行原先的params.listener的方法
                    end)
                end)
            end)
        end)
                  
    end


重新定義了的param,listener 還是一個方法,該方法裏的代碼可以分成三部分:

(1)執行param.prepare這個元素所指向的方法;

1
2
3
if params.prepare then
                   params.prepare()
           end

該部分用了if判斷語句,如果params.prepare 爲空則不會執行,所以根據實際情況,傳入參數中的元素prepare並不是一個必填項。


(2)定義了兩個本地的效果方法zoom1和zoom2;

zoom1和zoom2是兩個效果函數,主要是move和scale兩個動作,這個兩個內容不難,我就不講解了,大家看看理解下就好


(3)動作效果方法的組合使用;

使用zoom1和zoom2進行組合使用,達到氣泡效果的動作。難度不大但需要注意幾點:

A、使用回調函數,使動作連續

B、執行動作效果前,先關閉該PushButton的按鈕功能,防止在還沒做完動作又被玩家點上該按鈕:button:setButtonEnabled(false) , 效果結束了後在開啓它的按鈕功能

C、在動作效果結束時,還在要執行之前我們用本來變量listener保存下來的方法,就是這個句代碼:listener(tag),因爲動作效果是我們附加的視覺效果,listener的方法才我們真正效果,不要忘本了哦


4、設置按鈕點擊響應事件:代碼展開如下

1
2
3
button:onButtonClicked(function(tag)
        params.listener(tag)
end)

是不是很簡單,就是執行了我們剛纔重新定義了的params.listener,params.listener已經把所有需要做的事都做了:執行params.prepare、動作效果、還有最初的params.listener所定義的方法(如今在本地變量listener裏)。

現在再重新梳理一下,傳入參數{ imgae = XXX, sound = XXX, prepare = XXX, listener = XXX }

  • image:是用於創建PushButton的圖片,必須

  • sound:在BubbleButton.lua並沒用到,但它其實是按鈕被點擊時的效果聲音,等下再解釋

  • prepare:是一個必定會被執行到的方法,但可以置空,大家就根據實際情況決定要不要寫,應該寫些什麼。根據它的的名字,我理解這個元素是按鈕被點擊前需要做的事。

  • listener :同樣是一個必定被執行的方法

再回到MenuScen.lua的ctor()的方法中,找到使用了BubbleButton的代碼

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
--“開始”按鈕
    self.startButton = BubbleButton.new({
            image = "#MenuSceneStartButton.png",
            sound = GAME_SFX.tapButton,
            prepare = function()
                audio.playSound(GAME_SFX.tapButton)  -- 播放特效聲
                --self.startButton:setButtonEnabled(false)  
                self.moreGamesButton:setButtonEnabled(false) -- 先關閉功能,這樣防止還沒做完動作又被玩家點上別的按鈕上了
            end,
            listener = function()
                app:enterChooseLevelScene()  -- 該方法在MyApp.lua中
            end,
        })
        :align(display.CENTER, display.right - 150, display.bottom + 300) -- 設置錨點,X座標,Y座標
        :addTo(self) -- 添加到該場景中

註釋應該很清楚了,最後我想提出一下自己的改動:

看上面的代碼,是不是有一句與原代碼不同?就是註釋掉了self.startButton:setButtonEnabled(false),並添加了self.moreGamesButton:setButtonEnabled(false), 請讓我解釋一下,原先的self.startButton:setButtonEnabled(false) 這句代碼的作用關閉startButton這個按鈕的點擊,防止這個按鈕在還沒做完動作又被玩家點上,但是看過BubbleButton.lua裏面的代碼後發現關閉按鈕功能的代碼已經內嵌在其中了,這樣就發生了代碼功能重複,所以我認爲 self.startButton:setButtonEnabled(false) 這句代碼是多餘。

而添加了self.moreGamesButton:setButtonEnabled(false) 這句目的很簡單,因爲在MenuScene的場景一共就兩個由BubbleButton 創建出來的按鈕:startButton和moreGamesButton, 我希望在點擊其中一個按鈕時,另外的一個按鈕的點擊功能會被禁用,用戶體驗才更好(同時在另外一個按鈕代碼也應該做相應的修改)


以上只是我個人的理解,不知道對不對,大家多擔當,多給建議

同時,對於這個二次封裝的按鈕,大家也可以看看這位大神講解http://cn.cocos2d-x.org/tutorial/show?id=1350


二、進入MoreGamesScene場景

完全瞭解MenuScene這個場景後,我們隨着startButton和moreGamesButton這兩個按鈕進入其他場景,繼續學習吧。

首先,點擊moreGamesButton,我們將進入MoreGamesScene場景,如下圖:

1413012658121825.png

真是單調的界面啊,我們還是打開src\app\scenes\MoreGamesScene.iua來直接看代碼吧:

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
-- 導入AdBar.lua
local AdBar = import("..views.AdBar")
  
-- 創建一個名爲MoreGamesScene的場景類
local MoreGamesScene = class("MoreGamesScene", function()
    return display.newScene("MoreGamesScene")
end)
  
function MoreGamesScene:ctor()
    -- 1、背景
    self.bg = display.newSprite("#MenuSceneBg.png", display.cx, display.cy)
    self:addChild(self.bg)
      
    --2、信息條
    self.adBar = AdBar.new()
    self:addChild(self.adBar)
  
    --3、後退按鈕
    cc.ui.UIPushButton.new("#BackButton.png")
        :align(display.CENTER, display.right - 100, display.bottom + 120)
         -- 添加點擊的響應事件
        :onButtonClicked(function()
            app:enterMenuScene()  -- 回到MenuScene中
        end)
        :addTo(self)
end
  
return MoreGamesScene

1、背景:創建一精靈,並添加到場景中;

2、信息條:在上一篇中已經大膽預測它會再出現了,果不其然啊,不到多少,大家自己看代碼吧;

3、後退按鈕:使用是Quick框架封裝好的PushButton按鈕創建,源碼在quick-3.2rc0-win\quick-cocos2d-x-3.2rc0\quick\framework\cc\ui,大家要養成多查看框架代碼的習慣,用法裏面講得很清楚;


這個場景太簡單了,沒什麼好講,我們還是默默的按後退鍵吧。

一、進入遊戲場景PlayLevelScene

接着上篇的內容,回到MenuScene後,點擊StartButton進入遊戲關卡選擇場景ChooseLevelScene。

1413183142396622.png

但是我沒還打算講解這個場景的內容,而且跳過它,先從遊戲場景PlayLevelScene開始講解。原因有二:

1、遊戲關卡選擇場景ChooseLevelScene使用的控件並不是單純的Quick框架定義的控件,而且自己封裝的(比起上篇的自定義按鈕BubbleButton難度不是一個級別),我打算另外單獨講解;

2、另外一個原因就是提前進入進行遊戲的場景PlayLevelScene瞭解該遊戲的核心不是更讓人提起精神嗎?所以我想先講PlayLevelScene


那麼我們就隨便選擇一個等級的關卡進入遊戲場景吧。

1413183183433872.png

我們以視覺分析該場景的組成元素:

  • 有好多的銀幣;

  • 每個硬幣下面有一塊半透明的方塊;

  • 背景圖;

  • 一個後退鍵;

  • 一信息條;

  • 還有一個“Level:6”的等級文本;

好,帶着這些“視覺認知”,我們打開PlayLevelScene.lua,看看其中的ctor()函數是怎樣定義界面的吧。

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
local Levels = import("..data.Levels")
local Board = import("..views.Board")
local AdBar = import("..views.AdBar")
  
local PlayLevelScene = class("PlayLevelScene", function()
    return display.newScene("PlayLevelScene")
end)
  
function PlayLevelScene:ctor(levelIndex)
    -- 1、背景
    local bg = display.newSprite("#PlayLevelSceneBg.png")
    -- make background sprite always align top
    bg:setPosition(display.cx, display.top - bg:getContentSize().height / 2)
    self:addChild(bg)
  
    -- 2、標題
    local title = display.newSprite("#Title.png", display.left + 150, display.top - 50)
    title:setScale(0.5)
    self:addChild(title)
  
    -- 3、信息條
    local adBar = AdBar.new()
    self:addChild(adBar)
  
    -- 4、等級文本
    local label = cc.ui.UILabel.new({
        UILabelType = 1,
        text  = string.format("Level: %s", tostring(levelIndex)),
        font  = "UIFont.fnt",
        x     = display.left + 10,
        y     = display.bottom + 120,
        align = cc.ui.TEXT_ALIGN_LEFT,
    })
    self:addChild(label)
  
    -- 5、硬幣板
    self.board = Board.new(Levels.get(levelIndex))
    self.board:addEventListener("LEVEL_COMPLETED", handler(self, self.onLevelCompleted))
    self:addChild(self.board)
  
  
    -- 6、後退按鈕
    cc.ui.UIPushButton.new({normal = "#BackButton.png", pressed = "#BackButtonSelected.png"})
        :align(display.CENTER, display.right - 100, display.bottom + 120)
        :onButtonClicked(function()
            app:enterChooseLevelScene()
        end)
        :addTo(self)
End

哈,看樣子猜中很多啊,出差錯的地方就是那個“硬幣板”,所以我認爲所以的硬幣與半透明方塊都在內嵌在這個“硬幣板”中。先不管它了,我們從頭分析起:背景、標題、信息條;這三個沒什麼好講得,前面都已經理解基本能獨立看懂與使用,重點我們先講講等級文本和學學文本控件的用法。


瞭解UILabel的用法

根據cc.ui.UILabel.new,我打開quick-cocos2d-x-3.2rc0\quick\framework\cc\ui找到了UILabel.lua,打開它,我們就可以來查看源碼了!我們找到創建UILabel.類的這段代碼:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
--[[--
quick UILabel控件
]]
  
local UILabel
UILabel = class("UILabel", function(options)
    if not options then
        return
    end
  
    if 1 == options.UILabelType then
        return UILabel.newBMFontLabel_(options)
    elseif not options.UILabelType or 2 == options.UILabelType then
        return UILabel.newTTFLabel_(options)
    else
        printInfo("UILabel unkonw UILabelType")
    end
end)

因爲UILabelType=1,根據上面代碼,我們知道了接下來執行的應該是UILabel.newBMFontLabel_(options),接着我們在UILabel.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
--[[--
  
用位圖字體創建文本顯示對象,並返回 LabelBMFont 對象。
  
BMFont 通常用於顯示英文內容,因爲英文字母加數字和常用符號也不多,生成的 BMFont 文件較小。如果是中文,應該用 TTFLabel。
  
可用參數:
  
-    text: 要顯示的文本
-    font: 字體文件名
-    align: 文字的水平對齊方式(可選)
-    x, y: 座標(可選)
  
~~~ lua
  
local label = UILabel:newBMFontLabel({
    text = "Hello",
    font = "UIFont.fnt",
})
  
~~~
  
@param table params 參數表格對象
  
@return LabelBMFont LabelBMFont對象
  
]]
function UILabel.newBMFontLabel_(params)
    return display.newBMFontLabel(params)
end

找到了這段代碼加註釋後就完全明白UILabel的用法了,根本就不需我解釋了啊!所以,希望大家以後看到類似不懂的情況,也要懂得這樣找框架源碼,裏面的註釋極其強大,對我們菜鳥的學習很有幫助的。


對於後退按鈕,大家也可以用這種方法,學會UIPushButton的用法,這裏不做過多講解。


二、遊戲進行場景的重要組件:Level.lua、Coin.lua

重新看會“硬幣板”的那段代碼,我知道知道接下要去打開src\app\views\Board.lua,在Board.lua這個腳本上出現了兩個代碼:

1
2
local Levels = import("..data.Levels")
local Coin   = import("..views.Coin")

明顯的,我們先應該瞭解Levels.lua和Coin.lua後再來看Board.lua會更清晰易懂。


1、Level.lua

好,那我們從Level.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
local Levels = {}
  
Levels.NODE_IS_WHITE  = 1        -- 金色面                                 正                                        
Levels.NODE_IS_BLACK  = 0        -- 白色面 。。。寫BLACK是幹嘛,色盲?    反
Levels.NODE_IS_EMPTY  = "X"      -- 空,就是沒有硬幣
  
local levelsData = {}
  
levelsData[1] = {
    rows = 4,               -- 行數
    cols = 4,                -- 列類
    grid = {                  -- 網格,也就是硬幣狀態的佈局
        {1, 1, 1, 1},
        {1, 1, 0, 1},
        {1, 0, 0, 0},
        {1, 1, 0, 1}
    }
}
--中間省略了99個關卡布局數據
  
function Levels.numLevels()
    -- 長度操作符#用於返回一個數組或者線性表的最後的一個索引值 即size
    return #levelsData
end
  
function Levels.get(levelIndex)
    --assert (v [, message])
    --參數:
    --v:當表達式v爲nil或false將觸發錯誤,
    --message:發生錯誤時返回的信息,默認爲"assertion failed!"
    -- 確保levelIndex >= 1 and levelIndex <= #levelsData,如果不是,將輸出錯誤信息 :levelsData.get() - invalid levelIndex %s, 
    assert(levelIndex >= 1 and levelIndex <= #levelsData, string.format("levelsData.get() - invalid levelIndex %s", tostring(levelIndex)))
    return clone(levelsData[levelIndex])
end
  
return Levels

Level.lua腳本相對簡單,就是遊戲關卡的硬幣佈局相關的數據,總共有100個levelsData,量真大!理解請看代碼註釋,應該不難懂。但想更大家統一下思路,反面後面思路混亂。

就是對硬幣狀態的規定:

  • 正面—金色面—1—Levels.NODE_IS_WHITE

  • 反面—銀色面—0—Levels.NODE_IS_BLACK

  • 空—X—Levels.NODE_IS_EMPTY


2、Coin.lua

打開Coin.lua,先看前面一份的代碼:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
local Levels = import("..data.Levels")
  
local Coin = class("Coin", function(nodeType)
    -- 先設定爲1,正面
    local index = 1
    -- 判斷 
    if nodeType == Levels.NODE_IS_BLACK then
        index = 8  -- 8是反面
    end
      
    -- 依靠index選擇創建的coin是用哪張圖片資源
    local sprite = display.newSprite(string.format("#Coind.png", index))//翻轉效果是由一組圖片實現的
    -- 爲該精靈添加一個屬性,是否爲正面
    sprite.isWhite = index == 1
    return sprite
end)

思路是這樣的:以傳入參數決定決定index的值,再已index的值創建精靈,最後繼續依靠index的值決定sprite.isWhite此屬性。

從此可看出index的值很重要,起決定性作爲,但它爲什麼是1或8呢?

好,現在是解密時間!這回打開res\AllSprites.plist,找到這幾個:

1413183289987793.png

大家可以看到Coin0001到Coin0008是硬幣由正面(金)到反面(銀)的8張圖片,所以上面代碼纔會以index=1代表正面,index=8代表反面。


接着我們把Coin.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
-- 創建並執行硬幣翻轉特效的函數
function Coin:flip(onComplete)
  
    -- 1、創建並執行翻轉動畫
    -- 創建8張精靈幀,1到8 是 正面到反面的翻轉               
    -- 注意:第四個參數,當其爲false,按正常引索創建,爲true是,按遞減引索的方式創建
    local frames = display.newFrames("Coind.png", 1, 8, not self.isWhite)
//newFrames以特定模式創建一個包含多個圖像幀對象的數組。
    -- 以上面創建的8張精靈幀集 創建動畫,第二個參數是每一楨動畫之間的間隔時間
    local animation = display.newAnimation(frames, 0.3 / 8)
    self:playAnimationOnce(animation, false, onComplete)  -- 播放動畫
////////////////////////
內部封裝
@param CCNode target 顯示對象
@param CCNode animation 動作對象
@param boolean removeWhenFinished 播放完成後刪除顯示對象
@param function onComplete 播放完成後要執行的函數
@param number delay 播放前等待的時間
@return table 動作表格對象
function transition.playAnimationOnce(target, animation, removeWhenFinished, onComplete, delay)
///////////////////////

  
  
    -- 2、翻轉後的一系列的縮放動作
    self:runAction(transition.sequence({
        -- 兩個參數:第一個參數是時間(s),第二個參數是縮放倍數
        cc.ScaleTo:create(0.15, 1.5),
        cc.ScaleTo:create(0.1, 1.0),
        cc.CallFunc:create(function()
            local actions = {}
            local scale = 1.1
            local time = 0.04
            for i = 1, 5 do//4.3 控制結構語句
                -- 三個參數,第一個是時間,第二個是X方向的縮放倍數,第三個是Y方向的縮放倍數
// -- 長度操作符#用於返回一個數組或者線性表的最後的一個索引值 即size
                actions[#actions + 1] = cc.ScaleTo:create(time, scale, 1.0)
                actions[#actions + 1] = cc.ScaleTo:create(time, 1.0, scale)
                scale = scale * 0.95
                time time * 0.8
            end
            actions[#actions + 1] = cc.ScaleTo:create(0, 1.0, 1.0)
            self:runAction(transition.sequence(actions))
        end)
    }))
  
  /////ccspawn?

    -- 3、翻轉結束後,修改“是否爲正面”這個屬性
    self.isWhite = not self.isWhite
end

1、創建並執行翻轉動畫:先分清一點,動畫與動作是不一樣的哦!這樣的動畫是所剛纔看過的Coin0001到Coin0008所創建的幀動畫。首先,先創建動畫要播放的精靈幀集合

1
localframes=display.newFrames("Coind.png",1,8,notself.isWhite)

爲了學習我們去找源碼吧!裏面肯定解析清楚用法了,還記得怎麼找嗎?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
--[[--
以特定模式創建一個包含多個圖像幀對象的數組。
  
~~~ lua
  
-- 創建一個數組,包含 Walk0001.png 到 Walk0008.png 的 8 個圖像幀對象
local frames = display.newFrames("Walkd.png", 1, 8)
  
-- 創建一個數組,包含 Walk0008.png 到 Walk0001.png 的 8 個圖像幀對象
local frames = display.newFrames("Walkd.png", 1, 8, true)
  
~~~
  
@param string pattern 模式字符串
@param integer begin 起始索引
@param integer length 長度
@param boolean isReversed 是否是遞減索引
  
@return table 圖像幀數組
  
]]
function display.newFrames(pattern, begin, length, isReversed)

簡單明瞭,只要注意一點,就是第四個參數,我們是以sprite.isWhite該屬性爲基礎,很代碼更爲靈活與智能:是正面的話就正序創建精靈幀集合(正面到反面),是反面的話則相反。精靈幀集合創建完成後,再以其爲基礎創建動畫,再播放動畫就OK了!不懂的地方記得查框架源碼!!

2、翻轉後的一系列的縮放動作:一系列的動作組合。

3、翻轉結束後,修改“是否爲正面”這個屬性:切記這一步很重要,該屬性是判斷硬幣當前的狀態的一個接口,對於該遊戲的核心玩法和動畫的創建都起的決定的作用。


OK,本篇先講到這裏,在瞭解完Board.lua的組件,下篇我們在開好好談論下Board.lua吧,謝謝。


一、承載一切的“硬幣板” Board.lua

(1)ctor 

承接上篇,遊戲核心內容與玩法都集中在了這塊Board上,我們直接上其ctor分代碼先:

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
local Levels = import("..data.Levels")
local Coin   = import("..views.Coin")
  
local Board = class("Board", function()
    return display.newNode()
end)
  
local NODE_PADDING   = 100     --半透明的正方形方塊的邊長 PADDING 內邊距
local NODE_ZORDER    = 0       --半透明方塊的Z層數
local COIN_ZORDER    = 1000    --銀幣的Z層數
  
function Board:ctor(levelData)
    cc.GameObject.extend(self):addComponent("components.behavior.EventProtocol"):exportMethods()
//加入事件協議
/////////////////
cc/GameObject.lua——————————
function GameObject.extend(target) 
//裝飾函數 返回裝飾過的target 。 extend延伸;擴大;推廣 對參數target裝飾 給他增加方法checkComponent等
    target.components_ = {}
    function target:
checkComponent(name)
        return self.components_[name] ~= nil
    end
    function target:
addComponent(name)
        local component = Registry.newObject(name)
        self.components_[name] = component
        component:bind_(self)
//結合;裝訂;捆綁
        return component
    end
////
function Registry.newObject(name, ...)
    local cls = Registry.classes_[name]
    if not cls then
        -- auto load
        pcall(function()
            cls = require(name)
            Registry.add(cls, name)
        end)
    end
    assert(cls ~= nil, string.format("Registry.newObject() - invalid class \"%s\"", tostring(name)))
    return cls.new(...)
end
////
    function target:removeComponent(name)
        local component = self.components_[name]
        if component then component:unbind_() end
        self.components_[name] = nil
    end
    function target:
getComponent(name)
        return self.components_[name]
    end
    return target
end
/////////////////

    --從指定的圖像文件創建並返回一個批量渲染對象。
    --只要繪製的圖像在指定的圖像文件中,無論繪製多少圖像只用了 1 次 OpenGL draw call
    --反正就是提高機器效率的一種方法,但之後的精靈添加方式有點改變
    self.batch = display.newBatchNode(GAME_TEXTURE_IMAGE_FILENAME)
//
function display.newBatchNode(image, capacity)
    return CCSpriteBatchNode:create(image, capacity or 100) //批渲染節點 會把紋理放入紋理緩存 從而實現同一紋理精靈只用了 1 次 OpenGL draw call
end
  • 創建一個CCSpriteBatchNode對象,通過傳遞一個包含所有spritebatch的名字作爲參數,並把它加入到當前場景之中。
  • 接下來,你從batch中創建的任何sprite,你應該把它當作CCSpriteBatchNode的一個孩子加進去。只要sprite包含在batch中,那麼就沒問題,否則會出錯。後面加的CCSprite的名字,要不跟 batchNode創建的時候是同一個圖片名字,要不就是 CCSpriteFrameCache裏面的大圖的小圖的名字,否則會有問題。其實就是同一個紋理貼圖 CCTexture2D  ,比如下面的tabletop.png必須是 table.png的小圖。
//
    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[row][col]
            -- 如果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[row][col] = 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裏增加了五個屬性:

1
2
3
4
5
self.grid = clone(levelData.grid)
self.rows = levelData.rows
self.cols = levelData.cols
self.coins = {}
self.flipAnimationCount = 0


其中grid屬性用法要格外留意,期初的作用是捕獲傳入參數裏的grid,是像這樣的:

1
2
3
4
5
6
grid = {
     {1, 0, 0, 1},
     {0, 1, 1, 0},
     {0, 1, 1, 0},
     {1, 0, 0, 1}
}

之後依靠捕獲的到參數去創建出不同狀態的硬幣(正面或者反面)local node = self.grid[row][col],創建後,又將硬幣去替換grid對應位置的值 self.grid[row][col] = coin,請展開想象力,它應該變成這樣:

  56_371891_48881e2dc7698fd.png

3、佈局的方式,因爲批量渲染對象的緣故,場景的中心已經相當於座標(0,0)了,解析太難,所以我上課時手工畫了圖:

1413424462286764.png

這幅圖解決了很多我想要解釋的東西,希望大家看得懂(小正方形裏面的除了座標外,那個數字是繪製的順序)。其實一開始我也看不太懂,是依靠添加的 print("("..x,y..")") 這句代碼去了解到各個方塊的座標,進而明白整個佈局的。

1413424512877144.png

4、設置觸摸監聽器


(2)Board.lua 裏的方法

1、onTouch 

既然設置的觸摸監聽器,必定有回調函數,代碼如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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 翻轉硬幣

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
-- 翻轉硬幣,被點擊的硬幣與它四個方向相鄰的硬幣都會翻轉
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 得到硬幣

1
2
3
4
5
6
7
-- 得到指定行列的硬幣
function Board:getCoin(row, col)
    -- 注意:在ctor中,已經把創建的Coin與在grid對應的值替換了,所以可以用這種方法得到
    if self.grid[row] then
        return self.grid[row][col]
    end
end


4、checkLevelCompleted 檢測關卡完成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
-- 檢測函數,檢測該是否關卡通關,所有硬幣coin的isWhite屬性爲true時通關
function Board:checkLevelCompleted()
    local count = 0
    -- 遍歷所有硬幣

//////////////
第一,數值for循環:

for var=exp1,exp2,exp3 do

    loop-part

end

for將用exp3作爲step從exp1(初始值)到exp2(終止值),執行loop-part。其中exp3可以省略,默認step=1


第二,範型for循環:

前面已經見過一個例子:

-- print all values of array 'a' 

for i,v in ipairs(a) do print(v) end //i 是下標名 v是數組元素值

範型for遍歷迭代子函數返回的每一個值。

再看一個遍歷表key的例子:

-- print all keys of table 't'

for k in pairs(t) do print(k) end //k是表值


/////////////

    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"})
//發出事件 quick獨有消息處理中心
    end
end

這樣,Board.lua 基本就瞭解完了。


二、Board.lua的使用

回到PlayLevelScene.lua,找到下面使用Board.lua的代碼:

1
2
3
self.board = Board.new(Levels.get(levelIndex))
self.board:addEventListener("LEVEL_COMPLETED", handler(self, self.onLevelCompleted))
self:addChild(self.board)

使用很簡單,但其中調用到一個方法我們還沒講到就是:onLevelCompleted

1
2
3
4
5
6
7
8
9
10
11
12
13
-- 遊戲結束響應函數
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

當關卡完成通關時,就爲這個方法,一個下降的勝利橫幅:

1413424702877534.png

下篇我們再繼續該遊戲的最後一部分:ChooseLevelScene。



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

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