Cocos2d-lua工程運行流程的理解

關鍵詞:

  1. cocos2d-lua項目啟動流程
  2. lua調用cocos2d引擎API

開發環境:

  1. 引擎版本:Cocos2d-x 3.10
  2. 開發工具:Xcode8.1

簡述

  • 所謂的Cocos2d-lua,其實只是Cocos2d引擎添加了Lua綁定的版本。

從創建命令可以看出來 cocos new TestProj -d Desktop/ -l lua,這里的引擎其實是同一套,只是創建工程時提供了不同語言的橋接層

  • 使用C++語言和Cocos2d-x引擎進行開發時,我們寫的代碼是直接調用引擎的API的,因為引擎也是用C++語言編寫,不需要進行語言轉換
  • 使用Lua語言和Cocos2d-x引擎進行開發時,我們寫的代碼通過LuaEngine執行,而LuaEngine封裝了Cocos2d-x引擎的API,所以就相當于使用Lua腳本在調用Cocos2d-x的API了

C++項目和Lua項目的開始過程

簡單來比較一下C++項目和Lua項目開始的過程(后面會專門寫一下啟動流程),這里我們都從AppDelegate.cpp的
AppDelegate::applicationDidFinishLaunching()
函數開始。

C++項目

先貼一下代碼咯

bool AppDelegate::applicationDidFinishLaunching() {
    // initialize director
    auto director = Director::getInstance();    //初始化Director
    auto glview = director->getOpenGLView();    //獲得GLView,也就是游戲窗口
    
    //此時GLView為空
    if(!glview) {
#if (CC_TARGET_PLATFORM == CC_PLATFORM_WIN32) || (CC_TARGET_PLATFORM == CC_PLATFORM_MAC) || (CC_TARGET_PLATFORM == CC_PLATFORM_LINUX)
        //如果當前是以上平臺,就創建一個大小為designResolutionSize的窗口
        glview = GLViewImpl::createWithRect("TestCpp", Rect(0, 0, designResolutionSize.width, designResolutionSize.height));
#else
        //其他平臺,則使用默認設置(在ios上是全屏窗口,其他平臺不太清楚,也有可能是默認設置了一個窗口大小)
        glview = GLViewImpl::create("TestCpp");
#endif
        director->setOpenGLView(glview);    //director獲得當前新建的窗口
    }

    // turn on display FPS
    director->setDisplayStats(true);    //顯示幀率信息

    // set FPS. the default value is 1.0/60 if you don't call this
    director->setAnimationInterval(1.0 / 60);   //設置動畫幀率,也就是界面刷新幀率咯

    // Set the design resolution
    glview->setDesignResolutionSize(designResolutionSize.width, designResolutionSize.height, ResolutionPolicy::NO_BORDER);  //設置設計分辨率,而不是實際分辨率,這里是為了適配,不能把實際分辨率固定
    
    Size frameSize = glview->getFrameSize();    //獲得實際窗口大小
    
    //根據實際窗口大小,設置內容縮放比例
    // if the frame's height is larger than the height of medium size.
    if (frameSize.height > mediumResolutionSize.height)
    {        
        director->setContentScaleFactor(MIN(largeResolutionSize.height/designResolutionSize.height, largeResolutionSize.width/designResolutionSize.width));
    }
    // if the frame's height is larger than the height of small size.
    else if (frameSize.height > smallResolutionSize.height)
    {        
        director->setContentScaleFactor(MIN(mediumResolutionSize.height/designResolutionSize.height, mediumResolutionSize.width/designResolutionSize.width));
    }
    // if the frame's height is smaller than the height of medium size.
    else
    {        
        director->setContentScaleFactor(MIN(smallResolutionSize.height/designResolutionSize.height, smallResolutionSize.width/designResolutionSize.width));
    }

    register_all_packages();    //使用包管理器。。 不太清楚這里是為什么

    // create a scene. it's an autorelease object
    auto scene = HelloWorld::createScene(); //新建一個場景

    // run
    director->runWithScene(scene);  //從這個場景開始運行,開始繪制、子節點管理等

    return true;
}

