LoginSignup
19
19

More than 5 years have passed since last update.

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

Last updated at Posted at 2015-02-02

アイテムやキャラクター、イベント用バッチ等、
コンテンツ内で扱う画像リソースが段々と着実に増えていき
初回ダウンロードの増加が止まらない。。。

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


そして思い付いたのは、随時追加される画像リソースを
ネットワークから直接取得する。

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

19
19
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
19
19