关键词:
cocos2d-lua项目启动流程
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++项目

先贴一下代码咯

 
  1. bool AppDelegate::applicationDidFinishLaunching() {

  2. // initialize director

  3. auto director = Director::getInstance(); //初始化Director

  4. auto glview = director->getOpenGLView(); //获得GLView,也就是游戏窗口

  5.  
  6. //此时GLView为空

  7. if(!glview) {

  8. #if (CC_TARGET_PLATFORM == CC_PLATFORM_WIN32) || (CC_TARGET_PLATFORM == CC_PLATFORM_MAC) || (CC_TARGET_PLATFORM == CC_PLATFORM_LINUX)

  9. //如果当前是以上平台,就创建一个大小为designResolutionSize的窗口

  10. glview = GLViewImpl::createWithRect("TestCpp", Rect(0, 0, designResolutionSize.width, designResolutionSize.height));

  11. #else

  12. //其他平台,则使用默认设置(在ios上是全屏窗口,其他平台不太清楚,也有可能是默认设置了一个窗口大小)

  13. glview = GLViewImpl::create("TestCpp");

  14. #endif

  15. director->setOpenGLView(glview); //director获得当前新建的窗口

  16. }

  17.  
  18. // turn on display FPS

  19. director->setDisplayStats(true); //显示帧率信息

  20.  
  21. // set FPS. the default value is 1.0/60 if you don't call this

  22. director->setAnimationInterval(1.0 / 60); //设置动画帧率,也就是界面刷新帧率咯

  23.  
  24. // Set the design resolution

  25. glview->setDesignResolutionSize(designResolutionSize.width, designResolutionSize.height, ResolutionPolicy::NO_BORDER); //设置设计分辨率,而不是实际分辨率,这里是为了适配,不能把实际分辨率固定

  26.  
  27. Size frameSize = glview->getFrameSize(); //获得实际窗口大小

  28.  
  29. //根据实际窗口大小,设置内容缩放比例

  30. // if the frame's height is larger than the height of medium size.

  31. if (frameSize.height > mediumResolutionSize.height)

  32. {

  33. director->setContentScaleFactor(MIN(largeResolutionSize.height/designResolutionSize.height, largeResolutionSize.width/designResolutionSize.width));

  34. }

  35. // if the frame's height is larger than the height of small size.

  36. else if (frameSize.height > smallResolutionSize.height)

  37. {

  38. director->setContentScaleFactor(MIN(mediumResolutionSize.height/designResolutionSize.height, mediumResolutionSize.width/designResolutionSize.width));

  39. }

  40. // if the frame's height is smaller than the height of medium size.

  41. else

  42. {

  43. director->setContentScaleFactor(MIN(smallResolutionSize.height/designResolutionSize.height, smallResolutionSize.width/designResolutionSize.width));

  44. }

  45.  
  46. register_all_packages(); //使用包管理器。。 不太清楚这里是为什么

  47.  
  48. // create a scene. it's an autorelease object

  49. auto scene = HelloWorld::createScene(); //新建一个场景

  50.  
  51. // run

  52. director->runWithScene(scene); //从这个场景开始运行,开始绘制、子节点管理等

  53.  
  54. return true;

  55. }

这段代码是Cocos2d-x 3.10版本新建的C++语言工程中AppDelegate.cpp文件中拷出来的,加了一些注释。 从这里我们可以看出进入游戏逻辑的流程:

  1. 初始化Director
  2. 新建GLView,然后进行一些设置
  3. 新建Scene
  4. 使用Director运行这个场景

游戏逻辑就可以从这个Scene中的init函数开始,添加UI层,添加事件监听器,添加游戏层等等...如果我们有一些统计、资源管理器等,也可以在AppDelegate的applicationDidFinishLaunching函数中来进行。

Lua项目

