Cocs2d-xでアニメションを停止・再開する

Cocos2d-xでノードに設定したアニメーションを停止・再開する方法です。

Nodeクラスのpasue/resmeメソッドを使用します。使い方は以下の通りです。

auto sp = Sprite::create("sample.png");

// アクションを作成
auto jumpAction1 = JumpBy::create(0.25f, Vec2(0, -10), 28, 1);

// アクションを実行
sp->runAction(jumpAction1);

// スプライト(Node)に設定したアクションを停止
sp->pause();

// アクションを再開
sp->resure();

余談ですが、pauseSchedulerAndActionsとresumeSchedulerAndActionsはそれぞれ中身が以下の通りになっているためpause, resumeを使っても効果は同じです。

// CCNode.cpp

void Node::resumeSchedulerAndActions()
{
    resume();
}

void Node::pauseSchedulerAndActions()
{
    pause();
}

指定したノードに設定されている以下の効果がすべて停止されます。

  • runActionで指定したアニメーション効果
  • タッチリスナー
  • メインループ(update)

子要素全ての処理を停止するためのUtility

pause/resumeメソッドは指定したNodeのみ動作を停止できるので、Layerに設定されているSpriteなどの動きを一斉に停止できるようにUtilityを作成します。

#pragma once
#pragma execution_character_set("utf-8")

#include "cocos2d.h"

class GameUtil
{
public:
    
    // 指定したノードと子要素すべてアニメーションを停止する
    static void pauseAll(cocos2d::Node* node)
    {
        node->pause();

        for (cocos2d::Node* _node : node->getChildren())
        {
            pauseAll(_node);
        }
    }

    // 指定したノードと子要素すべてのアニメーションを再開する
    static void resumeAll(cocos2d::Node* node)
    {
        node->resume();

        for (cocos2d::Node* _node : node->getChildren())
        {
            resumeAll(_node);
        }
    }
};

但し、全部が乗ってるLayerとかScene事態を指定すると二度とゲームが再開できなくなるのでご注意を。

余談ですが停止するのに以下のメソッドを使用すると設定が削除されるので再開できません。

void Node::stopAllActions()
{
    _actionManager->removeAllActionsFromTarget(this);
}

動作サンプル

以下のような状態で前述のUtilityを使用して前回作成したダメージ表示だけを停止するサンプルを作成しました。

f:id:Takachan:20170815010754p:plain

動作イメージは以下の通り。ちょっと動きがキモいかもしれません。


コードは以下の通りです。

サンプル - ヘッダー

まずは初期プロジェクトに以下ヘッダーを追加します。

class HelloWorld : public cocos2d::Layer
{
    // 実行回数のカウンタ
    float total = 0;
    
    // ポーズ中かどうかを表すフラグ
    bool isPause = false;

    // アニメーションを表示するためのレイヤー
    cocos2d::Layer* animationLayer = nullptr;

public:

    // ...省略...

    void update(float delta);
};

サンプル - 実装

ベースレイヤーにキャラクターの歩行グラ、アニメーションレイヤーにダメージ表示をランダムに行いタップで停止・再開を行います。

bool HelloWorld::init()
{
    // ...省略...

    // 対象の画像の読み取り
    auto sprite = Sprite::create("hero_1.png");
    auto texture = Sprite::create("hero_1.png")->getTexture();
    texture->setAliasTexParameters();

    // アニメーションを格納するオブジェクト
    auto animation = Animation::create();
    animation->setDelayPerUnit(0.3f);
    animation->setRestoreOriginalFrame(true);

    // 1コマの大きさ
    int frameWidth = sprite->getContentSize().width / 6;
    int frameHeight = sprite->getContentSize().height / 4;

    for (int x : { 0, 1, 2, 1 })
    {
        animation->addSpriteFrameWithTexture(texture, Rect(frameWidth * x, 0, frameWidth, frameHeight));
    }

    // 空のスプライトを画面中央に設定
    auto animationSprite = Sprite::create();
    animationSprite->setPosition(visibleSize.width / 2 + origin.x, visibleSize.height / 2 + origin.y);
    animationSprite->setScale(2.0f);
    animationSprite->setContentSize(Size(frameWidth * 2, frameHeight * 2));

    // アニメーションを設定して実行
    animationSprite->runAction(RepeatForever::create(Animate::create(animation)));

    this->addChild(animationSprite);

    srand((unsigned int)time(nullptr));

    auto listner = EventListenerTouchOneByOne::create();

    // タッチ時のポーズ・再開の動作の指定
    listner->onTouchBegan = [this](Touch* touch, Event* event)
    {
        if (!this->isPause)
        {
            GameUtil::pauseAll(this->animationLayer);

            this->isPause = true;
        }
        else
        {
            GameUtil::resumeAll(this->animationLayer);

            this->isPause = false;
        }

        return true;
    };

    this->getEventDispatcher()->addEventListenerWithSceneGraphPriority(listner, this);

    return true;
}

void HelloWorld::update(float delta)
{
    if (this->isPause)
    {
        return; // ポーズ中なら無視
    }

    // ダメージ表示をランダムに行う
    this->total += delta;
    if (this->total > 0.1)
    {
        for (int i = 0; i < 3; i++)
        {
            auto visibleSize = Director::getInstance()->getVisibleSize();

            auto effect = DmgEffectFF5::create();
            effect->setPosition(Vec2(random() % 
                (int)visibleSize.width, random() % (int)visibleSize.height));

            effect->showEffect(random() % 9999);

            this->animationLayer->addChild(effect);
        }
        this->total = 0;
    }
}