Help us understand the problem. What is going on with this article?

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

More than 3 years have 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

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした