Cocos2d-x 3.2中的三種緩存類

來源網址:http://shahdza.blog.51cto.com/2410787/1611766

【前言】

Cocos引擎主要有三種緩存類:

> 紋理緩存:TextureCache

> 精靈幀緩存:SpriteFrameCache

> 動畫緩存:AnimationCache

緩存的目的就是:先將所需資源(如紋理圖片)加載到內存中,之後再次使用該資源的時候,就可以直接從內存中取出,而不需要重新加載。從而減少了CPU和GPU的內存佔用。

本文對參考文獻的內容進行了整理與整合,並加入一些自己的觀點。


【TextureCache】

1、概述

在遊戲中需要加載大量的紋理圖片,這些操作都是很耗內存和資源的。

當遊戲中有個界面用到的圖片非常多,第一次點進這界面時速度非常慢(因爲要加載繪製很多圖片)出現卡頓,我們可以使用TextureCache提前異步加載紋理,等加載結束,進入到這個界面再使用這些圖片速度就會非常快。

> Texture2D(紋理):即圖片加載入內存後供CPU和GPU操作的貼圖對象。

> TextureCache(紋理緩存):用於加載和管理紋理。一旦紋理加載完成,下次使用時可使用它返回之前加載的紋理,從而減少對GPU和CPU內存的佔用。


當你創建一個精靈Sprite,你一般會使用Sprite::create(fileName)。假如你去看Sprite::create(fileName)的實現方式,你將看到它將這個圖片增加到紋理緩存中去了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//
    Sprite* Sprite::create(const std::string& filename)
    {
        Sprite *sprite = new Sprite();
        if (sprite && sprite->initWithFile(filename))
        {
            sprite->autorelease();
            return sprite;
        }
        _SAFE_DELETE(sprite);
        return nullptr;
    }
    bool Sprite::initWithFile(const std::string& filename)
    {
        ASSERT(filename.size()>0, "Invalid filename for sprite");
        // 加載filename的紋理圖片
        Texture2D *texture = Director::getInstance()->getTextureCache()->addImage(filename);
        if (texture)
        {
            Rect rect = Rect::ZERO;
            rect.size = texture->getContentSize();
            return initWithTexture(texture, rect);
        }
        return false;
    }
//


2、獲取TextureCache

在3.x版本中,TextureCache不再作爲單例模式使用。而是作爲Director的成員變量,通過以下方式獲取。

1
2
3
4
//
// 獲取紋理緩存類 TextureCache
Director::getInstance()->getTextureCache();
//


3、紋理的加載與獲取

如果文件名以前沒有被加載時,它會創建一個新的 Texture2D 對象,它會返回它。它將使用文件名作爲key否則,它會返回一個引用先前加載的圖像。

> addImage:函數會返回一個紋理Texture2D的引用,可能是新加載到內存的,也可能是之前已經存在的。

> getTextureForKey :獲得這個key所對應的紋理緩存,若這個Key對應的紋理不存在,那麼就返回nullptr。

> 支持的圖片格式有: .png,.bmp,.tiff, .jpeg, .pvr 。

1
2
3
4
5
6
7
//
// addImage 加載紋理圖片
// 支持圖片格式: .png, .bmp, .tiff, .jpeg, .pvr
Texture2D *texture = Director::getInstance()->getTextureCache()->addImage(filename);
// getTextureForKey 獲取紋理圖片
Texture2D *texture = Director::getInstance()->getTextureCache()->getTextureForKey(textureKeyName);
//


4、異步加載紋理

TextureCache類還支持異步加載資源的功能,利用 addImageAsync 方法。你可以很方面地給addImageAsync方法添加一個回調方法,這樣,當紋理異步加載結束的時候,可以得到通知。

> 支持的圖片格式有:.png, .jpg 。

你可以選擇異步加載方式,這樣你就可以爲loading場景增加一個進度條。