這段代碼是Cocos2d-x 3.10版本新建的C++語言工程中AppDelegate.cpp文件中拷出來的,加了一些注釋。
從這里我們可以看出進入游戲邏輯的流程:

  1. 初始化Director
  2. 新建GLView,然后進行一些設置
  3. 新建Scene
  4. 使用Director運行這個場景

游戲邏輯就可以從這個Scene中的init函數開始,添加UI層,添加事件監聽器,添加游戲層等等...如果我們有一些統計、資源管理器等,也可以在AppDelegate的applicationDidFinishLaunching函數中來進行。

Lua項目

也來看AppDelegate.cpp中的applicationDidFinishLaunching函數

bool AppDelegate::applicationDidFinishLaunching()
{
    // set default FPS
    Director::getInstance()->setAnimationInterval(1.0 / 60.0f);     //設置動畫幀率,也就是游戲幀率了

    //重點:添加Lua相關支持
    // register lua module
    auto engine = LuaEngine::getInstance();     //初始化一個Lua語言引擎
    ScriptEngineManager::getInstance()->setScriptEngine(engine);    //將Lua語言引擎設置為當前腳本引擎(用腳本引擎管理器來管理各種腳本引擎)
    lua_State* L = engine->getLuaStack()->getLuaState();    //獲取Lua引擎的State,也就是一組屬性
    lua_module_register(L); //向Lua引擎注冊一些模塊,如網絡、動畫等

    register_all_packages();

    //設置腳本加密相關的key和sign
    LuaStack* stack = engine->getLuaStack();
    stack->setXXTEAKeyAndSign("2dxLua", strlen("2dxLua"), "XXTEA", strlen("XXTEA"));

    //register custom function
    //LuaStack* stack = engine->getLuaStack();
    //register_custom_function(stack->getLuaState());

#if (COCOS2D_DEBUG > 0) && (CC_CODE_IDE_DEBUG_SUPPORT > 0)
    //如果需要支持CodeIDE,則從引擎本身啟動
    // NOTE:Please don't remove this call if you want to debug with Cocos Code IDE
    auto runtimeEngine = RuntimeEngine::getInstance();
    runtimeEngine->addRuntime(RuntimeLuaImpl::create(), kRuntimeEngineLua);
    runtimeEngine->start();
#else
    //一般情況,這里使用Lua引擎執行第一個Lua腳本
    if (engine->executeScriptFile("src/main.lua"))
    {
        return false;   //腳本默認返回nil,如果腳本執行正常,不會進入這一句
    }
#endif

    return true;    //正常執行到這里,后面就開始執行Cocos引擎提供的主循環
}

從上面代碼可以看到,Cocos2d-x新建的Lua語言項目中,這里沒有進行GLView的設置,沒有使用C++代碼來創建Scene,所以這些操作肯定和"src/main.lua"腳本有關。這里我們一步一步來看src目錄下這些腳本執行的步驟(注意看注釋的序號):

  1. 在main.lua中部分注釋:
cc.FileUtils:getInstance():setPopupNotify(false)
cc.FileUtils:getInstance():addSearchPath("src/")
cc.FileUtils:getInstance():addSearchPath("res/")

require "config"        -- 1.執行當前目錄下的config.lua,定義一些初始化用的全局變量,包括窗口設置等
require "cocos.init"    -- 2.執行cocos/init.lua,初始化框架等一大堆東西,包括OpenGL、音效等引擎的初始化與配置

local function main()
    require("app.MyApp"):create():run() -- 3.執行app/MyApp.lua,調用對應class的Create方法創建對象,并執行run方法
end
-- 先忽略這個,我也不知道是啥
local status, msg = xpcall(main, __G__TRACKBACK__)
if not status then
    print(msg)
end

2.去app/MyApp.lua看看require做了什么:

local MyApp = class("MyApp", cc.load("mvc").AppBase)    -- 4.類MyApp繼承自mvc中的AppBase類,自動找到packages/mvc/AppBase.lua
function MyApp:onCreate()
    math.randomseed(os.time())
end
return MyApp

3.繼續去packages/AppBase.lua里看看生成MyApp的時候做了什么:

local AppBase = class("AppBase")

