タッチ・スワイプした後にエフェクトを発生させる

今回は、「画面にタッチした場所の表示」と「スワイプした軌跡」にエフェクトを表示する汎用レイヤーを作成します。

成果物

前回作成した「X-Y座標系と円座標系、2点間の角度を求める」の上に軌跡エフェクトのレイヤーをかぶせています。ちょっと原色されて色が地味になっています。

タッチした場所に丸い円、スワイプするとパーティクル風の矩形が軌道に少しの間表示されます。

作成環境

以下環境で作成しています。

  • Cocos2d-x 3.16 - Win32プロジェクト
  • Windows10
  • VisualStudio2017

コード

ヘッダー側コード

効果を発生させる画面をレイヤーを継承して作成しています。

#pragma once

#include <cocos2d.h>
#include <vector>
#include <AppCommon.hpp>

/**
 * タッチ、スワイプした場所に効果を発生させるためのレイヤー
 */
class TouchIndicateLayer : public cocos2d::Layer, CreateFunc<TouchIndicateLayer>
{
    /** タッチを引っ張った距離 */
    double totalDist;
    /** 最後の位置 */
    cocos2d::Vec2 _latestPos;
    /** カラーパレット */
    static const std::vector<cocos2d::Color3B> colorList;
    /** 次回の実行時間 */
    double nextDist;

public:
    bool init();
    using CreateFunc::create;

    bool onTouchBegan(cocos2d::Touch* touch, cocos2d::Event* e) override;
    void onTouchMoved(cocos2d::Touch* touch, cocos2d::Event* e) override;
    //void onTouchEnded(cocos2d::Touch* touch, cocos2d::Event* e) override;

    // 指定した位置にエフェクトを発生させる
    void createSwipeEffect(double x, double y);
    // 次のエフェクト発生までの時間を設定します。
    void setNextDist();
};

実装側コード

かなり長いですがレイヤーの実装は以下の通りです。

#include <cocos2d.h>
#include <local\layer\TouchIndicateLayer.hpp>

#include <AppCommon.hpp>

using namespace cocos2d;

const std::vector<cocos2d::Color3B> TouchIndicateLayer::colorList =
{
    // パステルカラーっぽいパレット
    Color3B(255, 206, 205),
    Color3B(245, 211, 225),
    Color3B(255, 207, 255),
    Color3B(230, 202, 242),
    Color3B(221, 187, 237),
    Color3B(205, 207, 255),
    Color3B(204, 254, 255),
    Color3B(200, 249, 228),
    Color3B(203, 253, 204),
    Color3B(231, 254, 202),
    Color3B(240, 241, 197),
    Color3B(254, 254, 204)
};

bool TouchIndicateLayer::init()
{
    // タッチイベントの登録
    auto touchListener = EventListenerTouchOneByOne::create();
    touchListener->onTouchBegan = CC_CALLBACK_2(TouchIndicateLayer::onTouchBegan, this);
    touchListener->onTouchMoved = CC_CALLBACK_2(TouchIndicateLayer::onTouchMoved, this);
    this->_eventDispatcher->addEventListenerWithSceneGraphPriority(touchListener, this);

    return true;
}

bool TouchIndicateLayer::onTouchBegan(cocos2d::Touch * touch, cocos2d::Event * e)
{
    //
    // タッチした場所の円が拡大しながら消えてくアニメーションを行う
    //

    // 円
    DrawNode* circle = DrawNode::create();
    circle->drawDot(Vec2::ZERO, 3, Color4F(1.0f, 1.0f, 1.0f, 0.5f));
    circle->setPosition(touch->getLocation().x, touch->getLocation().y);

    // 拡大しながら消えていく処理
    float dulation = 0.4f;
    ActionInterval* scaleAct = CCScaleTo::create(dulation, 6.0f);
    ActionInterval* fadeAct = CCFadeTo::create(dulation, 0);

    Spawn* spwn = Spawn::create(scaleAct, fadeAct, nullptr);

    // 終わったら消す処理
    Sequence* seq = Sequence::create(spwn, AnimationUtil::createRemoveSelf(), nullptr);

    circle->runAction(seq);
    this->addChild(circle);

    // 押した位置を記憶する
    this->_latestPos = touch->getLocation();

    // 次に発生する間隔を設定
    this->setNextDist();

    return true;
}