也来看AppDelegate.cpp中的applicationDidFinishLaunching函数

 
  1. bool AppDelegate::applicationDidFinishLaunching()

  2. {

  3. // set default FPS

  4. Director::getInstance()->setAnimationInterval(1.0 / 60.0f); //设置动画帧率,也就是游戏帧率了

  5.  
  6. //重点:添加Lua相关支持

  7. // register lua module

  8. auto engine = LuaEngine::getInstance(); //初始化一个Lua语言引擎

  9. ScriptEngineManager::getInstance()->setScriptEngine(engine); //将Lua语言引擎设置为当前脚本引擎(用脚本引擎管理器来管理各种脚本引擎)

  10. lua_State* L = engine->getLuaStack()->getLuaState(); //获取Lua引擎的State,也就是一组属性

  11. lua_module_register(L); //向Lua引擎注册一些模块,如网络、动画等

  12.  
  13. register_all_packages();

  14.  
  15. //设置脚本加密相关的key和sign

  16. LuaStack* stack = engine->getLuaStack();

  17. stack->setXXTEAKeyAndSign("2dxLua", strlen("2dxLua"), "XXTEA", strlen("XXTEA"));

  18.  
  19. //register custom function

  20. //LuaStack* stack = engine->getLuaStack();

  21. //register_custom_function(stack->getLuaState());

  22.  
  23. #if (COCOS2D_DEBUG > 0) && (CC_CODE_IDE_DEBUG_SUPPORT > 0)

  24. //如果需要支持CodeIDE,则从引擎本身启动

  25. // NOTE:Please don't remove this call if you want to debug with Cocos Code IDE

  26. auto runtimeEngine = RuntimeEngine::getInstance();

  27. runtimeEngine->addRuntime(RuntimeLuaImpl::create(), kRuntimeEngineLua);

  28. runtimeEngine->start();

  29. #else

  30. //一般情况,这里使用Lua引擎执行第一个Lua脚本

  31. if (engine->executeScriptFile("src/main.lua"))

  32. {

  33. return false; //脚本默认返回nil,如果脚本执行正常,不会进入这一句

  34. }

  35. #endif

  36.  
  37. return true; //正常执行到这里,后面就开始执行Cocos引擎提供的主循环

  38. }

从上面代码可以看到,Cocos2d-x新建的Lua语言项目中,这里没有进行GLView的设置,没有使用C++代码来创建Scene,所以这些操作肯定和"src/main.lua"脚本有关。这里我们一步一步来看src目录下这些脚本执行的步骤(注意看注释的序号):

  1. 在main.lua中部分注释:
 
  1. cc.FileUtils:getInstance():setPopupNotify(false)

  2. cc.FileUtils:getInstance():addSearchPath("src/")

  3. cc.FileUtils:getInstance():addSearchPath("res/")

  4.  
  5. require "config" -- 1.执行当前目录下的config.lua,定义一些初始化用的全局变量,包括窗口设置等

  6. require "cocos.init" -- 2.执行cocos/init.lua,初始化框架等一大堆东西,包括OpenGL、音效等引擎的初始化与配置

  7.  
  8. local function main()

  9. require("app.MyApp"):create():run() -- 3.执行app/MyApp.lua,调用对应class的Create方法创建对象,并执行run方法

  10. end

  11. -- 先忽略这个,我也不知道是啥

  12. local status, msg = xpcall(main, __G__TRACKBACK__)

  13. if not status then

  14. print(msg)

  15. end

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

 
  1. local MyApp = class("MyApp", cc.load("mvc").AppBase) -- 4.类MyApp继承自mvc中的AppBase类,自动找到packages/mvc/AppBase.lua

  2. function MyApp:onCreate()

  3. math.randomseed(os.time())

  4. end

  5. return MyApp

3.继续去packages/AppBase.lua里看看生成MyApp的时候做了什么:

 
  1. local AppBase = class("AppBase")

  2.  
  3. -- 5.构造函数

  4. function AppBase:ctor(configs)

  5. self.configs_ = {

  6. viewsRoot = "app.views",

  7. modelsRoot = "app.models",

  8. defaultSceneName = "MainScene",

  9. }

  10.  
  11. for k, v in pairs(configs or {}) do

  12. self.configs_[k] = v

  13. end

  14.  
  15. if type(self.configs_.viewsRoot) ~= "table" then

  16. self.configs_.viewsRoot = {self.configs_.viewsRoot}

  17. end

  18. if type(self.configs_.modelsRoot) ~= "table" then

  19. self.configs_.modelsRoot = {self.configs_.modelsRoot}

  20. end

  21.  
  22. if DEBUG > 1 then

  23. dump(self.configs_, "AppBase configs")

  24. end

  25.  
  26. if CC_SHOW_FPS then

  27. cc.Director:getInstance():setDisplayStats(true)

  28. end

  29.  
  30. -- event

  31. self:onCreate() -- 6.啥也没做的create函数

  32. end

4.我们可以看回第1步(注释编号3)中,生成MyApp对象后,执行了run方法,那就看看AppBase.lua中的run方法做了什么:

 
  1. -- 7.创建完对象之后,就到了这一步

  2. function AppBase:run(initSceneName)

  3. initSceneName = initSceneName or self.configs_.defaultSceneName

  4. self:enterScene(initSceneName) -- 8.如果没有指定第一个Scene,则第一个Scene为MainScene

  5. end

  6. -- 9.生成并进入第一个Scene

  7. function AppBase:enterScene(sceneName, transition, time, more)

  8. local view = self:createView(sceneName) -- 10.前去生成View

  9. view:showWithScene(transition, time, more) -- 20.因为MainScene继承自ViewBase类,这里就吊用ViewBase的方法了

  10. return view

  11. end