關鍵代碼如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
//
TextureCacheTest::TextureCacheTest()
: _numberOfSprites(20)
, _numberOfLoadedSprites(0)
{
    auto size = Director::getInstance()->getWinSize();
    _labelLoading = Label::createWithTTF("loading...""fonts/arial.ttf", 15);
    _labelPercent = Label::createWithTTF("%0""fonts/arial.ttf", 15);
    _labelLoading->setPosition(Point(size.width / 2, size.height / 2 - 20));
    _labelPercent->setPosition(Point(size.width / 2, size.height / 2 + 20));
    this->addChild(_labelLoading);
    this->addChild(_labelPercent);
    // 異步加載紋理圖片 addImageAsync
    // 加載完紋理後,會執行回調函數
    Director::getInstance()->getTextureCache()->addImageAsync("Images/HelloWorld.png", CC_CALLBACK_1(TextureCacheTest::loadingCallBack, this));
    Director::getInstance()->getTextureCache()->addImageAsync("Images/grossini.png", CC_CALLBACK_1(TextureCacheTest::loadingCallBack, this));
    Director::getInstance()->getTextureCache()->addImageAsync("Images/CloseNormal.png", CC_CALLBACK_1(TextureCacheTest::loadingCallBack, this));
    ....
}
// 異步加載的回調函數
void TextureCacheTest::loadingCallBack(cocos2d::Texture2D *texture)
{
    ++_numberOfLoadedSprites;
    char tmp[10];
    sprintf(tmp,"%%%d", (int)(((float)_numberOfLoadedSprites / _numberOfSprites) * 100));
    _labelPercent->setString(tmp);
    if (_numberOfLoadedSprites == _numberOfSprites)
    {
        this->removeChild(_labelLoading, true);
        this->removeChild(_labelPercent, true);
        addSprite();
    }
}
//


5、清理緩存

1
2
3
4
5
6
7
8
9
10
11
12
//
// 釋放當前所有引用計數爲1的紋理,即目前沒有被使用的紋理。
// 比如新場景創建好後,使用此方法釋放沒有使用的紋理非常方便。
Director::getInstance()->getTextureCache()->removeUnusedTextures();
// 通過給定的紋理貼圖(texture)的關鍵名(key name)從緩存中刪除該紋理貼圖
Director::getInstance()->getTextureCache()->removeTextureForKey("Images/grossinis.png");
// 清除加載紋理貼圖的記錄,如果你收到“內存警告”,請調用該方法。
// 在短期內 : 會釋放一些資源文件來防止你的app出現閃退現象
// 在中期內 : 會分配更多資源
// 長遠來看 : 沒有區別
Director::getInstance()->getTextureCache()->removeAllTextures();
//


【SpriteFrameCache】

1、概述

  • SpriteFrameCache 主要服務於多張碎圖合併出來的紋理圖片。這種紋理在一張大圖中包含了多張小圖,直接通過TextureCache引用會有諸多不便,因而衍生出來精靈框幀的處理方式,即把截取好的紋理信息保存在一個精靈框幀內,精靈通過切換不同的框幀來顯示出不同的圖案。

  • SpriteFrameCache 內部封裝了一個Map<std::string, SpriteFrame*> _spriteFrames對象。(其中key爲幀的名稱)

  • SpriteFrameCache一般用來 處理 plist文件 (這個文件指定了每個獨立的精靈在這張“大圖”裏面的位置和大小),該文件對應一張包含多個精靈的大圖,plist文件可以使用TexturePacker製作。

  • SpriteFrameCache的常用接口和TextureCache類似,不再贅述了,唯一需要注意的是添加精靈幀的配套文件一個plist文件和一張大的紋理圖。


2、獲取與銷燬SpriteFrameCache

SpriteFrameCache是一個單例對象,所以獲取方法與Director一樣。

1
2
3
4
5
6
//
// 獲取單例對象
SpriteFrameCache* cache = SpriteFrameCache::getInstance();
// 銷燬單例對象
SpriteFrameCache::destroyInstance();
//


3、精靈幀的加載與使用

通過 addSpriteFramesWithFile 加載 plist文件,將plist文件中的多張小圖加載到精靈幀緩存中。

1
2
3
4
5
6
7
8
9
//
// boy.png 裏集合了boy1.png,boy2.png這些小圖。
// 參數2 可不寫
SpriteFrameCache *frameCache = SpriteFrameCache::getInstance();
frameCache->addSpriteFramesWithFile("boy.plist""boy.png");
//從SpriteFrameCache緩存中找到boy1.png這張圖片.
auto frame_sp = Sprite::createWithSpriteFrameName("boy1.png");
this->addChild(frame_sp, 2);
//


