游戏刚启动时,最常见的画面就是进度条了。而进度条展示的都是资源的加载进度。包括图片资源的。
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这个耗时的操作。