Cocos2D-JS 加载速度优化

前一段时间对Cocos2D-JS的项目做了一次载入速度优化,在这里记录一下。

一、问题分析

优化前游戏在iPhone 4上从启动画面到渲染第一帧需要8秒左右,一直卡在启动画面不动。分析了一下代码,怀疑AppDelegate::didFinishLaunchWithOptions里做了太多事情。用Instruments分析一下,果然didFinishLaunchWithOptions用了5s,其中ScriptingCore::runScript用了2.5s,向JSContext注入binding用了0.5s,剩下各种SDK初始化用了2s。

二、优化方案

1. 加速代码的执行速度

ScriptingCore::runScript

ScriptingCore::runScript主要在读取js代码、编译然后执行。这里有几个优化的方法:

 1. 将JS代码编译成bytecode(jsc)再打到包里,这样加载时就不用再编译了。

 2. 将JS代码用UglifyJS、JSMin等压缩工具压缩,并合并成一个JS文件,减少磁盘IO的大小和次数。

压缩打包JS会带来一些问题。压缩后错误信息会比较难看,因为symbol都被压成1个字母了。另一个更严重的问题是,我们有动态更新代码的需求,以前每次只需要更新改动的JS文件,打包成一个文件后每次都更新一整个文件。

并行化

Instruments的数据里可以看出有米广告的SDK居然用了1.3s载入,在5s上也需要200ms,干脆放到单独的线程里去做,这样不会block主线程(iPhone4还是单核的A4处理器,所以开多少线程都没有什么卵用,4s和iPad2之后用的至少是双核的A5,收效就很明显)。

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void){

    // 有米广告的初始化代码

    [CocoBVideo cBVideoInitWithAppID:appID cBVideoAppIDSecret:secret];

    [CocoBVideo cBCloseAlertViewWhenWantExit:false];

});

2. 从用户体验的角度优化

上面这些耗时的事启动游戏时必须要做的,即使充分优化也可能要占用可观的时间。我们回过头想一下优化速度的目的是什么:给用户更好的启动体验避免加载时间过长导致流失。从这角度我们可以想:假如我们载入必须要花很长时间,有什么方法能让这个载入过程的体验更好。

我们采用方案是,didFinishLaunchWithOptions里显示出一个带进度条的载入界面,然后一边载入各种东西一边更新进度条,避免特别长时间的UI静止。这样玩家就不会以为游戏卡死而流失。整个启动载入的流程如下:

1. 显示C++的LoadingLayer(游戏背景 + 进入条)

2. 初始化各种第三方SDK

3. 初始化JS Context

4. JS代码中的游戏逻辑初始化,载入存档、数据表等等

5. JS代码载入主界面资源,显示有主界面

LoadingLayer得用cocos2D-x C++来写,这样就可以在JS Context初始化之前显示出来。并且尽量让这个UI不要用太多资源。JS Context初始化完成后,LoadingLayer上的进度条转由main.js来控制,为了让JS能方便得到ProgressTimer对象,最简单的方式是在C++里给LoadingLayer和ProgressTimer设置一个特殊的tag。

想让用户能进度条更新,需要把上面的工作分到不同帧里来完成,好在这在3.0版本里很容易,把下一步的工作放到一个lambda里schedule一下就行了,就和JS里德setTimeout(func, 0)一样。另外JS的资源载入最好用TextureCache的addImageAsync方法,以免block主线程。

auto func = [this] (float t) { _setupLibraries(); };

director->getScheduler()->schedule(func, this, 0.0, 0, 0, false, "_setupLibraries");

另外有一个问题是,Cocos2D-JS 3.0之后didFinishLaunchWithOptions返回时,第一帧并没有渲染,导致屏幕会黑一下,需要强制渲染避免这个问题。

用关键代码来描述一下整个加载过程

AppDelegate.cpp:

bool AppDelegate::applicationDidFinishLaunching() {

    ...

    // 生成 LoadingLayer

    _loadingLayer = LoadingLayer::create();

    _loadingLayer->setTag(100);

    Scene * scene = Scene::create();

    scene->addChild(_loadingLayer);

    // 强制渲染第一帧

    director->runWithScene(scene);

    director->drawScene();

    director->getRenderer()->render();

    auto func = [this] (float t) { _setupLibraries(); };

    director->getScheduler()->schedule(func, this, 0.0, 0, 0, false, "_setupLibraries");

}

