前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >想做更深入的加载优化?剖析Cocos引擎底层架构后,乐府大佬交出「90分答案」

想做更深入的加载优化?剖析Cocos引擎底层架构后,乐府大佬交出「90分答案」

作者头像
张晓衡
发布于 2023-02-23 05:34:16
发布于 2023-02-23 05:34:16
2.8K00
代码可运行
举报
运行总次数:0
代码可运行

引言:无论是对引擎研发团队或是游戏开发团队来说,优化的重要性都不言而喻。本次,来自乐府互娱「乐府小学生」在实际项目开发中,通过修改引擎源码实现了更加深入的加载优化。

游戏江湖上曾流传过一句名言:“三流的游戏做功能,二流的游戏做表现,一流的游戏做优化。”虽然有点扯,但并非全无道理,至少说明了优化在做游戏中的重要性。本文将结合我参与的项目实例,分享我们是如何“站在 Cocos Creator 的肩膀上”做更深入的加载优化。

本文所用引擎版本为 Cocos Creator 2.4.6。

一、原音重现

Cocos Creator 的加载流程

以上是 loadRes 的加载流程,其中的关键步骤说明如下:

  • url tranform:主要是将工程路径地址 /uuid 转换成对应的实际资源地址。
  • load res:主要是文件的 IO 过程,并把加载后的资源转成对应的 Json 对象或二进制数组。
  • parse:主要是把加载到的资源解析成对应的对象。
  • depends:获取当前资源的依赖,然后继续调用开始的步骤加载。

剖析 Prefab 的加载流

以上流程左侧清晰地展示了 Cocos Creator 的加载管线,从引擎源码获知从 url transform 至 depends 前的流程都可以插入自定义管线,具备较好的灵活性和扩展性。

右侧部分为 cc.Spriteframe 资源的加载流程,这里为了展示区别,我们将其与 Cocos2d-x 中的 CCSprite 加载进行对比:

不难看出在 Cocos Creator 中创建一个 Sprite 会比 Cocos2d-x 时多两个流程。而从 IO 次数上对比,单张贴图的加载上 Cocos Creator 比 Cocos2d-x 多2次 IO(SpriteFrame 配置和 Texture2d 配置)。那么这两个配置是否是必要的?

答案还得从 Cocos Creator 本身的特性说起:

1、SpriteFrame 配置文件(下文简称【配置1】):一个独立的 json 文件,用来存储一九宫,以及纹理大小偏移等信息。可以使纹理自定义修改九宫图等更灵活。对应的就是下面属性面板中的信息:

TIPS :Cocos2d-x 时期的配置是保存在对应 ui 编辑器生成的配置文件里,其他没有被界面引用的资源,需要在代码中指定配置。

2、Texture2d 的配置(下文简称【配置2】):主要定义纹理相关属性。

上图显示,有两个属性配置(WarpMode, FilterMode)会使我们使用图片和修改配置上更灵活。

综上,Cocos Creator 加载流程多出的两个配置是必要的。那么在效率上是否有优化空间?

二、选 A 还是选 C

官方的构建发布界面上有关于贴图配置的合并选项:

官方文档的解释如下:

内联所有 SpriteFrame

自动合并资源时,将所有 SpriteFrame 与被依赖的资源合并到同一个包中。建议网页平台开启,启用后会略微增大总包体,多消耗一点点网络流量,但是能显著减少网络请求数量。建议原生平台关闭,因为会增大热更新时的体积。

合并图集中的 SpriteFrame

将图集中的全部 SpriteFrame 合并到同一个包中。默认关闭,启用后能够减少热更新时需要下载的 SpriteFrame 文件数量,但如果图集中的 SpriteFrame 数量很多,则可能会稍微延长原生平台上的启动时间。

如果项目中图集较多,有可能会导致 project.manifest 文件过大,建议勾选该项来减小 project.manifest 的体积。

注意:在热更新时,需要确保新旧项目中该功能的开启/关闭状态保持一致,否则会导致热更新之后出现资源引用错误的情况。

