找回密码
 立即注册
查看: 1554|回复: 15

[简易教程] Cocos2D-JS 加载速度优化

[复制链接]
发表于 2019-1-30 18:12 | 显示全部楼层 |阅读模式
前一段时间对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/



作者:凡凡的小web
链接:https://www.jianshu.com/p/ec4de82a2fdf
來源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

发表于 2019-4-2 09:13 | 显示全部楼层
楼主是超人
发表于 2019-4-2 09:15 | 显示全部楼层
好帖就是要顶
发表于 2019-4-2 09:11 | 显示全部楼层
很好哦
发表于 2019-4-2 09:06 | 显示全部楼层
不错不错
发表于 2019-4-2 09:03 | 显示全部楼层
LZ真是人才
发表于 2019-8-20 07:39 | 显示全部楼层
好帖就是要顶
发表于 2019-8-20 08:18 | 显示全部楼层
真心顶
发表于 2019-8-20 07:42 | 显示全部楼层
难得一见的好帖
发表于 2019-8-20 07:54 | 显示全部楼层
不错不错
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2024-3-30 00:03 , Processed in 0.102008 second(s), 26 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表