void TouchIndicateLayer::onTouchMoved(cocos2d::Touch * touch, cocos2d::Event * e)
{
    Vec2 currentPos = touch->getDelta();

    this->totalDist += currentPos.getDistance(this->_latestPos);
    this->_latestPos = currentPos;

    // 閾値以上ならパーティクル風のエフェクトを表示する
    if (this->totalDist > this->nextDist)
    {
        this->createSwipeEffect(touch->getLocation().x, touch->getLocation().y);
        this->totalDist = 0;
        this->setNextDist();
    }
}

void TouchIndicateLayer::createSwipeEffect(double x, double y)
{
    Sprite* rect = Sprite::create();

    // 適当な大きさの矩形を作る
    double size = random<double>(1.25f, 3.0f);
    rect->setTextureRect(cocos2d::Rect(0, 0, size, size));
    
    // 半透明
    rect->setOpacity(255 * 0.6);

    // タッチ位置から少し離れた場所
    auto xsift = random<double>(-2.0f, 2.0f);
    auto yshift = random<double>(-2.0f, 2.0f);
    rect->setPosition(Vec2(x + xsift, y + yshift));

    // 任意の色をパレットから選択
    Color3B c = this->colorList.at(random<int>(0, this->colorList.size() - 1));
    rect->setColor(c);

    //ActionInterval* fadeAct = CCFadeTo::create(0.7f, 0);

    // アニメーションの時間
    double animationTime = random<double>(0.15f, 0.275f);

    // (1) 小さくするアニメーション
    FiniteTimeAction* scaleAct = CCScaleTo::create(animationTime, 0.2);

    // (2) 少し移動するアニメーション
    double dist = random<double>(0.75f, 3.0f);
    double xrad = CC_RADIANS_TO_DEGREES(random<double>(0, 360.0f));
    double yrad = CC_RADIANS_TO_DEGREES(random<double>(0, 360.0f));
    auto moveAct = CCMoveBy::create(animationTime, Vec2(cos(xrad) * dist, sin(yrad) * dist));

    // (1)と(2)を同時に実行
    auto spwn = Spawn::create(scaleAct, moveAct, nullptr);

    // 終了したら消えるように順次処理を追加
    rect->runAction(Sequence::create(spwn, AnimationUtil::createRemoveSelf(), nullptr));

    this->addChild(rect);
}

void TouchIndicateLayer::setNextDist()
{
    this->nextDist = random<double>(5.0f, 7.0f);
}

コードの説明

コード中でキーとなる部分を抜粋して説明します。

円・四角の表示

タイトルの通り、塗りつぶした円・四角形の作り方です。それぞれ作成したらNodeにaddChildすることで表示されます。

// 塗りつぶした円
DrawNode* circle = DrawNode::create();
// 引数 = [1] 基本ゼロ固定 [2] 半径、[3] 色の指定
circle->drawDot(Vec2::ZERO, 3, Color4F(1.0f, 1.0f, 1.0f, 0.5f));
circle->setPosition(touch->getLocation().x, touch->getLocation().y);

// 塗りつぶした四角形
Sprite* rect = Sprite::create();
// 大きさをRectで指定
rect->setTextureRect(cocos2d::Rect(0, 0, 5.0f, 5.0f));
rect->setColor(Color3B(128, 62, 256));

アニメーション

Toが絶対値でByが現在値からの相対量を表す。

// 大きさを1秒かけて0.2へ変更
auto scaleAct = CCScaleTo::create(1.0f, 0.2);

// 現在位置からXとYを+5移動
auto moveAct = CCMoveBy::create(1.0f, Vec2(5, 5)); 

// アニメーションの実行はrunActionに作ったものを指定する
Sprite* rect = Sprite::create();
rect->runAction(scaleAct);

アニメーションの同時実行・平行実行について

アニメーションを同時に実行する場合Spawn、シーケンシャルに実行する場合Sequenceを使用します。最後にnullptrを指定して終わらないと「XXX の読み取り中にアクセス違反が発生しました」とエラーが発生するため忘れずに。

// 同時に実行するアニメーションを作詞絵
auto spwn = Spawn::create(scaleAct, moveAct/*いくつでも指定できる*/, nullptr);

// 連続で実行する
Sequence::create(アニメーション(1), アニメーション(2)/*いくつでも指定できる*/, nullptr))

長くなりましたが以上です。