通俗的解释就是:

  • 内联:将 SpriteFrame 对应的 json 文件【配置1】合并到了 prefab 中。
  • 合并图集:把自动图集中所有 SpriteFrame 合并到同一个文件中,类似 TexturePacker 的 plist 文件。

各自的优缺点,在官方文档中有详细描述。那么有没有一种解决方案,即能提高加载效率,又不影响启动速度呢?

三、90分答案

本项目所采用的解决办法是:

  1. 合并所有的 SpriteFrame 的配置,减少 IO。
  2. 将合并后的配置转成二进制文件,加快启动速度。

SpriteFrame 配置优化

下面是 SpriteFrame 配置信息,只有 "e8Ueib+qJEhL6mXAHdnwbi"(依赖)和中间的数据区是不同的:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
[
  1,
  [
    "e8Ueib+qJEhL6mXAHdnwbi"
  ],
  [
    "_textureSetter"
  ],
  [
    "cc.SpriteFrame"
  ],
  0,
  [
    {
      "name": "default_btn_normal",
      "rect": [
        0,
        0,
        40,
        40
      ],
      "offset": [
        0,
        0
      ],
      "originalSize": [
        40,
        40
      ],
      "capInsets": [
        12,
        12,
        12,
        12
      ]
    }
  ],
  [
    0
  ],
  0,
  [
    0
  ],
  [
    0
  ],
  [
    0
  ]
]
  • 解决方案

1、相同的部分作为模板定义在代码中(减少冗余数据),提取所有的差异部分合并到同一个文件中,组成如下配置:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
{[
{
      "name": "default_btn_normal",
      "rect": [
        0,
        0,
        40,
        40
      ],
      "offset": [
        0,
        0
      ],
      "originalSize": [
        40,
        40
      ],
      "capInsets": [
        12,
        12,
        12,
        12
      ],
      "depend": "e8Ueib+qJEhL6mXAHdnwbi" // 额外加入字段
 },
 ...
 ],
 [uuid1,uuid2,...] // 额外加入字段为文件的uuid,与上面的顺序保持一致
}

2、将文件转成二进制格式,这样可以有效降低文件大小,提高初始化速度,并且减少数据和字段冗余。二进制方案推荐使用 flatbuffers,具体使用方法可以参考网上教程或官方文档。

3、接管游戏下载流程,保证文件正常读取。

3.1 接管 IO:修改 builtin/jsb-adapter/engine/ jsb-fs-utils.js 文件,添加如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
setJsonReadHandler(handler) {
        fsUtils._customJsonLoadHandler = handler
    },

    readJson (filePath, onComplete) {
        let jsonLoadhandler = fsUtils._customJsonLoadHandler
        if (jsonLoadhandler && jsonLoadhandler(filePath, onComplete)) {
            return
        }
        fsUtils.readFile(filePath, 'utf8', function (err, text) {
            var out = null;
            if (!err) {
                try {
                    out = JSON.parse(text);
                }
                catch (e) {
                    cc.warn(`Read json failed: path: ${filePath} message: ${e.message}`);
                    err = new Error(e.message);
                }
            }
            onComplete && onComplete(err, out);
        });
    },

注:这里是原生端的修改部分,网页端可以通过自定义加载管线的方式处理

3.2 数据还原:通过模板数据和二进制数据对 SpriteFrame 格式做还原,是这里的数据区存为 flatbuffers 对象即可,用到的地方再去解析:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
[
  1,
  [
    "e8Ueib+qJEhL6mXAHdnwbi"
  ],
  [
    "_textureSetter"
  ],
  [
    "cc.SpriteFrame"
  ],
  0,
  [
    // flatbuffer对象
  ],
  [
    0
  ],
  0,
  [
    0
  ],
  [
    0
  ],
  [
    0
  ]
]

3.3 修改 CCSpriteframe.js 文件,修改解析:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
_deserialize: function (data, handle) {
        if (!CC_EDITOR && data.bb) {
            this._deserializeWithFlatbuffers(data);
            return;
        }
        ...
}

Texture2d 配置优化