5.这里(注释编号10)看到会调用到AppBase.lua中的createView方法:

 
  1. -- 11.根据name生成一个View

  2. function AppBase:createView(name)

  3. for _, root in ipairs(self.configs_.viewsRoot) do

  4. local packageName = string.format("%s.%s", root, name) -- 12.这里拼接了View的路径,app/views/MainScene.lua

  5. local status, view = xpcall(function() -- 13.这里xpcall相当于try-catch结构了,所以看第一个function

  6. return require(packageName) -- 14.执行上面拼接的MainScene.lua脚本,view获得脚本返回值

  7. end, function(msg)

  8. if not string.find(msg, string.format("'%s' not found:", packageName)) then

  9. print("load view error: ", msg)

  10. end

  11. end)

  12. local t = type(view)

  13. if status and (t == "table" or t == "userdata") then

  14. return view:create(self, name) -- 15.这里调用了MainScene的create方法噢

  15. end

  16. end

  17. error(string.format("AppBase:createView() - not found view \"%s\" in search paths \"%s\"",

  18. name, table.concat(self.configs_.viewsRoot, ",")), 0)

  19. end

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

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

  2.  
  3. -- 19.创建一个Sprite,一个Label,添加到这个Node中

  4. function MainScene:onCreate()

  5. -- add background image

  6. display.newSprite("HelloWorld.png")

  7. :move(display.center)

  8. :addTo(self)

  9.  
  10. -- add HelloWorld label

  11. cc.Label:createWithSystemFont("Hello World", "Arial", 40)

  12. :move(display.cx, display.cy + 200)

  13. :addTo(self)

  14.  
  15. end

  16.  
  17. return MainScene

7.上面第一行代码(注释编号16)可以看到,MainScene类继承自ViewBase,那进入mvc/ViewBase.lua看看

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

  2.  
  3. -- 18.构造函数,还是进行一些初始化工作

  4. function ViewBase:ctor(app, name)

  5. self:enableNodeEvents()

  6. self.app_ = app

  7. self.name_ = name

  8.  
  9. -- check CSB resource file

  10. local res = rawget(self.class, "RESOURCE_FILENAME")

  11. if res then

  12. self:createResoueceNode(res)

  13. end

  14.  
  15. local binding = rawget(self.class, "RESOURCE_BINDING")

  16. if res and binding then

  17. self:createResoueceBinding(binding)

  18. end

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

  21. end

8.这里可以回看到第4步(注释编号10),方法createView执行完成后,生成了一个MainScene对象(继承自ViewBase(继承自ccNode)),然后下一步就是调用MainScene对象的showWithScene函数,在packages/mvc/ViewBase.lua中:

 
  1. -- 21.这里创建了一个Scene,并且把当前这个Node添加到Scene中。其实这就是C++项目HelloWorldScene类的createScene方法了

  2. function ViewBase:showWithScene(transition, time, more)

  3. self:setVisible(true)

  4. local scene = display.newScene(self.name_) -- 22.display包含很多功能,有点类似于Director了

  5. scene:addChild(self)

  6. display.runScene(scene, transition, time, more) -- 23.runScene熟悉的方法

  7. return self

  8. 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文件,看到引入的头文件:

 
  1. #include "AppDelegate.h"

  2. #include "CCLuaEngine.h"

  3. #include "SimpleAudioEngine.h"

  4. #include "cocos2d.h"

  5. #include "lua_module_register.h"

  6.  
  7. #if (CC_TARGET_PLATFORM != CC_PLATFORM_LINUX)

  8. #include "ide-support/CodeIDESupport.h"

  9. #endif

  10.  
  11. #if (COCOS2D_DEBUG > 0) && (CC_CODE_IDE_DEBUG_SUPPORT > 0)

  12. #include "runtime/Runtime.h"

  13. #include "ide-support/RuntimeLuaImpl.h"

  14. #endif

AppDelegate自己的头文件除外,第一个头文件就是CCLuaEngine.h,打开cocos2d_lua_bindings库的manual目录,我们就能看到这个类。打开CCLuaEngine.h文件,可以看到它包含了CCLuaStack.h和CCLuaValue.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引擎啊。。

Logo

CSDN联合极客时间,共同打造面向开发者的精品内容学习社区,助力成长!

更多推荐