アイテムやキャラクター、イベント用バッチ等、
コンテンツ内で扱う画像リソースが段々と着実に増えていき
初回ダウンロードの増加が止まらない。。。
そんな悪夢な未来を、手間なく楽に回避する方法について考えてみました。
そして思い付いたのは、随時追加される画像リソースを
ネットワークから直接取得する。
つまり、htmlのimgタグのような使い方で
勿論urlを指定して直接表示できるスプライトです。
理想としては、こんな感じに使いたい。
auto sprite = Sprite::createWithUrl("http://resource-server/a.png");
addChild( sprite );
後はロードが終わったら勝手に表示されてくれて、
初期化中にUIスレッドに負荷をかけない事が理想。
これを実現するには、
ネットワークから画像ファイルをダウンロードするのに
・network::HttpClient::getInstance()->sendImmediate
端末にダウンロード済みの画像ファイルを非同期読み込みするのに
・Director::getInstance()->getTextureCache()->addImageAsync
を使うのが良さそうです。
コードにすると、こんな感じでしょうか。
※とりあえず、Spriteを継承したLazySpriteとして定義してみます。
初期化時に先ず、ファイルをダウンロードします
bool LazySprite::initWithURL(const std::string& url){
if( Sprite::init() ){
_saveFileDir = FileUtils::getInstance()->getWritablePath();
_saveFilePath = _saveFileDir + url.substr( url.rfind('/')+1, std::string::npos );
network::HttpRequest* req = new network::HttpRequest();
req->setRequestType( network::HttpRequest::Type::GET );
req->setUrl( url.c_str() );
req->setResponseCallback( CC_CALLBACK_2(LazySprite::httpRequestCallback, this) );
// ダウンロード中に自インスタンスが削除されないように、参照カウントを上げておく
retain();
// HttpRequest発行
// network::HttpClient::getInstance()->sendではなく
// network::HttpClient::getInstance()->sendImmediateなのは、
// network::HttpClient::getInstance()->sendでAPIコールをしている場合に
// 阻害される事がないようにする為
network::HttpClient::getInstance()->sendImmediate( req );
return true;
}
return false;
}
次にダウンロードしたファイルをローカルへ保存し、そのテクスチャを非同期で読み込みます。
本当はダウンロードしたバイナリから直接Imageを作成できると良いのですが、
その場合は自分でスレッドを立てなければいけなさそうなので、
ひとまずaddImageAsyncを使います。
void LazySprite::httpRequestCallback(network::HttpClient* client, network::HttpResponse* response){
if( response->getResponseCode() == 200 ){
// ダウンロードしたファイルをローカルへ保存する
if( FileUtils::getInstance()->createDirectory( _saveFileDir ) ){
FILE* file = fopen( _saveFilePath, "wb" );
fwrite( &response->getResponseData()->at(0), response->getResponseData()->size(), 1, file );
fclose( file );
}
// 自分以外からの参照があれば処理
if( getReferenceCount() > 1 ){
// テクスチャ読込中に自インスタンスが削除されないように、参照カウントを上げておく
retain();
// テクスチャ読み込み開始
Director::getInstance()->getTextureCache()->addImageAsync( _saveFilePath, CC_CALLBACK_1(LazySprite::textureLoadCallback, this) );
}
}else{
CCLOG("%s", _saveFilePath.c_str());
CC_ASSERT(0);
}
// initWithURLで上げた参照カウントを戻す
release();
}
テクスチャの読み込みが完了したら、スプライトへ適用します。
このタイミングでスプライトが表示されます。
void LazySprite::textureLoadCallback(Texture2D* texture){
// 自分以外からの参照があれば処理
if( getReferenceCount() > 1 ){
setTexture( texture );
setTextureRect( Rect(0, 0, texture->getContentSize().width, texture->getContentSize().height ) );
}
// httpRequestCallbackで上げた参照カウントを戻す
release();
}
注意点としては、各非同期処理の完了待ち中に
インスタンスが削除されてしまう(removeChild等で)と良くないことになる為、
非同期処理コールの直前に自身をretainし、終わったらreleaseという感じで保護します。
最後に、cocos2d-x恒例の create**** メソッドを定義します。
Sprite* LazySprite::createWithURL(const std::string& url){
LazySprite *sprite = new (std::nothrow) LazySprite();
if (sprite && sprite->initWithURL(url))
{
sprite->autorelease();
return sprite;
}
CC_SAFE_DELETE(sprite);
return nullptr;
}
これで cocos2d-x版、imgタグみたいなクラスができました。
更にダウンロードした画像ファイルをキャッシュする機能を付けると
コチラのようになります。
CCLazySprite.h
CCLazySprite.cpp