Texture2d 的配置如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
[
  1,
  0,
  0,
  [
    "cc.Texture2D"
  ],
  0,
  [
    "0,9729,9729,33071,33071,0,0,1",
    -1
  ],
  [
    0
  ],
  0,
  [],
  [],
  []
]

与 SpriteFrame 配置相比,Texture2d 的配置简单多了,里面的属性值主要是与属性面板和文件扩展名有关。如果图片的属性都是默认的,并且扩展名是相同的情况下,Texture2d 配置是完全相同的,即项目中若有200张图片资源,那200个图片的配置文件就是完全相同的。

  • 解决方案

通过 md5 比对所有的 Texture2d 配置文件,提取不同的文件,生成对应的配置映射以便快速读取。以我当前的项目为例:有9000+图片资源,最终比对下来也就只有5种类型,所以就直接把这5种配置在代码中写死,同样在上面的接管流程中返回对应的配置信息。

优化前后,iphone6 测试的加载速度提升了43%左右:

Texture2d 加载流程优化

原生的纹理加载的流程,把纹理数据转换成 ArrayBuffer 传给 js,然后在 js 层再重新组装返回 C++ 层,这里存在两次数据传递的过程。流程如下:

优化的方向:在加载完成后,原生层一步到位。直接创建成 Texture2d 对象返回,减少中间的数据传入过程。修改后的流程如下(红框部分为省略的部分):

注:修改为如上流程后,原生端的动态合图将无法使用。但是大多数的原生开发都会使用压缩纹理,并且压缩纹理也是不支持动态合图的。所以动态合图的问题大家完全可以忽略。

  • 代码修改如下:

C++ 部分:

cocos2d-x/cocos/scripting/js-bindings/manual/jsb_global.cpp

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
...
if (loadSucceed)
{
  se::Object* retObj = se::Object::createPlainObject();
  retObj->root();
  refs.push_back(retObj);
  cocos2d::renderer::Texture2D* cobj = new (std::nothrow) cocos2d::renderer::Texture2D();
  auto obj = se::Object::createObjectWithClass(__jsb_cocos2d_renderer_Texture2D_class);
  obj->setPrivateData(cobj);

  cocos2d::renderer::Texture::Options options;
  options.bpp = imgInfo->bpp;
  options.width = imgInfo->width;
  options.height = imgInfo->height;

  options.glType = imgInfo->type;
  options.glFormat = imgInfo->glFormat;
  options.glInternalFormat = imgInfo->glInternalFormat;

  options.compressed = imgInfo->compressed;
  options.hasMipmap = false;
  options.premultiplyAlpha = imgInfo->hasPremultipliedAlpha;

  std::vector<cocos2d::renderer::Texture::Image> images;
  cocos2d::renderer::Texture::Image image;
  image.data = imgInfo->data;
  image.length = imgInfo->length;
  images.push_back(image);
  options.images = images;

  cobj->initWithOptions(options);

  retObj->setProperty("texture", se::Value(obj));
  retObj->setProperty("width", se::Value(imgInfo->width));
  retObj->setProperty("height", se::Value(imgInfo->height));
  seArgs.push_back(se::Value(retObj));

  imgInfo = nullptr;
}
...

JS 代码修改:

builtin/jsb-adapter/builtin/jsb-adapter/HTMLImageElement.js

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
set src(src) {
 this._src = src;
 jsb.loadImage(src, (info) => {

    if (!info) {

        this._data = null;

        return;

    } else if (info && info.errorMsg) {

        this._data = null;

        var event = new Event('error');

        this.dispatchEvent(event);

        return;

    }

    this.width = this.naturalWidth = info.width;

    this.height = this.naturalHeight = info.height;

   if (info.texture) {

        info.texture._ctor()

        this.texture = info.texture

    }

    else {

         ...

    }

    this.complete = true;

    var event = new Event('load');

    this.dispatchEvent(event);

});

}

