前提:本文筆者使用是的Quick-Cocos2d-x v3.2-RC0和Cocos Code IDE進行學習的。
首先,用 play3 新建一個工程(名字大家就任意起吧)
工程建立完成後,大家進去自己Quick-Cocos2d-x v3.2-RC0所在目錄,然後進入...\quick\samples\coinflip 中,將當中的兩個文件夾res和src複製下來。
(其中,res存放遊戲資源,src存放lua代碼)
然後進入剛纔我們新建的工程中,將當中的 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,場景界面如下
打開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多看看效果,感覺更爽
粗略一看之後是不是覺得代碼很多很亂很煩啊?其中仔細看看就會發現這個腳本里面只有一個方法,就是 new(param)
而其作用很明顯:對參入的參數進行捕獲,創建一個PushButton, 並封裝進動作效果
整個方法內代碼很多,頭一次看會很亂,我先隱藏一部分代碼(爲了方便理解,我改動了一點點順序,但對程序沒有絲毫影響)
大家就會發現該方法其實只幹了4件事情,如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
-- 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場景,如下圖:
真是單調的界面啊,我們還是打開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。
但是我沒還打算講解這個場景的內容,而且跳過它,先從遊戲場景PlayLevelScene開始講解。原因有二:
1、遊戲關卡選擇場景ChooseLevelScene使用的控件並不是單純的Quick框架定義的控件,而且自己封裝的(比起上篇的自定義按鈕BubbleButton難度不是一個級別),我打算另外單獨講解;
2、另外一個原因就是提前進入進行遊戲的場景PlayLevelScene瞭解該遊戲的核心不是更讓人提起精神嗎?所以我想先講PlayLevelScene
那麼我們就隨便選擇一個等級的關卡進入遊戲場景吧。
我們以視覺分析該場景的組成元素:
-
有好多的銀幣;
-
每個硬幣下面有一塊半透明的方塊;
-
背景圖;
-
一個後退鍵;
-
一信息條;
-
還有一個“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,找到這幾個:
大家可以看到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 顯示對象
///////////////////////
-- 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 -- 三個參數,第一個是時間,第二個是X方向的縮放倍數,第三個是Y方向的縮放倍數
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
//
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,請展開想象力,它應該變成這樣:
3、佈局的方式,因爲批量渲染對象的緣故,場景的中心已經相當於座標(0,0)了,解析太難,所以我上課時手工畫了圖:
這幅圖解決了很多我想要解釋的東西,希望大家看得懂(小正方形裏面的除了座標外,那個數字是繪製的順序)。其實一開始我也看不太懂,是依靠添加的 print("("..x,y..")") 這句代碼去了解到各個方塊的座標,進而明白整個佈局的。
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 |
當關卡完成通關時,就爲這個方法,一個下降的勝利橫幅:
下篇我們再繼續該遊戲的最後一部分:ChooseLevelScene。