4、清理緩存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//
// 從精靈幀緩存中刪除一個精靈幀.
SpriteFrameCache::getInstance()->removeSpriteFrameByName(const std::string &name);
// 清除載入精靈幀的字典。如果接收到“Memory Warning”, 請調用這個方法。
// 就眼前來說 : 它將釋放一些資源來阻止你的應用崩潰掉。
// 中期的角度 : 它將分配更多的資源。
// 從長遠來說 : 它將變成相同的。
SpriteFrameCache::getInstance()->removeSpriteFrames();
// 從一個.plist文件移除多個精靈幀。即:存儲在這個文件的精靈幀將被刪除。
// 當某個特定的紋理需要被刪除時候調用這個方法很方便。
SpriteFrameCache::getInstance()->removeSpriteFramesFromFile(const std::string &plist);
// 移除與特定的紋理結合的所有的精靈幀。
// 當某個特定的紋理需要被刪除時候調用這個方法很方便。
SpriteFrameCache::getInstance()->removeSpriteFramesFromTexture(cocos2d::Texture2D *texture);
// 移除沒用的精靈幀。保留數爲1的精靈幀將被刪除。
// 在開始一個新的場景之後調用這個方法很方便。
SpriteFrameCache::getInstance()->removeUnusedSpriteFrames();
//


5、SpriteFrameCache  VS. TextureCache

SpriteFrameCache精靈框幀緩存。顧名思義,這裏緩存的是精靈幀SpriteFrame,它主要服務於多張碎圖合併出來的紋理圖片(plist文件)。這種紋理在一張大圖中包含了多張小圖,直接通過TextureCache引用會有諸多不便,因而衍生出來精靈框幀的處理方式,即把截取好的紋理信息保存在一個精靈框幀內,精靈通過切換不同的框幀來顯示出不同的圖案。

跟TextureCache功能一樣,將SpriteFrame緩存起來,在下次使用的時候直接去取。

不過跟TextureCache不同的是:如果內存池中不存在要查找的圖片,它會提示找不到,而不會去本地加載圖片。

> TextureCache時最底層也是最有效的紋理緩存,緩存的是加載到內存中的紋理資源,也就是圖片資源。

> SpriteFrameCache精靈框幀緩存,緩存的時精靈幀。

> SpriteFrameCache是基於TextureCache上的封裝。緩存的是精靈幀,是紋理指定區域的矩形塊。各精靈幀都在同一紋理中,通過切換不同的框幀來顯示出不同的圖案。


【AnimationCache】

1、概述

通常情況下,對於一個精靈動畫,每次創建時都需要加載精靈幀,然後按順序添加到數組,再用Animation讀取數組創建動畫。這是一個非常煩瑣的計算過程。而對於使用頻率高的動畫,例如角色的走動、跳舞等,可以將其加入到AnimationCache中,每次使用都從這個緩存中調用,這樣可以有效的降低創建動畫的巨大消耗。

所以將創建好的動畫Animation直接放在動畫緩存AnimationCache中,當需要執行動畫動作時,就直接從動畫緩存中拿出來,去創建初始化Animation會非常方便。


2、相關函數

AnimationCache是一個單例對象,所以獲取方法與Director一樣。

通過 AnimationCache::getInstance() 獲取單例對象。

其相關函數如下:

1
2
3
4
5
6
7
8
9
10
11
//
// 添加一個動畫到緩存,命名爲name。
// name - animation : 是一組 鍵-值對(key-value) 的關係。
void addAnimation(Animation *animation, const std::string& name);
// 添加動畫的plist文件到緩存
void addAnimationsWithFile(const std::string& plist);
// 獲得指定名稱爲name的動畫
Animation* getAnimation(const std::string& name);
// 移除一個指定的動畫
void removeAnimation(const std::string& name);
//


3、使用舉例

1
2
3
4
5
6
7
8
9
//
//
//動畫命名爲 Explosion,加入到動畫緩存中
Animation* animation = Animation::createWithSpriteFrames(arr, 0.04);
AnimationCache::getInstance()->addAnimation(animation, "Explosion");
//直接從動畫緩存中取出 "Explosion" 動畫
Animation* animation = AnimationCache::getInstance()->getAnimation("Explosion");
//
//


【清理順序】

值得注意的是清理的順序。

我們推薦 清理順序 如下:

> 首先清理,動畫緩存AnimationCache,

> 然後清理,精靈幀緩存SpriteFrameCache,

> 最後清理,紋理緩存TextureCache。

按照引用層級由高到低,以保證釋放引用有效。


【致謝】

紋理緩存TextureCache

精靈幀緩存SpriteFrameCache

動畫緩存AnimationCache

三種緩存類介紹


感謝本文筆者(shahdza)的分享,Cocos引擎中文官網歡迎更多的開發者分享開發經驗。來稿請發送至:[email protected]

來源網址:http://shahdza.blog.51cto.com/2410787/1611766

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章