Edited at

imgタグのように使えるスプライト

More than 1 year has passed since last update.

アイテムやキャラクター、イベント用バッチ等、

コンテンツ内で扱う画像リソースが段々と着実に増えていき

初回ダウンロードの増加が止まらない。。。

そんな悪夢な未来を、手間なく楽に回避する方法について考えてみました。


そして思い付いたのは、随時追加される画像リソースを

ネットワークから直接取得する。

つまり、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