最近手头上一个cocos H5游戏,需求是根据后端传来的数据进行模拟角色打斗。角色不多,但是有很多全屏的技能动画,关键这些人物动作和技能全部是用帧动画来实现的。全部动作加起来应该有近两千的精灵帧吧。如果全部加载的的话,内存肯定会爆掉导致浏览器崩溃。
首先能想到的肯定是动态加载,即主场景只加载主场景的资源。这样试过之后,在浏览器上仍然是在loading的时候就崩溃,起初我以为是调用cc.loader.load文件数量过多导致的,后来做了一个小实验,用批处理生成了几百个文件,同时加载没问题。就否定了文件数量的问题,进一步确定是内存不足导致的崩溃。
其实在Windows上chrome的任务管理器中,内存占用才几百兆,应该不足以使iPhone崩溃。在再Mac上调试是,发现占用内存达到1.5G,惊了我个呆。(这里windows和Mac的内存占用区别这里不做解释,有兴趣的可以自己下去研究一下)
前面说的一个解决方案时动态加载,采取的具体方案是:先只加载主场景所用到的文件与资源。
但这里会产生一个问题,由于是在H5里调用的cc.loader.load()方法,里面其实包括的是下载和预处理资源。也就是说战斗场景的资源此时还没有下载,等到去打斗的时候再去下载?打斗再下载会存在一定时间的等待,是否不合常理?
通常我们玩的游戏里应该是一进来就下载所有的文件资源,只是首先会预加载一部分资源,其余的会在需要用到的时候再预加载。应该预加载的时间相对下载时间来说是相当快的,不需要过多时间去等待。
问题又来了,前面说过cc.loader.load()方法里面包含了下载和预处理,所以用引擎提供的方法已经不能满足我们的需求,怎么办?毫不犹豫的改啊!先看下源码,这里主要看texture的加载,看它到底做了什么!
cc._imgLoader = {
load : function(realUrl, url, res, cb){
var callback;
if (cc.loader.isLoading(realUrl)) {
callback = cb;
}
else {
callback = function(err, img){
if(err)
return cb(err);
cc.loader.cache[url] = img;
cc.textureCache.handleLoadedTexture(url);
cb(null, img);
};
}
cc.loader.loadImg(realUrl, callback);
}
};
cc.loader.cache[url] = img;
cc.textureCache.handleLoadedTexture(url);
着重看这两句,这是下载img文件之后的回调,第一句cc.loader.cache[url] = img,放入到缓存没问题。
但是handleLoadedTexture,字面意思就是处理已下载的Texture,继续看源码。
var _p = cc.textureCache;
_p.handleLoadedTexture = function (url) {
var locTexs = this._textures;
//remove judge
var tex = locTexs[url];
if (!tex) {
tex = locTexs[url] = new cc.Texture2D();
tex.url = url;
}
tex.handleLoadedTexture();
};
这里做的事显而易见,new 了一个Texture2D,存在了textureCache中,然后对改Texture2D对象调用了handleLoadedTexture()函数,如下:
handleLoadedTexture: function (premultiplied) {
var self = this;
premultiplied =
(premultiplied !== undefined)
? premultiplied
: self._hasPremultipliedAlpha;
// Not sure about this ! Some texture need to be updated even after loaded
if (!cc.game._rendererInitialized)
return;
if (!self._htmlElementObj) {
var img = cc.loader.getRes(self.url);
if (!img) return;
self.initWithElement(img);
}
if (!self._htmlElementObj.width || !self._htmlElementObj.height)
return;
//upload image to buffer
var gl = cc._renderContext;
cc.glBindTexture2D(self);
gl.pixelStorei(gl.UNPACK_ALIGNMENT, 4);
if(premultiplied)
gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, 1);
// Specify OpenGL texture image
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, self._htmlElementObj);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
self.shaderProgram = cc.shaderCache.programForKey(cc.SHADER_POSITION_TEXTURE);
cc.glBindTexture2D(null);
if(premultiplied)
gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, 0);
var pixelsWide = self._htmlElementObj.width;
var pixelsHigh = self._htmlElementObj.height;
self._pixelsWide = self._contentSize.width = pixelsWide;
self._pixelsHigh = self._contentSize.height = pixelsHigh;
self._pixelFormat = cc.Texture2D.PIXEL_FORMAT_RGBA8888;
self.maxS = 1;
self.maxT = 1;
self._hasPremultipliedAlpha = premultiplied;
self._hasMipmaps = false;
//dispatch load event to listener.
self.dispatchEvent("load");
},
这里将webGL讲纹理绘制了出来以便直接使用,而程序里面纹理是最占内存的。
综上,调用cc.loader.load除了下载之外,还会将.png/jpg等格式的纹理绘制出来。所以load资源过多时,会由于纹理绘制导致内存急剧飙高,然后导致崩溃。
知道了这些问题,然后做之前的需求就好做了,改一下引擎的代码,cc._imgLoader里面只下载文件,不做handleLoadedTexture预处理。这个预绘制我们自己手动调用,需要的时候自己来调用,即可做到分批使用和分批绘制。