cocos黑暗斩_动态合图你真的用好了吗?| Cocos Creator

支持动态合图的渲染组件: Sprite、 Label( BITMAP 模式)。

注意事项:

在场景加载前,动态合图系统会进行重置, SpriteFrame贴图的引用和 uv 都会恢复到初始值。

查看方法:

通过以下代码可以在游戏中看到所有动态合图会被加到一个 scrollview上,便于实时查看合图效果。

cc.dynamicAtlasManager.showDebug( true);

其它规则(文档里未直接说明的):

动态合图最大张数为5张,使用完后会强制重建。

单张合图的大小为 2048*2048。

贴图的多个属性设置为非默认值会影响合批(FilterMode,genMipmaps,premultiplyAlpha,flipY,wrapS,wrapT,pixelFormat等)

如果参与合图的两个渲染组件 A 和 B 被另一个未合图的渲染组件 C 隔开,那么这两个 A 和 B 并不会在一个 Dr awCall 里渲染。

如果一直不切换场景,那么随着动态合图的数量增长,渲染效率可能会降低,适得其反。

这里使用官方文档中给到一个实例 暗黑斩来测试动态合图的效果。

暗黑斩: https://github.com/cocos-creator/tutorial-dark-slash

注意事项:

由于该项目中把所有贴图的 FilterMode设置为 Point模式了,因此首先我们把这个全部改为 Bilinear。

首先关闭动态合图,查看运行两个场景时的 Drawcall数量分别是:

StartGame:37

PlayGame:38

然后打开动态合图,我们在两个场景下分别查看合图效果如下:

9447ea25df1640668f66d9521af09d25.png

(StartGame)

29b871727b5d938faf20cadf75564e8d.png

(PlayGame)

DrawCall数量分别如下:

StartGame:9

PlayGame:25

可以看到在不同的场景下,参与合图的纹理都被合到一张大图上了, DrawCall确实提高了很多。

3. 增加需求

上面这个示例只是一个非常简单的 DEMO,实际游戏中的使用需求远比上面这个复杂,主要有以下几个实际问题是我们要去考虑的:

游戏中只有一个场景,也就是说动态合图不会被重建。

已经合图的资源被释放后,下次图集上的纹理还能否使用?

如果在重建前出现多图集的情况下会有哪些影响?

那么我们分别来尝试满足以上条件时,动态合图还能否保持高效率。

3.1 保持动态合图不被重建

由于 DEMO 中有两个场景,为了不改动游戏逻辑,我们直接改引擎,在切换场景时直接不要重建动态合图。代码如下:

// manager.js

functionbeforeSceneLoad{

//dynamicAtlasManager.reset;

}

然后测试此时的动态合图效果:

73f012ed48b7ec5b2cd8fe86d5f1d05f.png

如上图所示:

所有符合合图条件的纹理都合并到一张图集上, DrawCall依次为 9和 25。

3.2 增加资源释放

在 DEMO 中切换场景时,增加资源释放代码:

// HomeUI.js

playGame: function{

cc.eventManager.pauseTarget(this.btnGroup, true);

letself = this

cc.director.preloadScene( 'PlayGame', null, function(error, newScene){

res.cleanRes(self.node, newScene)

cc.director.loadScene( 'PlayGame')

})

// cc.director.loadScene( 'PlayGame', function(error, newScene){

// res.cleanRes(self.node, newScene)

// });

}

// Game.js: 战斗结束时,点取消按钮回到HomeUI场景

gameOver: function{

this.deathUI.hide;

this.gameOverUI.show;

letself = this

// cc.director.loadScene( 'StartGame', function(error, sceneAsset){

// res.cleanRes(self.node, sceneAsset)

// });

cc.director.preloadScene( 'StartGame', null, function(error, newScene){

res.cleanRes(self.node, newScene)

cc.director.loadScene( 'StartGame')

})

},

// 新增加文件res.js

letres = {

cleanRes: function( oldScene, newScene ) {

letscene = cc.director.getRunningScene

letoldDeps = scene.dependAssets

letnewDeps = cc.loader.getDependsRecursively(newScene)

letlength = oldDeps.length

for( letindex = length - 1; index >= 0; --index) {

letdep = oldDeps[index]

if(newDeps.indexOf(dep) >= 0) {

oldDeps.splice(index, 1)

}

}

cc.loader.release(oldDeps)

},

}

module.exports = res;

运行游戏,执行 StartGame->PlayGame->StartGame->PlayGame->StartGame的这个流程后,再看动态合图的效果如下:

4d2e9c6fd28d7e0fa9cf63827ca46d57.png

注意:这时候,动态合图里面出现了两张动态图集:

第一张被使用完全,第二张使用到了一部分。