engine/cocos2d/core/assets/CCTexture.js

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
_nativeAsset: {
    get () {
        // maybe returned to pool in webgl
        return this._image;
    },
    set (data) {
        if (data.texture) {
            this.initWithTexture(data.texture, data.width, data.height)
            return
        }
        ...
    }
}// 添加如下函数
initWithTexture (texture, pixelsWidth, pixelsHeight) {
    this._texture = texture
    this.width = pixelsWidth;
    this.height = pixelsHeight;

    // 通知原生端更新配置,如果没有修改texture属性的,代码基本跑不到。
    // _updateNative标志在当前对象序列化的时候记录如果配置中的信息和默认值不一致时为true
    if (this._updateNative) {
        var opts = _getSharedOptions();
        opts.minFilter = FilterIndex[this._minFilter];
        opts.magFilter = FilterIndex[this._magFilter];
        opts.wrapS = this._wrapS;
        opts.wrapT = this._wrapT;
        texture.update(opts, true) // 这里需要在原生端添加一个简易的更新函数。就拿原来的更新函数提出纹理数据就好了,这里就不贴了。
    }
    this.loaded = true;
    this.emit("load");
    return true;
},

优化前后,iphone6 测试的加载速度提升了 12%-15% 左右:

以上统计的是 Prefab 加载前后的数据,包含了异步加载纹理的时间,所以会有时间较长的情况,但是同步耗时的地方基本没了,并且在 iphone6 上已经感受不到明显的卡顿了。

四、附加题

spine 加载优化

由于 spine 的骨骼动画是在原生端单独加载的,所以在 js 加载的时候可以移除 spine 骨骼加载,减少一次 IO。

  • 修改文件如下:deserialize.js
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
function deserialize (json, options) {
    ...    
    // 不是原生端或者不是骨骼文件,spine原生端不加载骨骼文件
    asset._native && (asset.__nativeDepend__ = !CC_JSB || !(asset instanceof sp.SkeletonData));

    pool.put(tdInfo);
    return asset;
}

路径搜索(fullPathForFilename)

由于第一次路径填充的时候,需要从所有的路径里去查找。从小米5上测试发现每次路径检查需要消耗 2ms 左右。正常我们会有两个路径:一个更新路径,一个是当前包路径。所以小米5上一个文件检索至少要 4ms+。

  • 解决方案:

自己生成一个路径映射表。因为打包和更新的时候文件有哪些都是确定的。这样就可以使文件查找的速度降到 50μs 以下。


本文主要是想分享一个加载优化的思路和方向给大家,感兴趣的小伙伴可以点击文末【阅读原文】前往论坛专贴一起交流讨论:

https://forum.cocos.org/t/topic/134363

