1.音效内存泄露的问题
应该是3.x 好几个版本都有,因为我们项目开始选的3.x,一直有类似的问题
3.6.1的时候做了比较多的内存分析,最后定位问题是:
audioSource.playOneShot(…)时,内部有一个最大同时播放的音效数量。目前小游戏上看值定义为10.如果同时播放的音效数量超过这个,cocos 就会停掉一个。然后播放新的音效。问题就出在这里,cocos在停掉的音效的时候,是非自然停止,所有不会走end 回调,而是走的 stop 回调。但是销毁音效是在 end 回调中,这样一来,这个音效就一直没有销毁,在音效频繁播放的时候,就会明显导致内存增长。
AudioSource.ts:381行
public playOneShot (clip: AudioClip, volumeScale = 1) {
if (!clip._nativeAsset) {
console.error('Invalid audio clip');
return;
}
AudioPlayer.loadOneShotAudio(clip._nativeAsset.url, this._volume * volumeScale, {
audioLoadMode: clip.loadMode,
}).then((oneShotAudio) => {
audioManager.discardOnePlayingIfNeeded();
oneShotAudio.onPlay = () => {
audioManager.addPlaying(oneShotAudio);
};
oneShotAudio.onEnd = () => {
audioManager.removePlaying(oneShotAudio);
};
oneShotAudio.play();
}).catch((e) => {});
}
这个问题在浏览器测试的时候基本没发现,但是微信小游戏实现就很明显。
最终我们的临时解决方案是在OneShotAudioMinigame的构造函数中添加一段代码解决。
private constructor (nativeAudio: InnerAudioContext, volume: number) {
this._innerAudioContext = nativeAudio;
nativeAudio.volume = volume;
nativeAudio.onPlay(() => {
this._onPlayCb?.();
});
nativeAudio.onEnded(() => {
this._onEndCb?.();
nativeAudio.destroy();
// @ts-expect-error Type 'null' is not assignable to type 'InnerAudioContext'.
this._innerAudioContext = null;
});
//KB start
nativeAudio.onStop(()=>{
nativeAudio.destroy();
// @ts-expect-error Type 'null' is not assignable to type 'InnerAudioContext'.
this._innerAudioContext = null;
})
//KB end
}
上面的 KB 块验证后确实修复了泄露问题。本来不想自定义引擎的,但是因为这些类都是未导出的类,没法写 Polyfill 打补丁。。
2.2D合批极端情况下可能导致卡死
这个情况,错误是:offset is out of bounds。是合批的 MeshBuffer的索引数组长度不够了。其实这个我看论坛很多人说,可以改StaticVBAccessor.IB_SCALE增大索引和定点的比例绕开问题。但是呢,如果项目中使用 spine 比较多的话,其实定点和索引的比例是不太确定的,可能还是会出问题。尤其是渲染 spine 的数量不少,且经常切换渲染内容的情况。最后发现了一个地方的逻辑可能没有考虑索引长度,然后导致了这个错误。
看代码:
static-vb-accessors.ts:160
// Loop buffers
for (let i = 0; i < this._buffers.length; ++i) {
buf = this._buffers[i];
freeList = this._freeLists[i];
// Loop entries
for (let e = 0; e < freeList.length; ++e) {
// Found suitable free entry
if (freeList[e].length >= byteLength) {
entry = freeList[e];
bid = i;
eid = e;
break;
}
}
if (entry) break;
}
问题在
if (freeList[e].length >= byteLength) {
这里只判断了IFreeEntry的顶点数据字段长度,是否还有一种可能,顶点字段长度是够的,但是索引已经不够了?
3.Spine 组件复用,只替换显示的动画数据时,内存一直增加无法释放的问题,直到释放这个 spine 组件。
这个我们做过测试,几十个 spine 资源循环在同一个 spine 组件上替换展示,内存一直增加。
从最初的100m,可以到几个 G 的占用。
所以我们目前项目在 spine 渲染这块,只能每一次都新建一个 spine 组件避免这个问题。
4.复用AudioSource音乐播放时,停掉之前的音乐,然后播放一个新的,大概率会出现2个音乐都在播放
这个问题我们主要是在3.5中发现,解决方案是,每次都用不同的 AudioSouce。。。3.6更新后,没有特意再去试有没有问题(反正我们自己的机制已经改成不复用 AudioSource 播放背景音乐了。)
最后还有一些建议,渲染健壮性这块个人觉得,cocos还可以提升一下。
感受比较明显一点是和asset 释放的配合不是很友好。在手动管理内存的场景,如果资源被释放掉了,会导致 cocos 整个渲染都出问题,因为渲染的时候因为资源取不到。渲染直接中断了,画面就卡住了,这样的结果也不说好坏,但是确实很不利于定位问题,因为没法直观知道那个资源被释放掉了导致错误,只能找到报错的地方,在合适的时机启用调试断点,然后看调用上下文,找到最终对应的节点,才能确认是哪个资源。建议可以考虑直接不渲染错误资源,这样的话,起码其他逻辑还可以继续跑,并且,也更方便知道哪一个资源意外被释放了。