此时的 Drawcall相比没有释放资源时,有时会增加了 1。分别是 10和 26。

很明显,此时 Drawcall增加的原因是:部分资源在第一个图集上,部分资源在第二个图集上,相比只有一张图集时, DrawCall就增加了 1个。

3.3 出现多张图集时的影响

正如 3.2 所示,出现多张图集时,主要有以下影响:

图集数量的增加消耗了内存。

图集数量的增加导致 Dr awCall 可能会升高,图集数量越多,影响越大。

综合以上 DEMO 中的尝试,我们可以得出结论:

如果要在实际项目中从效率和性能兼顾的方向来使用动态合图,显然当前的这个机制是不符合要求的,那么主要解决哪些问题?我认为有以下两个:

只要原始贴图参与过合图,不论后来是否被释放,下次能直接使用合图中已经有的纹理来渲染,而不必再占用新的图集空间。

控制总的动态图集数量,最好不能超过3张,最好在2张(含)以内。

为了能够重复利用合图的空间,我们需要明白为什么同一个资源被释放后,它在合图空间中的纹理不能再次被使用了。我们通过阅读源码可以找到答案:

// atlas.js

insertSpriteFrame (spriteFrame) {

letrect = spriteFrame._rect,

texture = spriteFrame._texture,

// 合图记录是通过纹理的_id值来查找的。

info = this._innerTextureInfos[texture._id];

......

}

// CCTexture2D.js

ctor{

// 生成id的方法

this._id = idGenerater.getNewId;

......

}

// id-generate.js

functionIdGenerater (category) {

// init with a random id to emphasize that the returns id should not be stored inpersistence data

this.id = 0 | (Math.random * 998);

this.prefix = category ? (category + NonUuidMark) : '';

}

IdGenerater.prototype.getNewId = function{

returnthis.prefix + (++this.id);

};

通过以上代码,我们可以知晓,所有的纹理加载时生成的 _id都是唯一的且其中的数字部分是自增的。因此即使是同一个资源,释放之后再加载到内存时,它的 _id与之前不一样。因此参与合图时即使已经合过,也找不到记录了。

既然找到了症结所在,那么解决办法也很简单,把 _id改成一个能与当前贴图唯一对应的标识即可,显然,这个标识就是 texture._uuid(这里不做解释,阅读引擎源码能找到答案)。

把 insertSpriteFrame方法中 texture._id改为 texture._uuid后,测试执行 StartGame->PlayGame->StartGame->PlayGame->StartGame的这个流程后,动态合图效果如下:

e76da2d45e54efc90bb791a974f9c38e.png

drawcall依然保持是 9和 25。

4.2 提高图集利用率

从图5可以看到,虽然所有纹理都集中合到一张图集上,但是这个图集里面有太多的空白区域,不会被利用到。如果能利用到一整张图集的所有空间,那么可以提高单张图集上同时合并的纹理数量,间接也降低了 DrawCall。

我们可以想到,如果能按照 TexturePacker的合图方式来做动态图集的合图,那么无疑是比较理想的。当然这是可以的,我们可以自己写一个算法来计算合图时的位置和空间。也可以找第三方的算法。这里我推荐网上一个 MaxRect的开源代码(c++版本,直接翻译成js版本即可)。代码我就不贴了,直接看使用这个算法的结果:

9ec7ba2fbf0842e321145340e7dc6291.png

4.3 控制图集数量

客观来说,一个游戏用几张动态图集是不确定的。但是可以确定的是,我们需要哪些贴图被合并进动态图集。这要从我们的目的来着手。动态图集解决的是降低 DrawCall的目的。

因此以下情况是考虑是否要让贴图参与动态合图的重要因素:

贴图 size 较大,接近 512*512 或者你设置的最大 size。

贴图符合合图要求,但使用频次较低。

贴图是否会打断需要批量渲染的组件。

贴图参与合图后能否显著提高当前界面 Dr awCall 。

相反,我们真正需要动态合图的需求是:

复用结构中的小尺寸贴图。

高频次使用的小尺寸贴图。

常用列表上的公共背景贴图。

高频次使用的 TTF 的小图集。

最后,一定不要参与合图的是:

BITMAP 模式的低频次 Label :纯粹浪费合图空间。

单一使用的背景框。

非主角相关的 icon 贴图。

其它低频次的贴图。

通过以上的一些限制条件来控制游戏中参与合图的贴图后,如果能把图集控制在3张以内。那么 DrawCall可以在稳定的较高的水平。同时,渲染过程中合图的工作可以省掉大部分。

以上是由 Cocos 开发者 乐府-堂主分享的优质技术教程, 欢迎分享本文给身边的朋友,也欢迎您在评论区或进入作者原文一起交流!

乐府系列更多分享:

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值