这是个对cocos JS的简单经验谈,让大家明白引擎原理,以及使用JS开发过程中要注意的事项,但不会涉及太多基本编程知识以及基础安装等,在阅读本文前请先对Javascript与Cocos有基本了解。
Cocos JS项目组成
首先,你可能会问:为什么cocos JS能同时开发HTML5与C++游戏?
我们先看看cocos JS 3.x(3.3)项目目录构成:
先建立一个项目:
cocos new HelloCococJS -p com.163.cocosjs -l js --ios-bundleid com.163.cocosjs
大家看图上的标注:
- cocos2d-html5引擎:纯html5引擎,在浏览器上的图形处理全靠它了;
- js-bindings:为什么JS可以写原生游戏,关键就在这里;
- runtime-src:原生的runtime工程,基础的Class文件,Android到iOS甚至Win32的工程;
- tools:JSBinding工具
最让人好奇的是js-bindings,进入里面,里面有3个目录:
- bindings:绑定脚本,一个脱水层,用于把cocos JS耦合到到原生引擎,稍候会讲到绑定原理;
- cocos2d-x:cocos2d-x的C++引擎,就是那个原生引擎了;
- external:里面就只有SpiderMonkey JS引擎,Firefox就是用它了,相比V8引擎,在kraken time与sunspider time测试中略胜V8。
上图:鲜红色为Firefox,分数越低越好
看到这里,相信不少同学已经发出“哦~”一声,原来如此!
没错,cocos JS组件内包含了3个引擎1个工具:
- 在HTML5时,运行的是cocos2d-html5引擎,采用canvas或WebGL渲染;
- 在打包成原生时,SpiderMonkey做JS脚本运行解释器打包进去,同时编译cocos2d-x C++,前者通过JSB让JS调用后者的C++处理图形,原生除了JS外,已经与html5完全无关了,JS就是脚本语言,类似Lua。
JSBinding原理
JSBinding项目最初由zynga实现,用于胶合JS与Objective C的cocos2d-iphone,项目Github: jsbindings
JavaScript Bindings for C / Objective-C (JSB) is the "glue" code (or wrapper code) that sits between native code (C or Objective-C) and JavaScript (JS) code. JSB allows calling native code from JS and vice-versa.
现在已经移植到这里来,用于把cocos2d-x生成对应的JS接口,方法大致为:
编写配置文件.ini,通过tools下的bindings-generator,把C++类生成对应的辅助C++脚本以及Javascript接口脚本。
配置文件.ini作用
JSB本身就是一个工具,在项目中不负责运行代码,只生成C++对应的JS,而配置文件就是生成的规则,例如JSBinding 上的描述:
// CCAnimation (from cocos2d-iphone v2.0)
+(id) animationWithAnimationFrames:(NSArray*)arrayOfAnimationFrames delayPerUnit:(float)delayPerUnit loops:(NSUInteger)loops;
默认生成以下JS:
// ugly
cc.CCAnimation.animationWithAnimationFrames_delayPerUnit_loops_( frames, delay, loops );
通过规则配置,
method_properties = CCAnimation # animationWithAnimationFrames:delayPerUnit:loops: = name:"create",
可以生成:
// more JS friendly
cc.Animation.create( frames, delay, loops );
JS调用C++细节
JS:
gnode = cc.Node.create();
JSB C++:
JSBool js_cocos2dx_CCNode_create(JSContext *cx, uint32_t argc, jsval *vp)
{
if (argc == 0) {
cocos2d::CCNode* ret = cocos2d::CCNode::create();
jsval jsret;
do {
if (ret) {
js_proxy_t *proxy = js_get_or_create_proxy(cx, ret);
jsret = OBJECT_TO_JSVAL(proxy->obj);
} else {
jsret = JSVAL_NULL;
}
} while (0);
JS_SET_RVAL(cx, vp, jsret);
return JS_TRUE;
}
JS_ReportError(cx, "wrong number of arguments");
return JS_FALSE;
}
到这里,相信大家明白其中原理了,更深入的可以看文件夹:tools/bindings-generator/test 里的例子。
好了,JSBinding原理就简单说到这里。
实践心得
明白了上面的原理,假如要用JS作用脚本写原生游戏,应该怎么做呢?
在实践过程中,JS中大部分在 cc
命名空间下的接口都有对应的C++ API,小部分html5引擎特有的,有可能是历史遗留,幸好在开发过程中H5是可以实时预览的,也可以容易的放到iOS原生模拟器中看,如果出现类似JSB class not found的错误基本就是这个原因了,换一个写法就可以,这也是一个坑,需要慢慢踩。这里主要说一下JS怎么编码,因为JS自由度太大,所以最好结合JSB原理遵守一些规范。
(如有错误请不吝指正)
- 规范命名空间,例如 var tt = {} || tt;
- JS类:统一用cc.Class.extend来实现,例如:
tt.Monster = cc.Class.extend({
id: ""
level: 1,
texture: null
});
这个好处是让JS类做到类似C++那样的继承,构造函数中通过this._super()可以调用父类构造函数;
- 除了cc,ccui这些引擎指定的命名空间可能会调用C++外,所有JS的编写与浏览器中编写没有任何区别;
- 效率问题:一般来说,不可交互的动画动作,都直接转化成C++接口,之后再也与JS无关不用担心效率问题;
- 可交互动作,例如touch事件拖动精灵,减少JS里的复杂运算增加效率,但经过测试拖动精灵原生中没有让人感觉有区别;
- 资源:虽然加载方式不一样,但原生使用上区别不大,两个点:一是touch事件接口有区别有坑要避雷,二是cocos studio只支持1.6及以前的json文件,以及最新cocos 2.1的json文件。
this.stage = guiReader.widgetFromJsonFile(res.StageUI_json);
this.addChild(this.stage, 200);
this.player = ccui.helper.seekWidgetByName(this.stage, "Player");
- 其它:虽然很多JS写法比较便利,例如
sripte.x = 20
,但建议写成sprite.setPositionX(20)
,只为避雷;
包大小问题
采用JSB写游戏的好处是可以真正的多端运行,但从原理可知这是怎么一个回事,如果要做原生,唯一问题就是包大小了,SpiderMonkey本身就近10m的.so文件,最后打出的iOS近10M,apk包7M,纯C++的iOS才2M多,元芳怎么看?
扩展思考
看了上面的原理,应该不难知道Egret也是类似这么干了吧,但Egret优点是JS的编写用了TypeScript,微软的进阶版JS,更OO和利于组件模块化。
时间有限,先介绍到这里,下一篇会写关于网络请求相关。
参考文献
- Javascript Binding(简称JSB)自动绑定教程
- Cocos2d-x从C++到JS的进阶之路
- Cocos2dx: Javascript Binding
- Github:zynga/jsbindings
- http://arewefastyet.com/
- 关于V8 JavaScript Engine的使用方法研究
- http://www.zhihu.com/question/21130385