void AppDelegate::_setupLibraries() {

    // 在这里初始化各种SDK ...

    // 更新进度,下一帧载入JS Context

    _loadingLayer->setPercent(40);

    _loadingLayer->setPercent(20);

    auto func = [this] (float t) { _setupJSBEnv(); };

    Director::getInstance()->getScheduler()->schedule(func, this, 0.0, 0, 0, false, "_setupJSBEnv");

}

void AppDelegate::_setupJSBEnv() {

    // 初始化JS Context

    ScriptingCore* sc = ScriptingCore::getInstance();

    sc->addRegisterCallback(register_all_cocos2dx);

    ...

    // 执行Cocos2D-JS的启动脚本

    auto func = [this] (float t) { _runBootScript(); };

    Director::getInstance()->getScheduler()->schedule(func, this, 0.0, 0, 0, false, "_runBootScript");

}

void AppDelegate::_runBootScript() {

    ScriptingCore::getInstance()->runScript("script/jsb_boot.js");

    ScriptingCore::getInstance()->runScript("js/main.js");

    _loadingLayer->setPercent(50);

}

main.js:

cc.game.onStart = function() {

    var addProgress = function(percent) {

        if (!cc.sys.isNative) return;

        var progress = cc.director.getRunningScene().getChildByTag(100).getChildByTag(100);

        progress.setPercentage(progress.getPercentage() + percent);

    };

    //load resources

    cc.LoaderScene.preload(g_resources, function() {

        cl.init(); // js init code

        addProgress(10);

        setTimeout(function() {

            var res = cl.getCommonRes().concat(cl.ModeSelectLayer.getRes());

            Util.preloadRes(res, function() {

                addProgress(100);

                setTimeout(function() { Util.runScene(cl.ModeSelectLayer)  }, 0);

            }, addProgress.bind(null, 2));

        }, 0);

    });

}

经过这些优化,游戏会很快从Launch Image进入Loading界面,进度条随着载入的进行不断更新,整个载入过程在iPhone4上也从6s降到了3s,体验比之前好了很多。

三、一些问题

强制渲染第一帧cc.game.restart方法重启游戏时崩溃,因为restart时也会调用didFinishLaunchWithOptions,解决办法是重启时不强制渲染,需要用一个全局变量startCount来识别是否为重启。

if (startCount == 1) {

    director->drawScene();

    director->getRenderer()->render();

    auto func = [this] (float t) { _setupLibraries(); };

    director->getScheduler()->schedule(func, this, 0.0, 0, 0, false, "_setupLibraries");

} else {

    _setupJSBEnv();

}

另外由于JS Context的初始化被延后了,有一些刚起动就执行的功能会遇到一些问题。比如点击微信的App消息、remote push启动游戏,需要先把状态暂存,等JS Context起来后,在调用相关逻辑。

原文链接:http://boundary.cc/2015/07/cocos2d-js-loading-optimization/

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,569评论 4 363
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,499评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,271评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,087评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,474评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,670评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,911评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,636评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,397评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,607评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,093评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,418评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,074评论 3 237
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,092评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,865评论 0 196
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,726评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,627评论 2 270

推荐阅读更多精彩内容

  • SwiftDay011.MySwiftimport UIKitprintln("Hello Swift!")var...
    smile丽语阅读 3,806评论 0 6
  • 我一向喜欢阿丁。有一次,他在公众号里说,读者赞赏后可以任意提问。 我的问题是:如果你被放逐到一座荒岛,只准带三本书...
    某人李下阅读 8,393评论 75 215
  • 风,刮吧。再大我都不怕。 雨,下吧。淋湿我的脸颊。 八月的柳树,以不在浮华。 ...
    A文氓阅读 306评论 0 1
  • 让我借一盏灯的温暖, 仰望一片雪花, 在这初冬有些沉郁, 沉郁而儒雅的紫色里。 灯微微的, 夜微微的。 我在想, ...
    枝有木兮木无山阅读 331评论 4 5