在前面的章节中,我们介绍了图片常用的加载方式和相关内存优化。这一节中主要介绍下cocos2dx的缓存。
首先致敬大神
http://www.cocoachina.com/bbs/read.php?tid-200714.html (纹理缓存TextureCache)
http://www.cocoachina.com/bbs/read.php?tid-200359.html (精灵帧缓存SpriteFrameCache)
http://www.cocoachina.com/bbs/read.php?tid-201628.html (动画缓存AnimationCache)
一、为什么使用缓存
在前面提到,在游戏中需要加载大量的纹理图片,这些操作都是很耗内存和资源的, 当游戏中有个界面用到的图片非常多,第一次点进这界面时速度非常慢(因为要加载绘制很多图片)出现卡顿的现象,第二次进入这个界面就非常快了,这是为什么呢?
下面就带着这个问题来一起看一下吧
首先,我们通常使用如下方式创建一个精灵Sprite
auto sp = Sprite::create("sprite.png");
sp->setPosition(Vec2::ZERO);
this->addchild(sp);
没错,又是这个(出现了好多次)
我们可以通过源代码了解到,我们创建精灵后,将精灵加入了缓存
Sprite* Sprite::create(const std::string& filename)
{
Sprite *sprite = new (std::nothrow) Sprite();
if (sprite && sprite->initWithFile(filename))
{
sprite->autorelease();
return sprite;
}
CC_SAFE_DELETE(sprite);
return nullptr;
}
我们可以看到,在创建的时候调用了
sprite->initWithFile(filename))
那么下面来看一下 initWithFile 这个方法
bool Sprite::initWithFile(const std::string& filename)
{
if (filename.empty())
{
CCLOG("Call Sprite::initWithFile with blank resource filename.");
return false;
}
_fileName = filename;
_fileType = 0;
Texture2D *texture = Director::getInstance()->getTextureCache()->addImage(filename);
if (texture)
{
Rect rect = Rect::ZERO;
rect.size = texture->getContentSize();
return initWithTexture(texture, rect);
}
return false;
}
注意这一行,获取了一个缓存加载了该图并返回一个纹理
Texture2D *texture = Director::getInstance()->getTextureCache()->addImage(filename);
Texture2D: 纹理,即图片加载入内存后供CPU和GPU操作的贴图对象。
TextureCache(纹理缓存),用于加载和管理纹理。一旦纹理加载完成,下次使用时可使用它返回之前加载的纹理,从而减少对GPU和CPU内存的占用。
现在大家是不是对第二次加载速度变快有所了解了呢。
二、三种缓存
Cocos引擎主要有三种缓存类:纹理缓存:TextureCache,帧缓存:SpriteFrameCache,动画缓存:AnimationCache
这3个缓存类分别维护各自的map容器对象,对于容器,下一张将会单独介绍哦,现在先简单看一下:
TextureCache
std::unordered_map<std::string, Texture2D*> _textures;
SpriteFrameCache
Map<std::string, SpriteFrame*> _spriteFrames;ValueMap _spriteFramesAliases;std::set<std::string>* _loadedFileNames;
AnimationCache
Map<std::string, Animation*> _animations;
1、纹理缓存:TextureCache
上面大家已经初步了解了Sprite加载,如果文件名以前没有被加载时,它会创建一个新的Texture2D 对象,它会返回它。它将使用文件名作为key否则,它会返回一个引用先前加载的图像。
TextureCache屏蔽了加载纹理的许多细节;
addImage函数会返回一个纹理Texture2D的引用,可能是新加载到内存的,也可能是之前已经存在的;
Texture2D *texture = Director::getInstance()->getTextureCache()->addImage(filename);
也可以通过getTextureForKey方法来获得这个key所对应的纹理缓存,如果这个Key对应的纹理不存在,那么就返回NULL
Texture2D *texture = Director::getInstance()->getTextureCache()->getTextureForKey(textureKeyName);
TextureCache类还支持异步加载资源的功能,利用addImageAsync方法。
可以在进度条或者跳转界面预先将资源加载好哦,那么在后面需要使用的时候就不会卡顿啦。
有加载当然有释放,removeUnusedTextures则会释放当前所有引用计数为1的纹理,即目前没有被使用的纹理。
Director::getInstance()->getTextureCache()->removeUnusedTextures();
指定释放
Director::getInstance()->getTextureCache()->removeTextureForKey("sp.png");
全部释放
Director::getInstance()->getTextureCache()->removeAllTextures();
2、帧缓存:SpriteFrameCache
SpriteFrameCache 主要服务于多张碎图合并出来的纹理图片。这种纹理在一张大图中包含了多张小图,直接通过TextureCache引用会有诸多不便,因而衍生出来精灵框帧的处理方式,即把截取好的纹理信息保存在一个精灵框帧内,精灵通过切换不同的框帧来显示出不同的图案。
SpriteFrameCache的常用接口和TextureCache类似,唯一需要注意的是添加精灵帧的配套文件一个plist文件和一张大的纹理图。TexturePacker工具是合成大图的良好选择哈。
使用方法
SpriteFrameCache *frameCache = SpriteFrameCache::getInstance();
frameCache->addSpriteFramesWithFile("boy.plist","boy.png");
auto frame_sp = Sprite::createWithSpriteFrameName("boy1.png");
this->addChild(frame_sp,2);
3、动画缓存:AnimationCache
Cocos2d-x中,动画的具体内容是依靠精灵显示出来的,为了显示动态图片,我们需要不停切换精灵显示的内容,通过把静态的精灵变为动画播放器从而实现动画效果。动画由帧组成,每一帧都是一个纹理,我们可以使用一个纹理序列来创建动画。
我们使用Animation类描述一个动画,而精灵显示动画的动作则是一个Animate对象。动画动作Animate是精灵显示动画的动作,它由一个动画对象创建,并由精灵执行。
有两种创建方法:
(1)手动添加序列帧到Animation类
手动添加的方法需要将每一帧要显示的精灵有序添加到Animation类中,并设置每帧的播放时间,让动画能够匀速播放。另外,还要通过setRestoreOriginalFrame
来设置是否在动画播放结束后恢复到第一帧。创建好Animation实例后,需要创建一个Animate实例来播放序列帧动画。
auto animation = Animation::create();
for( int i=1;i<15;i++) {
char szName[100] = {0};
sprintf(szName, "sp_%02d.png", i);
animation ->addSpriteFrameWithFile(szName);
}
animation->setDelayPerUnit(2.8f / 14.0f);
animation->setRestoreOriginalFrame(true);
auto action = Animate::create(animation);
_animation->runAction(Sequence::create(action, action->reverse(), NULL));
(2)使用文件初始化Animation类
AnimationCache可以加载xml/plist文件,plist文件里保存了组成动画的相关信息,通过该类获取到plist文件里的动画。
使用文件添加的方法只需将创建好的plist文件添加到动画缓存里面,plist文件里包含了序列帧的相关信息。再用动画缓存初始化Animation实例,用Animate实例来播放序列帧动画。
auto cache = AnimationCache::getInstance();
cache->addAnimationsWithFile("animations/animations.plist");
auto animation2 = cache->getAnimation("dance_1");
auto action2 = Animate::create(animation2);
_animation->runAction(Sequence::create(action2, action2->reverse(), NULL));