引言:如何进行 TiledMap 地图优化?开发者 Bool Chen 将分享一套行之有效的 TiledMap 地图优化方案,其中包括了渲染、解析、寻路方面。
当项目里的地图越来越庞大和复杂,一些性能上的问题也开始逐渐出现。本文将从裁剪区域共享、Sprite 颜色数据去除、多图集渲染合批和分帧寻路四个方面,分享关于 TiledMap 地图的优化以及实现。
测试用例
本次的测试用例是这样的一张地图,有6个图层,其中4个图块层、2个物件层。测试的数据来源是在浏览器环境下,利用 console.time
和 timeEnd
函数,打印对应的逻辑耗时或渲染耗时,需要注意的是每次运行的耗时并不是一致的,但是在取均值后,可以认为是相对可靠的。
优化前后(注:横轴是游戏运行的帧数,纵轴是在该帧数下,对应的耗时,单位是毫秒)
上图是我们最后将裁剪区域共享+Sprite 颜色数据去除+多图集渲染合批一起使用后的优化效果,测试显示渲染耗时大约降低了20%左右。其实这张地图并不算复杂,如果物件数量、图层数量增加的话,优化效果会更加明显。
本次的主要优化方案参考自大城小胖的《如何重绘<江南百景图>》,文章介绍了很多性能优化技巧,强烈推荐大家去看看。项目基于 Cocos Cocos Creator 2.4.3,不过大部分优化思路在 v3.x 依旧适用。限于篇幅,本文仅呈现部分核心代码,完整代码及测试项目源码下载见文末。
裁剪区域共享
玩家操控人物在地图上移动的时候,地图显示的内容也需要跟随人物的位置发生改变。此时,为了优化性能,引擎会计算屏幕的可视范围,只有在可视范围内的图块才会被渲染。
研究引擎中 TiledMap 地图的渲染流程后我们发现,其实 TiledMap 本身并不是渲染组件,地图的渲染是通过图层 TiledLayer
实现的,其对应的渲染器是 TmxAssembler
。渲染时,渲染流会逐个调用 TmxAssembler
的 fillBuffers
函数进行渲染数据填充,此函数中会调用 CCTiledLayer
的 _updateCulling
函数进行可视范围,只有可视范围发生改变才会进行渲染。
但是,在计算的时候,由于每个图层都有对应的 Assembler
,所以每个图层都会单独计算一次。而一般情况下我们每个图层显示的范围是一致的,所以我们希望它只计算一次就好了。
接下来我们就来实现裁剪区域共享(Share Culling),让不同 TiledLayer
间,共享可视区域的裁剪计算结果,以此节约性能。
实现过程
首先我们继承 TiledMap,重写创建图层的 _buildLayerAndGroup
函数,实现创建自定义的 ShareCullingTiledLayer
。
因为相对来说记录第一个图层实现起来更方便,所以我们缓存第一个图层,并将首个 TieldLayer
传递给后面的图层,方便后面去读取计算结果。
_buildLayerAndGroup() {
for (let i = 0, len = layerInfos.length; i < len; i++) {
if (layerInfo instanceof cc.TMXLayerInfo) {
// 创建自定义的ShareCullingTiledLayer
let layer = child.getComponent(ShareCullingTiledLayer);
// 传递、记录首个TiledLayer
layer._init(layerInfo, firstLayer);
firstLayer = firstLayer || layer;
}
}
}
接着修改 TiledLayer
的裁剪函数,一样通过重写的方式实现。
这里我们进行判断,如果是首个图层,我们才让他进行计算,并把结果缓存起来;如果不是首个图层,我们就直接读取首个图层的计算结果。
最后重写 TiledLayer
的裁剪函数,实现复用裁剪区域的功能。
_updateCulling() {
// this._firstLayer为空时 表示为首个layer
let firstLayer = this._firstLayer;
if (!firstLayer) {
// 进行裁剪区域计算
this._updateViewPort();
this._cacheCullingDirty = this._cullingDirty;
} else {
// 直接复用firstLayer的结果
this._cullingRect = firstLayer._cullingRect;
this._cullingDirty = firstLayer._cacheCullingDirty;
return;
}
}
很简单地我们就完成了这个优化。Share Culling 实现起来并不麻烦,但效果是显著的。
优化效果
优化前后