更多精彩

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2023-01-30,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Creator星球游戏开发社区 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
TO GOOD TO GO筹集3100万美元对抗食物浪费
TO GOOD TO GO,这家让你在食物浪费之前购买食物的初创公司正在筹集3110万美元的资金;blisce正在领导这轮融资,并投资1540万美元。现有投资者和员工也参与其中。虽然这家公司已经存在了一段时间,但这是第一次从风投公司融资。
用户8054111
2021/01/14
9110
美悬赏1500万美元寻勒索软件团伙Conti 的关键人物信息
据Bleeping Computer网站5月7日消息,为了能帮助识别和定位臭名昭著的勒索软件团伙 Conti 的主要核心人员及同谋,美国国务院开出了1500 万美元的高额赏金。
FB客服
2022/06/08
2340
美悬赏1500万美元寻勒索软件团伙Conti 的关键人物信息
物种功能,多样性分解及功能多样性
Ecol. Lett. | 普莱斯方程的生态学应用:解析群落组成变动对生态系统功能的影响
Listenlii-生物信息知识分享
2020/07/14
3.7K1
物种功能,多样性分解及功能多样性
【史上最全】计算机的编年史
前几天我写算力简史的时候,顺便整理了一份计算机技术的编年史(将近一万字)。今天发给大家,以供参考。
鲜枣课堂
2023/08/21
1.2K0
【史上最全】计算机的编年史
跨境电子商务税收合规平台Taxdoo筹集了2100万美元的A轮融资
Taxdoo是一家针对跨境电子商务公司构建了所谓的“财务合规自动化平台”的创业公司,已筹集了2100万美元的新资金。
甜甜圈
2020/12/18
8580
AI日报|跃问App上架加入AI助理竞争!GPTZero获千万美元A轮融资,创始人不到30岁!
AI日报|Luma推出AI视频模型,又一Sora级选手登场?SD3 Medium发布,图中文效果改善明显
可信AI进展
2024/06/17
3060
沃森和特朗普:一家伟大美国企业的兴与衰
---- 新智元编译 来源:cacm.acm.org 作者:Thomas Haigh 编译:Shawn、May 【新智元导读】Thomas Haigh是威斯康星大学密尔沃基分校历史系副教授,主要关注信息技术史以及商业史。最近,经常有关于美国之伟大、美国为何不再伟大以及如何让美国再次伟大的讨论,但是伟大这件事十分复杂,我们对伟大的定义已经随着时间发生了改变。在本文中,Thomas Haigh将从IBM的历史中寻找答案,探讨这家伟大的美国企业的兴与衰。 数十年以来,IBM一直都是世界上最大、最赚钱、最受
新智元
2018/05/29
7370
历史上最伟大的12位程序员
所谓程序员,是指那些能够创造、编写计算机程序的人。不论一个人是什么样的程序员,或多或少,他都在为我们这个社会贡献着什么东西。然而,有些程序员的贡献却超过了一个普通人一辈子能奉献的力量。这些程序员是先驱
程序员互动联盟
2018/03/13
1.9K0
历史上最伟大的12位程序员
最新!全球顶级对冲基金持仓公布,文艺复兴规模超1600亿美元
公众号根据全球各地对冲基金公司向美国证监会(SEC)提交了其13F报告。对其持仓做了全面的分析。所有数据都是根据最新编写。希望大家有所收获!
量化投资与机器学习微信公众号
2020/09/03
2.1K0
最新!全球顶级对冲基金持仓公布,文艺复兴规模超1600亿美元
本周(6.14-6.30)FF首轮融资终落地 | 投融资汇总
本周硬科技领域投融资事件一共51起,人工智能领域发生25起融资事件,占比49%;区块链领域发生13起融资事件,占比25%;物联网领域发生4起融资事件,占比8%;3R(VR/AR/MR)和生物医疗领域分别发生3起融资事件,分别占比6%;新能源领域发生2起融资事件,占比4%;光电芯片发生1起融资事件,占比2%。
镁客网
2018/07/31
6440
本周(6.14-6.30)FF首轮融资终落地 | 投融资汇总
Midjourney|文心一格prompt教程[技巧篇]:生成多样性、增加艺术风格、图片二次修改、渐进优化、权重、灯光设置等17个技巧等你来学
我认为学习图片类的 prompt,跟学习画画是类似的,最好的学习方法不是直接用模板。
汀丶人工智能
2023/05/19
1.1K0
Midjourney|文心一格prompt教程[技巧篇]:生成多样性、增加艺术风格、图片二次修改、渐进优化、权重、灯光设置等17个技巧等你来学
法拉第未来任命临时CFO,并宣布已收到5500万美元融资 |镁客网每周硬科技领域投融资汇总(10.22-10.28)
Faraday Future将从下个月起将所有员工的薪水削减25%,以保存现金,当前这家初创公司正在为推出首款电动汽车筹措新资金。
镁客网
2023/01/04
7320
法拉第未来任命临时CFO,并宣布已收到5500万美元融资 |镁客网每周硬科技领域投融资汇总(10.22-10.28)
世界算力简史(上)
1946年2月14日,在美国宾夕法尼亚州东南部的费城,人们正在像以往一样正常工作和生活。
鲜枣课堂
2023/08/21
4030
世界算力简史(上)
J. Chem. Inf. Model.|局部骨架多样性增强的分子生成模型用于开发NLRP3抑制剂
今天为大家介绍的是成都赜灵生物医药科技有限公司/四川大学华西医院生物治疗全国重点实验室陈俐娟教授团队在Journal of Chemical Information and Modeling上发表的文章Local Scaffold Diversity-Contributed Generator for Discovering Potential NLRP3 Inhibitors。成都赜灵生物医药科技有限公司成立于2019年,是一家专门从事创新药物研究及产业化的生物医药科技公司,构建了完整的新药研发体系,自主研发能力覆盖创新药物的靶点研究与机理验证、人工智能分子设计、临床前评价、CMC研究、临床方案设计与执行、新药注册等各个环节,打造了具有特色的创新药物发现平台。目前公司已获得5张临床试验批件,其中获美国FDA临床试验批件1张。成都赜灵建立了差异化并具有市场竞争力的丰富研发管线:1、治疗血液系统淋巴瘤化药Ⅰ类新药注射用甲磺酸普依司他;2、治疗骨髓纤维化Ⅰ类新药马来酸氟诺替尼;3、治疗自身免疫性疾病炎症性肠病(IBD)和类风湿关节炎(RA)的选择性JAK3小分子抑制剂药物ZL-82。公司核心品种甲磺酸普依司他和马来酸氟诺替尼在临床上已初步显示出良好的临床疗效,具有成为Best-in-Class和First-in-Class药物的潜力。
DrugAI
2024/01/25
2510
J. Chem. Inf. Model.|局部骨架多样性增强的分子生成模型用于开发NLRP3抑制剂
跨年AI应用指南:有人获30万美元年薪新工作,GPTs可能彻底改变人类社会
奈飞在1999年推出DVD邮件订阅服务后,用了三年半的时间才拥有了100万用户;Airbnb用了两年半的时间吸引了100万用户;Facebook用了10个月时间取得这样的成绩;音乐流媒体服务Spotify只用了5个月就达到了这个受众规模,而Instagram在2010年用不到3个月的时间吸引到100万用户。
小腾资讯君
2024/01/23
1620
跨年AI应用指南:有人获30万美元年薪新工作,GPTs可能彻底改变人类社会
算力简史(完整版)
引言:今天这篇文章,我将给大家详细介绍一下人类算力的演进过程。这是一段波澜壮阔的历史,值得我们驻足与回忆。
鲜枣课堂
2023/11/17
6110
算力简史(完整版)
2021年资产管理与托管银行行业发展研究报告
资产管理与托管银行是主要从事投资管理和与其相关的有偿托管和证券业务。包括经营共同基金和封闭式基金的公司以及单位信托公司。不包括那些主要从事商业贷款、投资银行业、经纪业和其它特殊金融业务的银行和金融机构。
资产信息网
2022/04/22
8420
2021年资产管理与托管银行行业发展研究报告
Docker中容器的随机命名方式
使用 docker 创建容器时,如果没有用 --name 指定,docker 会为用户选择一个名称, 格式是两个带有下划线的单词,如xxx_yyyy
fliter
2023/09/05
4910
Docker中容器的随机命名方式
宾大苏炜杰:从「匮乏」走向「丰富」
初识苏炜杰,他与许多青年学者一样,外表温文尔雅、略带腼腆,但一谈起学术和 AI 的未来发展,就充满激情。 看他的简历,成长经历一帆风顺:本科就读于富有盛誉的北京大学数学科学学院,接着在美国斯坦福大学攻读博士,师从美国国家科学院院士、麦克阿瑟天才奖得主 Emmanuel Candès 和美国国家科学院和工程院双院院士 Cynthia Dwork。紧接着跳过博士后阶段,执教于培养了埃隆·马斯克和沃伦·巴菲特的沃顿商学院,并联合指导常青藤名校宾夕法尼亚大学在机器学习领域的发展。 他的团队在机器学习优化算法、可信
AI科技评论
2022/03/29
4620
机器学习学术速递[9.6]
【1】 Computing Graph Descriptors on Edge Streams 标题:基于边缘流的图描述子计算 链接:https://arxiv.org/abs/2109.01494
公众号-arXiv每日学术速递
2021/09/16
1.1K0
推荐阅读
相关推荐
TO GOOD TO GO筹集3100万美元对抗食物浪费
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档