-- 5.構造函數
function AppBase:ctor(configs)
    self.configs_ = {
        viewsRoot  = "app.views",
        modelsRoot = "app.models",
        defaultSceneName = "MainScene",
    }

    for k, v in pairs(configs or {}) do
        self.configs_[k] = v
    end

    if type(self.configs_.viewsRoot) ~= "table" then
        self.configs_.viewsRoot = {self.configs_.viewsRoot}
    end
    if type(self.configs_.modelsRoot) ~= "table" then
        self.configs_.modelsRoot = {self.configs_.modelsRoot}
    end

    if DEBUG > 1 then
        dump(self.configs_, "AppBase configs")
    end

    if CC_SHOW_FPS then
        cc.Director:getInstance():setDisplayStats(true)
    end

    -- event
    self:onCreate() -- 6.啥也沒做的create函數
end

4.我們可以看回第1步(注釋編號3)中,生成MyApp對象后,執行了run方法,那就看看AppBase.lua中的run方法做了什么:

-- 7.創建完對象之后,就到了這一步
function AppBase:run(initSceneName)
    initSceneName = initSceneName or self.configs_.defaultSceneName
    self:enterScene(initSceneName)  -- 8.如果沒有指定第一個Scene,則第一個Scene為MainScene
end
-- 9.生成并進入第一個Scene
function AppBase:enterScene(sceneName, transition, time, more)
    local view = self:createView(sceneName)     -- 10.前去生成View
    view:showWithScene(transition, time, more)  -- 20.因為MainScene繼承自ViewBase類,這里就吊用ViewBase的方法了
    return view
end

5.這里(注釋編號10)看到會調用到AppBase.lua中的createView方法:

-- 11.根據name生成一個View
function AppBase:createView(name)
    for _, root in ipairs(self.configs_.viewsRoot) do
        local packageName = string.format("%s.%s", root, name)  -- 12.這里拼接了View的路徑,app/views/MainScene.lua
        local status, view = xpcall(function()  -- 13.這里xpcall相當于try-catch結構了,所以看第一個function
                return require(packageName) -- 14.執行上面拼接的MainScene.lua腳本,view獲得腳本返回值
            end, function(msg)
            if not string.find(msg, string.format("'%s' not found:", packageName)) then
                print("load view error: ", msg)
            end
        end)
        local t = type(view)
        if status and (t == "table" or t == "userdata") then
            return view:create(self, name)  -- 15.這里調用了MainScene的create方法噢
        end
    end
    error(string.format("AppBase:createView() - not found view \"%s\" in search paths \"%s\"",
        name, table.concat(self.configs_.viewsRoot, ",")), 0)
end

6.上面的函數中執行到了app/views/MainScene.lua腳本,那就去看看做了什么:

local MainScene = class("MainScene", cc.load("mvc").ViewBase)   -- 16.MainScene類繼承自ViewBase,去mvc/ViewBase.lua看看

-- 19.創建一個Sprite,一個Label,添加到這個Node中
function MainScene:onCreate()
    -- add background image
    display.newSprite("HelloWorld.png")
        :move(display.center)
        :addTo(self)

    -- add HelloWorld label
    cc.Label:createWithSystemFont("Hello World", "Arial", 40)
        :move(display.cx, display.cy + 200)
        :addTo(self)

end

return MainScene

7.上面第一行代碼(注釋編號16)可以看到,MainScene類繼承自ViewBase,那進入mvc/ViewBase.lua看看

local ViewBase = class("ViewBase", cc.Node) -- 17.繼承自Node噢

-- 18.構造函數,還是進行一些初始化工作
function ViewBase:ctor(app, name)
    self:enableNodeEvents()
    self.app_ = app
    self.name_ = name

    -- check CSB resource file
    local res = rawget(self.class, "RESOURCE_FILENAME")
    if res then
        self:createResoueceNode(res)
    end

    local binding = rawget(self.class, "RESOURCE_BINDING")
    if res and binding then
        self:createResoueceBinding(binding)
    end

    if self.onCreate then self:onCreate() end
end

8.這里可以回看到第4步(注釋編號10),方法createView執行完成后,生成了一個MainScene對象(繼承自ViewBase(繼承自ccNode)),然后下一步就是調用MainScene對象的showWithScene函數,在packages/mvc/ViewBase.lua中:

