Fork me on GitHub

/无间落叶 I am a leaf on the wind ~

玩转 Cocos2d-x 脚本引擎

| Comments

[无间落叶]http://blog.leafsoar.com/archives/2013/11-10.html

在 Unix 文化中,有这样一种理念,Happy Hacking!使用 Cocos2d-x/C++ 写过一些游戏,其绑定的脚本语言,用的也不少,脚本语言的一个好处就是快速开发,你无需明白它之运行机理,便可容易的完成所想要的效果,三天上手,五天就能写出像样的程序来,C++ 则不然,其各种语言细节特性,各种开发技巧,内存管理等细枝末叶 ~

计算机不会魔法,在一叶看来其内容,只有 “知、或者不知” ,没有 “懂、或者不懂”,“知或者不知”来自于你的学习历程与经验,至于“不懂”么,我还没接触到的领域内容,我都不懂,哈 ~ Cocos2d-x 脚本引擎也用过一段时间了,但其运行机理还不明白,就使用而言也无需明白,不过于在意细节的实现,可能更好的从宏观角度把握整体。过去只是对其“存疑”(对于这里的“不懂”,一叶通常美其名曰:要学会存疑 :p ),而现在想要对其运行机制多了解一些,那就只有一步步去探究喽,要了解到什么程度,那就随意了~

对 Cocos2d-x 的运行机制只是略懂一二,C++ 的场景由 C++ 运行,脚本呢,先开启脚本引擎,让后将控制权交由脚本代码执行,在这过程中发生了什么,由脚本所控制的元素和 C++ 有什么不同,或者说它的本质是什么!这之前一叶一直说的是脚本引擎,而非具体那种脚本引擎,lua 或者 js (jsvascript)的引擎实现!凭着对已有知识的了解和直觉,很多疑问和可能性随之而来,它所支持的脚本语言有两种,此两种的共同之处是什么,其使用了脚本绑定技术,什么是绑定 ?各种对象在内存中如何分布,如何配合在一起工作。

为了增加探究过程的趣味性,所以一叶试想着能不能让 Cocos2d-x 现今所支持的两种脚本引擎同时运行 (lua and js),然后确定是否能在三者之间(C++, js, lua)访问同一个内存元素,如果行,便弄出来,即便不能做到,那也无所谓,这其中的过程比结果更有意思,不是吗 ~

两种脚本引擎同时运行

这里使用了 cocos2d-x-3.0alpha0-pre 版本,原因有二。一者:这是最新版本,反正是折腾,顺便了解一下 3.0 的新特性和 代码 style ,其二:3.0 对 三大开发平台(windows, linux ,mac),两大运行平台(android, ios)的支持更好更全一些,比如,lua 可以跑在 mac 上面,这一点最新的 2.2 版本不行(lua )。这样的选择,可以让我在当前系统(Mac OS X) 系统下,直接运行看效果,而不用开模拟器或者虚拟机,使过程更为方便。(过去使用 Linux 作为开发环境,也很方便,一叶的博客也有其具体的开发环境搭建配置等)而 windows 系统,几年前就几乎不怎么用了,各种不顺手。

3.0 中去除了使用项目模板来构建项目,而改为使用脚本创建,支持的平台如下(这个脚本是 github 上最新的版本),观其关键代码:

1
2
3
4
5
6
# [Cocos2d-x]/tools/project-creator/create_project.py
PLATFORMS = {
  "cpp" : ["ios_mac", "android", "win32", "linux"],
  "lua" : ["ios_mac", "android", "win32", "linux"],
  "javascript" : ["ios_mac", "android", "win32"]
}

从这里脚本看出 cpp 和 lua ,对五个平台已经全面支持,javascript 对 linux 还没有支持(脚本上是这样),相比 -x 2.x 版本支持更好,更全面,其代码也经过重构,更模块化,还有很多 C++ 11 的新特性,这里同样也期待 3.0 早日成熟,达到实用阶段 :p

