遊戲剛啓動時,最常見的畫面就是進度條了。而進度條展示的都是資源的加載進度。包括圖片資源的。
cocos2dx引擎的整體流程都是基於單線程的,但是加載圖片資源是個很耗時的操作,如果放在主線程中執行,很容易讓畫面卡頓。
所以cocos2dx提供了異步加載圖片資源的方法。先看看test裏怎麼用的,再看看具體如何實現。
首先就是加載你的資源
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(Vec2(size.width / 2, size.height / 2 - 20));
_labelPercent->setPosition(Vec2(size.width / 2, size.height / 2 + 20));
this->addChild(_labelLoading);
this->addChild(_labelPercent);
// load textrues
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/grossini_dance_01.png", CC_CALLBACK_1(TextureCacheTest::loadingCallBack, this));
Director::getInstance()->getTextureCache()->addImageAsync("Images/grossini_dance_02.png", CC_CALLBACK_1(TextureCacheTest::loadingCallBack, this));
Director::getInstance()->getTextureCache()->addImageAsync("Images/grossini_dance_03.png", CC_CALLBACK_1(TextureCacheTest::loadingCallBack, this));
Director::getInstance()->getTextureCache()->addImageAsync("Images/grossini_dance_04.png", CC_CALLBACK_1(TextureCacheTest::loadingCallBack, this));
Director::getInstance()->getTextureCache()->addImageAsync("Images/grossini_dance_05.png", CC_CALLBACK_1(TextureCacheTest::loadingCallBack, this));
Director::getInstance()->getTextureCache()->addImageAsync("Images/grossini_dance_06.png", CC_CALLBACK_1(TextureCacheTest::loadingCallBack, this));
Director::getInstance()->getTextureCache()->addImageAsync("Images/grossini_dance_07.png", CC_CALLBACK_1(TextureCacheTest::loadingCallBack, this));
Director::getInstance()->getTextureCache()->addImageAsync("Images/grossini_dance_08.png", CC_CALLBACK_1(TextureCacheTest::loadingCallBack, this));
Director::getInstance()->getTextureCache()->addImageAsync("Images/grossini_dance_09.png", CC_CALLBACK_1(TextureCacheTest::loadingCallBack, this));
Director::getInstance()->getTextureCache()->addImageAsync("Images/grossini_dance_10.png", CC_CALLBACK_1(TextureCacheTest::loadingCallBack, this));
Director::getInstance()->getTextureCache()->addImageAsync("Images/grossini_dance_11.png", CC_CALLBACK_1(TextureCacheTest::loadingCallBack, this));
Director::getInstance()->getTextureCache()->addImageAsync("Images/grossini_dance_12.png", CC_CALLBACK_1(TextureCacheTest::loadingCallBack, this));
Director::getInstance()->getTextureCache()->addImageAsync("Images/grossini_dance_13.png", CC_CALLBACK_1(TextureCacheTest::loadingCallBack, this));
Director::getInstance()->getTextureCache()->addImageAsync("Images/grossini_dance_14.png", CC_CALLBACK_1(TextureCacheTest::loadingCallBack, this));
Director::getInstance()->getTextureCache()->addImageAsync("Images/background1.png", CC_CALLBACK_1(TextureCacheTest::loadingCallBack, this));
Director::getInstance()->getTextureCache()->addImageAsync("Images/background2.png", CC_CALLBACK_1(TextureCacheTest::loadingCallBack, this));
Director::getInstance()->getTextureCache()->addImageAsync("Images/background3.png", CC_CALLBACK_1(TextureCacheTest::loadingCallBack, this));
Director::getInstance()->getTextureCache()->addImageAsync("Images/blocks.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();
}
}
整個過程是異步的,不會讓遊戲產生卡頓的情況。
再看看具體是怎麼實現的。
void TextureCache::addImageAsync(const std::string &path, const std::function<void(Texture2D*)>& callback)
{
Texture2D *texture = nullptr;
std::string fullpath = FileUtils::getInstance()->fullPathForFilename(path);
auto it = _textures.find(fullpath);
if( it != _textures.end() )
texture = it->second;
if (texture != nullptr)
{
callback(texture);
return;
}
// lazy init
if (_asyncStructQueue == nullptr)
{
_asyncStructQueue = new queue<AsyncStruct*>();
_imageInfoQueue = new deque<ImageInfo*>();
// create a new thread to load images
_loadingThread = new std::thread(&TextureCache::loadImage, this);
_needQuit = false;
}
if (0 == _asyncRefCount)
{
Director::getInstance()->getScheduler()->schedule(schedule_selector(TextureCache::addImageAsyncCallBack), this, 0, false);
}
++_asyncRefCount;
// generate async struct
AsyncStruct *data = new AsyncStruct(fullpath, callback);
// add async struct into queue
_asyncStructQueueMutex.lock();
_asyncStructQueue->push(data);
_asyncStructQueueMutex.unlock();
_sleepCondition.notify_one();
}
首先先在texture緩存裏查找,如果找到了,說明之前加載進來了,直接調用回調。沒找到則另開一個線程加載圖片,並將兩個圖片路徑和回調加入隊列_asyncStructQueue中
void TextureCache::loadImage()
{
AsyncStruct *asyncStruct = nullptr;
while (true)
{
std::queue<AsyncStruct*> *pQueue = _asyncStructQueue;
_asyncStructQueueMutex.lock();
if (pQueue->empty())
{
_asyncStructQueueMutex.unlock();
if (_needQuit) {
break;
}
else {
std::unique_lock<std::mutex> lk(_sleepMutex);
_sleepCondition.wait(lk);
continue;
}
}
else
{
asyncStruct = pQueue->front();
pQueue->pop();
_asyncStructQueueMutex.unlock();
}
Image *image = nullptr;
bool generateImage = false;
auto it = _textures.find(asyncStruct->filename);
if( it == _textures.end() )
{
_imageInfoMutex.lock();
ImageInfo *imageInfo;
size_t pos = 0;
size_t infoSize = _imageInfoQueue->size();
for (; pos < infoSize; pos++)
{
imageInfo = (*_imageInfoQueue)[pos];
if(imageInfo->asyncStruct->filename.compare(asyncStruct->filename) == 0)
break;
}
_imageInfoMutex.unlock();
if(infoSize == 0 || pos == infoSize)
generateImage = true;
}
if (generateImage)
{
const std::string& filename = asyncStruct->filename;
// generate image
image = new Image();
if (image && !image->initWithImageFileThreadSafe(filename))
{
CC_SAFE_RELEASE(image);
CCLOG("can not load %s", filename.c_str());
continue;
}
}
// generate image info
ImageInfo *imageInfo = new ImageInfo();
imageInfo->asyncStruct = asyncStruct;
imageInfo->image = image;
// put the image info into the queue
_imageInfoMutex.lock();
_imageInfoQueue->push_back(imageInfo);
_imageInfoMutex.unlock();
}
if(_asyncStructQueue != nullptr)
{
delete _asyncStructQueue;
_asyncStructQueue = nullptr;
delete _imageInfoQueue;
_imageInfoQueue = nullptr;
}
}
這個線程在取出_imageInfoQueue隊列裏每個圖片的路徑信息,從而構建圖片資源信息。因爲是在另一個線程裏進行的,所以不會卡頓。接下來就剩把圖片信息傳給opengl了。而這個過程耗時就在這,把大量的圖片信息一次性傳給opengl,這是相當耗時的操作,這會是畫面出現卡頓。所以cocos2dx採用每一幀讓opengl處理一張圖片的方法,
void TextureCache::addImageAsyncCallBack(float dt)
{
// the image is generated in loading thread
std::deque<ImageInfo*> *imagesQueue = _imageInfoQueue;
_imageInfoMutex.lock();
if (imagesQueue->empty())
{
_imageInfoMutex.unlock();
}
else
{
ImageInfo *imageInfo = imagesQueue->front();
imagesQueue->pop_front();
_imageInfoMutex.unlock();
AsyncStruct *asyncStruct = imageInfo->asyncStruct;
Image *image = imageInfo->image;
const std::string& filename = asyncStruct->filename;
Texture2D *texture = nullptr;
if (image)
{
// generate texture in render thread
texture = new Texture2D();
texture->initWithImage(image);
#if CC_ENABLE_CACHE_TEXTURE_DATA
// cache the texture file name
VolatileTextureMgr::addImageTexture(texture, filename);
#endif
// cache the texture. retain it, since it is added in the map
_textures.insert( std::make_pair(filename, texture) );
texture->retain();
texture->autorelease();
}
else
{
auto it = _textures.find(asyncStruct->filename);
if(it != _textures.end())
texture = it->second;
}
if (asyncStruct->callback)
{
asyncStruct->callback(texture);
}
if(image)
{
image->release();
}
delete asyncStruct;
delete imageInfo;
--_asyncRefCount;
if (0 == _asyncRefCount)
{
Director::getInstance()->getScheduler()->unschedule(schedule_selector(TextureCache::addImageAsyncCallBack), this);
}
}
}
這個回調在addImageAsync第一次被調用時就被註冊了,定時器的fps跟主線程的一樣。 texture->initWithImage(image);執行的就是glTexImage2D這個耗時的操作。