-- 21.這里創建了一個Scene,并且把當前這個Node添加到Scene中。其實這就是C++項目HelloWorldScene類的createScene方法了
function ViewBase:showWithScene(transition, time, more)
    self:setVisible(true)
    local scene = display.newScene(self.name_)  -- 22.display包含很多功能,有點類似于Director了
    scene:addChild(self)
    display.runScene(scene, transition, time, more) -- 23.runScene熟悉的方法
    return self
end

到這里,對于Cocos2d-x引擎生成的C++和Lua語言項目,我們都分析到了生成第一個Scene的步驟,后面就可以開始寫UI、寫結構、寫邏輯等內容了。


簡單對比Cocos2d-x創建的C++工程和Lua工程

這里使用Cocos2d-x 3.10分別創建了Lua語言工程和C++語言工程,在Xcode下打開兩個項目,可以對比一下項目結構:

compare.png

從最外層結構可以看出Lua工程比C++工程多了兩個lib:

  1. libsimulator 模擬器支持
  2. cocos2d_lua_bindings 引擎與Lua腳本的橋接層

libsimulator就先不看了,和這次主題無關,就先放一邊,以后有空再來看(其實我還真沒仔細看過這個東東)。

打開AppDelegate.cpp文件,看到引入的頭文件:

#include "AppDelegate.h"
#include "CCLuaEngine.h"
#include "SimpleAudioEngine.h"
#include "cocos2d.h"
#include "lua_module_register.h"

#if (CC_TARGET_PLATFORM != CC_PLATFORM_LINUX)
#include "ide-support/CodeIDESupport.h"
#endif

#if (COCOS2D_DEBUG > 0) && (CC_CODE_IDE_DEBUG_SUPPORT > 0)
#include "runtime/Runtime.h"
#include "ide-support/RuntimeLuaImpl.h"
#endif

AppDelegate自己的頭文件除外,第一個頭文件就是CCLuaEngine.h,打開cocos2d_lua_bindings庫的manual目錄,我們就能看到這個類。打開CCLuaEngine.h文件,可以看到它包含了CCLuaStack.hCCLuaValue.h,這兩個文件都是C++與Lua直接交互需要用到的。繼續往下看,可以看到cocos2d/LuaScriptHandlerMgr.h,打開manual下的cocos2d文件夾可以看到如下文件列表:

manual:cocos2d.png

從其中LuaOpengl.cpp中包含的代碼:


LuaOpengl.png

可以看到這里在注冊一個module,并綁定函數。另外一些lia_開頭的文件中包含的也是這些代碼。

理解一下

想想腳本執行時的情景,當執行到一個名為drawCircle的函數時,用戶自己很可能并沒有定義這樣一個函數,那Lua引擎如何識別“drawCircle”這樣一個命令,而不會把它當作錯誤的代碼呢?
看到上面的文件我們就能知道,字符串“drawCircle”早就被注冊到LuaEngine中,所以當執行腳本時遇到drawCircle時,才知道需要去調用哪一個函數。
這也就是說,cocos2d_lua_bindings庫提供了Lua對Cocos2d引擎的綁定,相當于通過注冊Module的方式對Cocos2d引擎提供的(相關的)API進行了一次封裝(當然,如果是直接封裝API,可能達不到提高開發效率的目的,所以有了quick的出現,也就是把常用的功能(例如創建一個Scene)封裝成一個函數newScene)。

總結

相對于Cocos2d-x C++工程來說,Cocos2d-x生成的Lua語言工程提供了對Cocos2d引擎的Lua語言封裝。將Cocos2d引擎API綁定到對應的Lua語言函數,在調用到這些函數時,會執行對應的Cocos2d引擎API。
以這個思想來看,所以能夠直接或間接與C++語言進行交互的編程語言都可以用來封裝Cocos2d引擎啊。。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,797評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,179評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,628評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,642評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,444評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,948評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,040評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,185評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,717評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,602評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,794評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,316評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,045評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,418評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,671評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,414評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,750評論 2 370

推薦閱讀更多精彩內容