为了简化操作步骤,一叶尽量利用现有的环境内容,使用 XCode打开 samples 项目,这里包含了所有项目内容,起初一叶打算基于 HelloCpp 项目做扩展,添加脚本支持,来实现自己的功能,也许是我对 XCode 环境了解不够,通过手动配置,让它能够支持 lua 扩展,但是 js 扩展却没能跑起来(没理由 lua 可以 js 却不行,知道一定是哪里配置有误),此时看来,在已有的项目中添加 lua 比 js 成功几率要大,固转而使用 HelloLua 项目添加 js 扩展支持,以达到项目组织上能够独立运行 js 引擎 或者 lua 引擎。(注:使用 HelloCpp 作为扩展,主要想看看怎么配置方便,如果都好配置,就基于 Cpp ,如果其中一个配置难,或者没通过,就基于没有通过配置的已有实现,扩展它。比如,这里 lua 通过 js 却没有,那就基于已有的 HelloLua 扩展支持 js,更为节省精力,这是策略问题 :p)

以上只是让项目同时支持 js 或者 lua 的运行,比如只跑 lua,或者只跑 js,但是 两者却不能同时跑,如果在代码中,同时用到了 lua 和 js 的支持库,在编译时会报错。这是因为在绑定的时候,两种脚本引擎分别实现了自己的 GLNode 。

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
// [Cocos2d-x]/scripting/javascript/bindings/js_bindings_opengl.h
#include "cocos2d.h"
#include "ScriptingCore.h"
#include "cocos2d_specifics.hpp"

class GLNode : public cocos2d::Node {
 public:
  void draw();
};

void js_register_cocos2dx_GLNode(JSContext *cx, JSObject *global);

// [Cocos2d-x]/scripting/lua/cocos2dx_support/LuaOpengl.h
#ifndef __LUA_OPENGL_H__
#define __LUA_OPENGL_H__

#ifdef __cplusplus
extern "C" {
#endif
#include "tolua++.h"
#ifdef __cplusplus
}
#endif

#include "base_nodes/CCNode.h"
class GLNode:public cocos2d::Node
{
    virtual void draw();
};

TOLUA_API int tolua_Cocos2d_CCDrawNode_drawPolygon00(lua_State* tolua_S);

TOLUA_API int tolua_opengl_open(lua_State* tolua_S);

#endif //__LUA_OPENGL_H__

同时使用两种引擎,就意味着同时用到两者的库依赖,而重复的类型定义导致编译通不过,所以只能根据需要 hack 源码了,如果修改,二者修改其一,看修改哪个方便,引用的地方少。如下是使用 Emacs 的 find-grep 命令,在 scripting 目录搜索 “GLNode” 关键字的结果。(Emacs 是一叶的必备工具,这里搜索出的结果可以快速定位代码位置)

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
-*- mode: grep; default-directory: "~/Tools/cocos2d-x/cocos2d-x-3.0alpha0-pre/scripting/" -*-
Grep started at Mon Nov 11 13:55:04

