Cocos的启动大致可以分为5个阶段:Cocos引擎加载、Cocos引擎运行、Cocos静态资源加载、Cocos场景运行、Cocos动态资源加载;
Cocos引擎加载和运行是业务侧无法控制,那么黑屏时间优化就只剩下Cocos静态资源加载了。静态资源加载的手段有2个,资源压缩和资源缓存(硬盘、内存)。
资源压缩主要是针对图片资源的压缩,tinify支持 png 和 jpg 格式图片的在线压缩,一般可以压缩掉 75% 的大小,并且在视觉上不会有明显的差异,十分推荐。
若接受一定程度的失真,在 cocos creator 编辑器中也能够对 png 和 jpg 图片进行压缩。如果是 png 格式图片就 png,jpg 格式则选 jpg,选择后可以调整图片质量,图片质量越低,大小越小,失真也会越多。
资源缓存分为硬盘缓存和内存缓存。
对于原生端,资源本身是存在本地的。对于 Web 端,可以通过 http 的缓存,或者 PWA 来实现资源在硬盘的缓存。
资源还可以缓存在内存中,一般来说,游戏中会有多个场景,例如游戏中会有很多关卡,每个关卡一个场景。如果一个场景不会重复进入,那么场景资源可以不用缓存。如果场景需要重复进入,那么缓存一下,可以加速第二次打开的速度。
一般来说,硬盘的存储空间比较大,多做硬盘的存储问题不大。但是内存一般空间比较宝贵,任何资源都放在里面,容易造成内存占用率高,并且可能存在内存泄漏的风险,所以一般来说只缓存一些常驻的资源。
由于游戏中需要大量的计算和绘制,比较吃CPU,CPU的优化非常重要,如果CPU负载过高,会造成设备发热严重、帧率降低甚至卡退。CPU是负责解析执行指令的,那么CPU高负载的原因主要是需要执行的指令太多,尤其是一些耗时的指令。在游戏中,主要是绘制指令drawcall的调用。对于绘制指令的优化,理想情况是调用次数越少越好,优化绘制指令drawcall最有效的方式就是批处理了,即所谓的合图,就是将要渲染的纹理图合成一个大的图集,一次性渲染,可以减少载入content switch的时间,提升加载速度;
字体分为两种实现方式,一种是位图字体,一种是Free type字体。
所谓位图字体,就是将所有字符全部都打到一张图片中,这样做简单粗暴,效率也比较高,因为相当于字体都是预渲染好的。缺点是在字符集比较大时,例如所有汉字,那么字符的图片可能会比较大,内存占用率会比较高。并且不够灵活,因为图片的分辨率固定,在高分屏中,位图字体会出现一些锯齿。
另外一种是 Free type 字体,例如ttf格式的字体。不同于位图字体使用像素来表示字体,Free type 字体只是定义了字体的渲染数据,需要在运行时实时计算然后渲染。这样的字体就不存在放缩问题,但需要一定的计算消耗,所以一般需要通过缓存来优化。
对于只有数字和英文字母,并且文本结点比较多或者经常变化的情况,可以考虑使用位图字体进行优化,可以有效降低文字渲染造成的 drawcall 数。
结点的创建和销毁也是比较耗费性能的,所以要避免频繁的进行结点创建和销毁操作,并且应该尽量减少结点的数量。如果发现结点数量过多,并且结点频繁创建销毁,例如游戏中的小怪、子弹等数量比较多的重复物体,通常可以通过回收工厂进行优化。回收工厂就是结点用完之后,不销毁,而是缓存起来,下次获取结点可以直接复用缓存中的结点,而不需要重新创建。
游戏中的碰撞检测,也会比较耗性能。我们可以尽量使用box或者circle碰撞器,而少用多边形碰撞器。
游戏中比较占用资源的主要是资源的缓存,例如图片资源缓存。而资源分为静态资源和动态资源。静态资源指的是,场景一开始进入时便立即加载的资源。动态资源是指在场景中异步加载的资源,例如一些网络图片、音频等通过 cc.loader.load 或者 cc.loader.loadRes 加载的资源。
内存要降下来,也无非是两种方式,一是减少不必要的资源,二是资源压缩。
例如:场景中的背景图,在移动端中是一套,在 PC 端是一套。那么应该是通过代码判断是什么平台,然后再动态加载对应资源的方式实现,而不是在场景中同时放置移动端和 PC 端的背景,然后控制显隐的方式实现。这样可以减少一套资源的内存占用。
对于背景,一般来说由设计直接给图会比较大,如果是只是纯色或者通过简单的背景重复或者变换可以实现,可以由开发来实现,这样可以把大背景图优化掉。
另外,合图的时候我们注意只将比较相关的图片进行合图,否则意味着可能加载一整张合图,只是用到其中的一个小图,会造成很多内存空间的浪费。
资源压缩,主要是指对图片资源的压缩,也称纹理压缩。
单纯使用 tinify 等工具,对图片大小进行压缩,如果不改变图片尺寸,是不会减少图片资源在内存中的体积的,只能减小图片在磁盘中的存储体积。对于分辨率要求不高的资源,可以使用2倍图或者1倍图,可以
纹理压缩算法,例如 Etc1, Etc2, PVRTC 等,可以优化图片在内存中的体积。jpg 和 png 格式虽然能够对图片数据进行压缩,但是并不能被gpu读取,所以是需要 CPU 解码之后再给到 GPU 渲染的。而经过纹理压缩算法压缩后的数据,是能够直接给gpu渲染的,所以纹理压缩不仅能够优化内存,还能优化 CPU。
需要注意的是,纹理压缩一般都是有损压缩,可以选择压缩率。另外,纹理压缩的算法依赖于设备的 GPU 能否解码,所以针对不同的平台,需要使用不同的纹理压缩算法。
对于不用的内存,我们也要及时释放,防止内存泄漏。分自动释放和手动释放两种。对于静态资源的释放,可以通过勾选场景自动释放选项来实现;
这样在场景切换后,场景中的静态资源就会被自动释放了。
如果不想等到切换场景才释放静态资源,也可以使用 cc.assetManager.releaseAsset 进行手动释放。
有一个需要注意的点是动态加载的资源无法在场景切换时,跟随静态资源自动释放。需要通过 cc.setAutoReleaseRecursively 手动设置一下:
这样资源在场景切换时,会自动释放这部分动态加载的资源。也可以通过 cc.loader.releaseRes 手动释放动态加载资源。