Cocos2d-x 3.0 でエフェクトを制作する(Sprite を使用)

エフェクト担当の 楷ノ木かえで です。第1回の記事ということで恐縮ですが、今回は Cocos2d-x 3.0 の機能である Sprite を用いたエフェクトの製作方法について書きます。

とりあえずエフェクトを統括的に取り扱う EffectManager クラスを作りましょう。Xcode のディレクトリツリー上で右クリックすると New File という項目があるので、C and C++ の中の C++ Class を選びましょう。Mac のバイナリも製作したい場合は Targets に iOSMac の両方を入れておくことを忘れずに。

で、最初のエフェクトとして、クリックした位置に浄化されるエフェクトが表示されるようにしましょう。

こんな感じで呼び出せるようになることを目標に作ります。

HelloWorld.h

private:
    bool onTouchBegan(cocos2d::Touch *touch, cocos2d::Event *unused_event);
    EffectManager* effectManager;

HelloWorld.cpp

bool HelloWorld::init(){  // Layer
    this->effectManager = new EffectManager();
    this->effectManager->setLayer(this);
}
bool HelloWorld::onTouchBegan(cocos2d::Touch *touch, cocos2d::Event *unused_event)
{
    this->effectManager->effectPurified(touch->getLocation());
    return true;
}

今回は、スプライトの上でアニメーションを走らせてエフェクトのように振る舞うようにします。アニメーションが1回走ったらエフェクトは消えます。雰囲気ではありますが以下のようなコードを書きました。このサイト を参考にしました。

EffectManager.h

#include "cocos2d.h"

class EffectManager
{
public:
    virtual bool init();

    void setLayer(cocos2d::Layer* layer);
    cocos2d::Sprite* effectPurified(cocos2d::Point location);
   
private:
    cocos2d::Layer* currentLayer;
};

EffectManager.cpp

#include "EffectManager.h"
#include "cocos2d.h"

bool EffectManager::init()
{
    return true;
}

void EffectManager::setLayer(cocos2d::Layer* layer)
{
    this->currentLayer = layer;
}
cocos2d::Sprite* EffectManager::effectPurified(cocos2d::Point location)
{
    cocos2d::SpriteFrameCache* spriteFrameCache = cocos2d::SpriteFrameCache::getInstance();
    spriteFrameCache->addSpriteFramesWithFile("mizu.plist");
    cocos2d::Animation *anim = cocos2d::Animation::create();
    for (int i = 2; i < 44; i ++)
    {
        char szImageFileName[128] = {0};
        sprintf(szImageFileName, "mizu_000%02d.png", i);
        cocos2d::SpriteFrame *frame = spriteFrameCache->getSpriteFrameByName(szImageFileName);
        anim->addSpriteFrame(frame);
    }

    anim->setDelayPerUnit(0.01f);
    anim->setRestoreOriginalFrame(false);
    anim->setLoops(1);
   
    cocos2d::Animate *animateAction = cocos2d::Animate::create(anim);
   
    auto sprite2 = cocos2d::Sprite::create();
    sprite2->runAction(
                       cocos2d::Sequence::create(animateAction,
                                                 cocos2d::CallFunc::create(
                                                                           [sprite2](){
                                                                               sprite2->removeFromParentAndCleanup(true);
                                                                           }), NULL));
    sprite2->setPosition(location);
    currentLayer->addChild(sprite2, 500000);
    return sprite2;
}

逐次見て行きましょう。まずは spriteFrameCache インスタンスを作って、そこに mizu.plist を読み込ませています。mizu.plist は TexturePacker などで製作された plist で、この中に mizu_00003.png みたいなファイル名で画像ファイルが入っています。for ループはその画像をバラして Animation のインスタンスの SpriteFrame として追加していっています。連番のファイル名を指定するためにキモいことをやっていますがご了承ください((plist 内のファイルを指定するもっといい方法はないんですかね。Python 風に書けば for png in plist: みたいに書けないかなぁと想定している。))。

Animation のインスタンスである anim に画像を放り込んでしまえばあとはわりと簡単で、Animate にぶち込んでそれを Sprite の runAction で動かしています。ただここで1点ポイントがあって、アニメーションが終わったら自動的に消えるという部分をラムダ式を使って実装しています。具体的には

    sprite2->runAction(
                       cocos2d::Sequence::create(animateAction,
                                                                    cocos2d::CallFunc::create([sprite2](){sprite2->removeFromParentAndCleanup(true);}),
                                                                    NULL)); 

の部分です。これは Sequence という複数の動作を繋げるものを用いて、作った Animation が1周したら sprite->removeFromParentAndCleanup(true) が呼ばれるようになっています。これで「エフェクトが終わったら勝手に消える」を実現しました。

あとは呼び出したいところで effectManager->effectPurified(point); としてやればその位置にエフェクトが表示されます。やったね。