find . -type f -exec grep -nH -e GLNode {} +
./javascript/bindings/js/jsb_cocos2d_extension.js:167:cc.GLNode.extend = cc.Class.extend;
./javascript/bindings/js_bindings_opengl.cpp:3:void GLNode::draw() {
./javascript/bindings/js_bindings_opengl.cpp:27:JSClass  *js_cocos2dx_GLNode_class;
./javascript/bindings/js_bindings_opengl.cpp:28:JSObject *js_cocos2dx_GLNode_prototype;
./javascript/bindings/js_bindings_opengl.cpp:30:JSBool js_cocos2dx_GLNode_constructor(JSContext *cx, uint32_t argc, jsval *vp)
./javascript/bindings/js_bindings_opengl.cpp:34:    GLNode* cobj = new GLNode();
./javascript/bindings/js_bindings_opengl.cpp:41:    TypeTest<GLNode> t;
./javascript/bindings/js_bindings_opengl.cpp:51:    JS_AddNamedObjectRoot(cx, &p->obj, "cocos2d::GLNode");
./javascript/bindings/js_bindings_opengl.cpp:59:void js_cocos2dx_GLNode_finalize(JSFreeOp *fop, JSObject *obj) {
./javascript/bindings/js_bindings_opengl.cpp:62:static JSBool js_cocos2dx_GLNode_ctor(JSContext *cx, uint32_t argc, jsval *vp)
./javascript/bindings/js_bindings_opengl.cpp:65:    GLNode *nobj = new GLNode();
./javascript/bindings/js_bindings_opengl.cpp:68:    JS_AddNamedObjectRoot(cx, &p->obj, "GLNode");
./javascript/bindings/js_bindings_opengl.cpp:73:JSBool js_cocos2dx_GLNode_create(JSContext *cx, uint32_t argc, jsval *vp)
./javascript/bindings/js_bindings_opengl.cpp:75:  GLNode* ret = new GLNode();
./javascript/bindings/js_bindings_opengl.cpp:79:      js_proxy_t *proxy = js_get_or_create_proxy<GLNode>(cx, ret);
./javascript/bindings/js_bindings_opengl.cpp:91:void js_register_cocos2dx_GLNode(JSContext *cx, JSObject *global) {
./javascript/bindings/js_bindings_opengl.cpp:92:  js_cocos2dx_GLNode_class = (JSClass *)calloc(1, sizeof(JSClass));
./javascript/bindings/js_bindings_opengl.cpp:93:  js_cocos2dx_GLNode_class->name = "GLNode";
./javascript/bindings/js_bindings_opengl.cpp:94:  js_cocos2dx_GLNode_class->addProperty = JS_PropertyStub;
./javascript/bindings/js_bindings_opengl.cpp:95:  js_cocos2dx_GLNode_class->delProperty = JS_PropertyStub;
./javascript/bindings/js_bindings_opengl.cpp:96:  js_cocos2dx_GLNode_class->getProperty = JS_PropertyStub;
./javascript/bindings/js_bindings_opengl.cpp:97:  js_cocos2dx_GLNode_class->setProperty = JS_StrictPropertyStub;
./javascript/bindings/js_bindings_opengl.cpp:98:  js_cocos2dx_GLNode_class->enumerate = JS_EnumerateStub;
./javascript/bindings/js_bindings_opengl.cpp:99:  js_cocos2dx_GLNode_class->resolve = JS_ResolveStub;
./javascript/bindings/js_bindings_opengl.cpp:100:  js_cocos2dx_GLNode_class->convert = JS_ConvertStub;
./javascript/bindings/js_bindings_opengl.cpp:101:  js_cocos2dx_GLNode_class->finalize = js_cocos2dx_GLNode_finalize;
./javascript/bindings/js_bindings_opengl.cpp:102:  js_cocos2dx_GLNode_class->flags = JSCLASS_HAS_RESERVED_SLOTS(2);
./javascript/bindings/js_bindings_opengl.cpp:109:      JS_FN("ctor", js_cocos2dx_GLNode_ctor, 0, JSPROP_PERMANENT | JSPROP_ENUMERATE),
./javascript/bindings/js_bindings_opengl.cpp:114:    JS_FN("create", js_cocos2dx_GLNode_create, 0, JSPROP_PERMANENT | JSPROP_ENUMERATE),
./javascript/bindings/js_bindings_opengl.cpp:118:  js_cocos2dx_GLNode_prototype = JS_InitClass(
./javascript/bindings/js_bindings_opengl.cpp:121:                         js_cocos2dx_GLNode_class,
./javascript/bindings/js_bindings_opengl.cpp:122:                         js_cocos2dx_GLNode_constructor, 0, // constructor
./javascript/bindings/js_bindings_opengl.cpp:129:  JS_SetPropertyAttributes(cx, global, "GLNode", JSPROP_ENUMERATE | JSPROP_READONLY, &found);
./javascript/bindings/js_bindings_opengl.cpp:132:  TypeTest<GLNode> t;
./javascript/bindings/js_bindings_opengl.cpp:139:    p->jsclass = js_cocos2dx_GLNode_class;
./javascript/bindings/js_bindings_opengl.cpp:140:    p->proto = js_cocos2dx_GLNode_prototype;
./javascript/bindings/js_bindings_opengl.h:5:class GLNode : public cocos2d::Node {
./javascript/bindings/js_bindings_opengl.h:10:void js_register_cocos2dx_GLNode(JSContext *cx, JSObject *global);
./javascript/bindings/jsb_opengl_registration.cpp:56:    js_register_cocos2dx_GLNode(_cx, ccns);
./javascript/bindings/obfuscate/obfuscate_exclude_cocos2d.js:3049:CSSProperties.prototype.GLNode;
./lua/cocos2dx_support/CCLuaEngine.cpp:615:    extendGLNode(lua_S);
./lua/cocos2dx_support/CCLuaEngine.cpp:726:void LuaEngine::extendGLNode(lua_State* lua_S)
./lua/cocos2dx_support/CCLuaEngine.cpp:731:    lua_pushstring(lua_S,"GLNode");
./lua/cocos2dx_support/CCLuaEngine.cpp:736:        lua_pushcfunction(lua_S,tolua_Cocos2d_GLNode_registerScriptDrawHandler00);
./lua/cocos2dx_support/CCLuaEngine.cpp:739:        lua_pushcfunction(lua_S,tolua_Cocos2d_GLNode_unregisterScriptDrawHandler00);
./lua/cocos2dx_support/CCLuaEngine.h:146:    void extendGLNode(lua_State* lua_S);
./lua/cocos2dx_support/LuaOpengl.cpp:24:void GLNode::draw()
./lua/cocos2dx_support/LuaOpengl.cpp:26:    int handler = ScriptHandlerMgr::getInstance()->getObjectHandler((void*)this, ScriptHandlerMgr::kGLNodeDrawHandler);
./lua/cocos2dx_support/LuaOpengl.cpp:38:    tolua_usertype(tolua_S, "GLNode");
./lua/cocos2dx_support/LuaOpengl.cpp:42:static int tolua_collect_GLNode (lua_State* tolua_S)
./lua/cocos2dx_support/LuaOpengl.cpp:44:    GLNode *self = (GLNode*) tolua_tousertype(tolua_S,1,0);
./lua/cocos2dx_support/LuaOpengl.cpp:63:/* method: create of class  GLNode */
./lua/cocos2dx_support/LuaOpengl.cpp:64:#ifndef TOLUA_DISABLE_tolua_Cocos2d_GLNode_create00
./lua/cocos2dx_support/LuaOpengl.cpp:65:static int tolua_Cocos2d_GLNode_create00(lua_State* tolua_S)
./lua/cocos2dx_support/LuaOpengl.cpp:70:        !tolua_isusertable(tolua_S,1,"GLNode",0,&tolua_err) ||
./lua/cocos2dx_support/LuaOpengl.cpp:77:        GLNode *glNode = new GLNode();
./lua/cocos2dx_support/LuaOpengl.cpp:83:            toluafix_pushusertype_ccobject(tolua_S, nID, pLuaID, (void*)glNode,"GLNode");
./lua/cocos2dx_support/LuaOpengl.cpp:100:/* method: setShaderProgram of class  GLNode */
./lua/cocos2dx_support/LuaOpengl.cpp:101:#ifndef TOLUA_DISABLE_tolua_Cocos2d_GLNode_setShaderProgram00
./lua/cocos2dx_support/LuaOpengl.cpp:102:static int tolua_Cocos2d_GLNode_setShaderProgram00(lua_State* tolua_S)
./lua/cocos2dx_support/LuaOpengl.cpp:107:        !tolua_isusertype(tolua_S,1,"GLNode",0,&tolua_err) ||
./lua/cocos2dx_support/LuaOpengl.cpp:115:        GLNode* self = (GLNode*)  tolua_tousertype(tolua_S,1,0);
./lua/cocos2dx_support/LuaOpengl.cpp:292:/* method: glBindFramebuffer of class  GLNode */
./lua/cocos2dx_support/LuaOpengl.cpp:300:        !tolua_isusertype(tolua_S,1,"GLNode",0,&tolua_err) ||
./lua/cocos2dx_support/LuaOpengl.cpp:6386:      tolua_cclass(tolua_S,"GLNode","GLNode","CCNode",tolua_collect_GLNode);
./lua/cocos2dx_support/LuaOpengl.cpp:6387:        tolua_beginmodule(tolua_S,"GLNode");
./lua/cocos2dx_support/LuaOpengl.cpp:6388:            tolua_function(tolua_S, "create", tolua_Cocos2d_GLNode_create00);
./lua/cocos2dx_support/LuaOpengl.cpp:6389:            tolua_function(tolua_S, "setShaderProgram", tolua_Cocos2d_GLNode_setShaderProgram00);
./lua/cocos2dx_support/LuaOpengl.h:13:class GLNode:public cocos2d::Node
./lua/cocos2dx_support/LuaScriptHandlerMgr.cpp:584:int tolua_Cocos2d_GLNode_registerScriptDrawHandler00(lua_State* tolua_S)
./lua/cocos2dx_support/LuaScriptHandlerMgr.cpp:588:    if (!tolua_isusertype(tolua_S,1,"GLNode",0,&tolua_err) ||
./lua/cocos2dx_support/LuaScriptHandlerMgr.cpp:595:        GLNode* glNode = (GLNode*)  tolua_tousertype(tolua_S,1,0);
./lua/cocos2dx_support/LuaScriptHandlerMgr.cpp:597:        ScriptHandlerMgr::getInstance()->addObjectHandler((void*)glNode, handler, ScriptHandlerMgr::kGLNodeDrawHandler);
./lua/cocos2dx_support/LuaScriptHandlerMgr.cpp:608:int tolua_Cocos2d_GLNode_unregisterScriptDrawHandler00(lua_State* tolua_S)
./lua/cocos2dx_support/LuaScriptHandlerMgr.cpp:612:    if (!tolua_isusertype(tolua_S,1,"GLNode",0,&tolua_err) ||
./lua/cocos2dx_support/LuaScriptHandlerMgr.cpp:618:        GLNode* glNode = (GLNode*)tolua_tousertype(tolua_S,1,0);
./lua/cocos2dx_support/LuaScriptHandlerMgr.cpp:619:        ScriptHandlerMgr::getInstance()->removeObjectHandler((void*)glNode,ScriptHandlerMgr::kGLNodeDrawHandler);
./lua/cocos2dx_support/LuaScriptHandlerMgr.cpp:779:        tolua_constant(tolua_S,"kGLNodeDrawHandler",ScriptHandlerMgr::kGLNodeDrawHandler);
./lua/cocos2dx_support/LuaScriptHandlerMgr.h:99:       kGLNodeDrawHandler,
./lua/cocos2dx_support/LuaScriptHandlerMgr.h:137:TOLUA_API int tolua_Cocos2d_GLNode_registerScriptDrawHandler00(lua_State* tolua_S);
./lua/cocos2dx_support/LuaScriptHandlerMgr.h:138:TOLUA_API int tolua_Cocos2d_GLNode_unregisterScriptDrawHandler00(lua_State* tolua_S);
./lua/script/Opengl.lua:297:    return GLNode:create()
Grep finished (matches found) at Mon Nov 11 13:55:07

目测感觉两者差不多,而且代码量也不多,所以修改其中之一的 GLNode 类名称是可行的,总共八十多行结果,修改一种就四十行左右,而且其中出现的 “GLNode” 关键字,并不是都需要修改,有的是绑定到脚本引擎内部的,我们只需要修改其 C++ 端绑定的类型就 OK 了,一叶修改了 lua 绑定中的 GLNode 名字为 LuaGLNode,然后定位到 LuaOpengl 和相关文件(两个文件左右),查找并替换其中一部分 “GLNode” 关键字代码,这里借助 Emacs ,递进式的关键字查询替换,通过匹配规则,一个个过滤,手动选择是替换还是不替换,最后替换了所有 C++ 中 Lua 端的 GLNode 类名实现(替换的内容很少,比我想象中要少)。

到这里就已经完成能够让两种脚本引擎同时运行的方法。并且一叶修改了入口函数,在 AppDelegate 入口处,添加修改:

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
bool AppDelegate::applicationDidFinishLaunching()
{
    // initialize director
    Director *pDirector = Director::getInstance();
    pDirector->setOpenGLView(EGLView::getInstance());

    EGLView::getInstance()->setDesignResolutionSize(480, 320, ResolutionPolicy::NO_BORDER);

    // turn on display FPS
    pDirector->setDisplayStats(true);

    // set FPS. the default value is 1.0/60 if you don't call this
    pDirector->setAnimationInterval(1.0 / 60);

//    runLua();
    runCpp();
//    runJsb();

    return true;
}

void AppDelegate::runCpp(){
    auto scene = HelloLeafsoar::scene();
    Director::getInstance()->runWithScene(scene);
}

void AppDelegate::runJsb(){
    ScriptingCore* sc = ScriptingCore::getInstance();
    sc->addRegisterCallback(register_all_cocos2dx);
    sc->addRegisterCallback(register_all_cocos2dx_extension);
    sc->addRegisterCallback(register_cocos2dx_js_extensions);
    sc->addRegisterCallback(jsb_register_chipmunk);
    sc->addRegisterCallback(register_all_cocos2dx_extension_manual);
    sc->addRegisterCallback(register_CCBuilderReader);
    sc->addRegisterCallback(jsb_register_system);
    sc->addRegisterCallback(JSB_register_opengl);

    sc->start();

    ScriptEngineProtocol *pEngine = ScriptingCore::getInstance();
    ScriptEngineManager::getInstance()->setScriptEngine(pEngine);
    ScriptingCore::getInstance()->runScript("main.js");
}

void AppDelegate::runLua(){
    LuaEngine* luaEngine = LuaEngine::getInstance();
    ScriptEngineManager::getInstance()->setScriptEngine(luaEngine);

    std::string path = FileUtils::getInstance()->fullPathForFilename("hello.lua");
    luaEngine->executeScriptFile(path.c_str());
}

可以看到这里有三个方法,runCpp 、runLua 和 runJsb,这三者可单独独立运行,也可同时运行,在脚本实现不同的 log 打印,便能知晓,但如如果你在这三处同时运行了一个场景的话,那么根据场景的运行规则,后面运行的场景会入栈,替换之前场景的运行,三者的运行顺序,可以随意修改,并观其运行结果。为了测试这一点,我们同时运行 runLua、runCpp 和 runJsb ,然后使用一个全局定时器(请看 实现 『Cocos2d-x 全局定时器』 一文),每三秒钟弹出一个场景。而每一个场景的内部不同,这样我们便能看见首先运行了 runJsb 场景,三秒后 runLua ,之后 runCpp 场景,最终三秒后,所有场景弹出,Game Over 了。

脚本绑定技术的特性

以上通过对源代码的修改,一个例子,让 lua 和 js 两种脚本引擎同时运行,由于在任何时候只能有一个场景运行,所以,无论由 C++、lua 还是 js 来启动游戏,另外两种语言将会得不倒执行的权利,但是从另一个侧面,全局定时器它得的运行并不依赖场景的运行,这对我们研究程序执行过程中对象的特性提供了方便,前文通过一个 C++ 端实现的全局定时器,不论你运行的是不是脚本,是什么脚本,都不影响它之运行,那么我们就能能用这个定时器去定期的调用各种脚本,以让这三种脚本语言同时运行,在这样一个过程中,去验证对它们的内存分布,操作机制的的情况等 ~

一叶在 C++ 端开启了一个定时器,且由 C++ 运行了第一个场景,然后修改全局定时器的定时调用实现,添加对 lua 和 js 的脚本调用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
void GlobalSchedule::globalUpdate(float dt) {
    // 这里写全局定时器的逻辑处理代码
    CCLOG("global update");
//    Director::getInstance()->popScene();
    count ++;

    auto scene = Director::getInstance()->getRunningScene();
    LabelTTF* label = (LabelTTF*)scene->getChildByTag(100);
    if (!label){
        label = LabelTTF::create("一叶 v 5~", "", 24);
        label->setPosition(Point(250, 300));
        label->setColor(Color3B::BLACK);
        scene->addChild(label, 1, 100);
    } else {
        if (count % 3 == 0)
            label->setScale(1);
        else if (count % 3 == 1)
            ScriptingCore::getInstance()->runScript("jsbMethod.js");
        else if (count %3 == 2)
            LuaEngine::getInstance()->executeScriptFile("luaMethod.lua");
    }
}

这是全局定时器的实现,它在当前运行的场景中添加了一个 Label ,label 的内容 “一叶 v 5~“,并且通过一个执行技术 count 来决定当前执行的是 C++ 还是 lua 或者 js,在不同的语言中,修改同一个元素的大小 Scale,看看 js 和 lua 的 method 文件实现:

1
2
3
4
5
6
7
8
9
10
-- luaMethod.lua 具体实现
cclog = function(...)
    print(string.format(...))
end

cclog("lua method .");

local scene = CCDirector:getInstance():getRunningScene()
local label = scene:getChildByTag(100)
label:setScale(0.8)
1
2
3
4
5
6
// jsbMethod.js 具体实现
cc.log(" jsb method .");

var scene = cc.Director.getInstance().getRunningScene();
var label = scene.getChildByTag(100);
label.setScale(1.2);

以上的代码都很简单,各种语言的逻辑一样,首先或者当前运行着的场景,然后通过 tag 获取场景中的元素 Label,再之修改它的大小,这样将程序运行,便能看见此 Label 定时改变大小,而且是三种大小状态不停的切换,可见已经完成了我们之前的目标,双开脚本引擎,用不语言控制同一个元素(文章最后给出所有代码)。

绑定技术的特性浅析

说道脚本绑定技术,这里可以插入一个新的内容来说,Cocos2d-html5 版本!作为对比,更为明了,h5 的实现和 jsb 的实现显然不同,h5 是跑在浏览器上的,jsb 是跑在移动终端上的。但是它们都用统一的接口实现,即用 js 写的游戏(同一套代码)即能够跑在浏览器上,又能够能跑在手机的 js 引擎上,这两者之间 表面相同,本质不同 ,也是隐藏了内部实现,提供统一的接口让写程序更为简单。有兴趣的朋友可以去了解一下。在 Emacs 内置的 lisp 语言函数中有很多性能要求比较高的是使用 c 语言实现的,但在调用的时候全然不知(不知道就对了)。

而 jsb 和 lua 之间:知其不同,是见其表,知其皆同,是知其本,舍不同而观其同,可游心于物之初,哈。 在脚本引擎库中,以 C++ 实现的类型为基本,通过动态往虚拟机(引擎环境,或者上下文对象)里添加类型定义并绑定,每钟脚本类型都有其对应的 C++ 类型作为依据,通过脚本创建的对象最终被映射为调用 C++ 创建对象,而在 C++ 中创建的对象,也可以在脚本中随时获取,并修改其属性,当然其内部还有复杂的内存管理解决方案 (特别是本文中这样混合形的运行时环境,其对象生命周期就更复杂了,关系到引擎内部实现的细节,由不同语言创建的对象,由谁管理,由谁释放等等),在需要之时可以深究,而这里显然没有必要(这里引擎双开仅作学习之用),宏观角度考量,内存管理无非是定义一套规则,或是规范,这样能保证出错误的最小可能性(在 『Cocos2d-x 内存管理浅说』 , 『Cocos2d-x 内存管理的一种实现』, 『深入理解 Cocos2d-x 内存管理』) 几篇文章有怎样通过编程规范来尽量避免内存出现的问题)。

如果我们需要添加一种新脚本绑定实现,比如使用 lisp 语言作为绑定 (Emacs 用户首先想到的就是 lisp 了),那么我们需要一个 lisp 运行时环境的实现,然后通过函数绑定,javascript 和 lua 的第一类型是 函数(First-class Function),它们都有很强的函数式语言特性,其封装的 Cocos2d-x 调用方式不过是语法糖衣,看起来像面相对象而已,此点 js 表现更甚。所以对于 lisp 实现来说,是可行的,至于最后写起来是否顺手不得而知,目测如果实现,写法更像 lua 对 Cocos2d-x 的 style,可能很好使,可能很糟糕 :P 也许使用对象形的脚本语言更加合宜。

非吾小天下 宏观而已

想要玩转这里脚本引擎,那么你至少会绑定,知道怎么绑定,其具体步骤 ?jsb 怎么手动绑定,lua 怎么手动绑定,而且还有自动绑定脚本,jsb 的内部使用了 spidermonkey 开源的 js 引擎,lua 还可以开 jit,其运行环境有很多复杂的上下文参数,各种错综复杂,提出各种专有的概念,有着各自不同的游戏规则,其内部的细枝末叶是对具体问题的解决方案,然而有的时候我们并不知道需要解决的问题由来,一环套着一环,我在这里却避而不谈,一方面是因为我也不知 :p ,另一方面是因为不想让我或者别人陷入这样那样的泥沼中去,从宏观的角度去看问题,或者抽象,在 lua 和 js 这两种脚本引擎中,其特点为何,能达到什么样的效果,在本文的操作过程,并没有什么复杂的步骤,修改了一处编译报错问题,引擎双开,操作同一对象。避开了很多各脚本的内部实现细节内容。把复杂的问题简单化 ~

关于本文的内容,一叶会将代码项目提交到 github 以供参考(在文章最后给出1),内容不多,组织凌乱(时不时想修改,临时添加以看不同的测试效果),所以就将就着看了,哈,我的博文从不倾向给出一个完整的解决方案,以思路为重,其过程比结果更为重要。

Comments