ページ

2015年6月23日火曜日

Cocos2d-xでタッチの2度押しを防止する方法

はじめに


Cocos2d-xでタッチイベントを発生させたとき、そのイベントで実行するアクションが終了するまでイベントを発生させたくない場合があります。

いわゆる2度押し防止というものですかね。

そのやり方について考えてみたのでメモします。

私が思いついたのは以下の2つです。

  1. アクション実行中はイベントを無効化する。
  2. シーングラフの最上位にイベント制御のための不可視のノードを置く。

サンプルがないと説明しにくいと思ったので作りました(Cocos2d-x v3.5)。


デフォルトのHelloWorld画面に以下のような動作を追加しています。

  • 画面をタップすると画像が180度回転する(最初は右回り)。
  • 再度タップすると逆方向に180度回転する。


2度押し防止処理をしない場合


サンプルは以下です。


アクション実行部分は以下のようになっています。

// HelloWorldScene.cpp
void HelloWorld::onTouchEnded(Touch* touch, Event* event)
{
    log("=== Call HelloWorld::onTouchEnded() ===");
    
    // Create Action
    float deltaAngle = this->getRightFlg() ? 180.0f : -180.0f;
    auto rotateBy = RotateBy::create(2.0f, deltaAngle);
    
    // Run Action
    this->getSprite()->runAction(rotateBy);
    
    // Set RightFlg
    this->setRightFlg(!this->getRightFlg());
}

これだと回転中にタッチすると、そこから逆回転してしまいます。

2度押し防止対策を行い、回転中にはタッチが無効になるようにしたいと思います。

アクション実行中はイベントを無効化する


アクション実行前にイベントリスナーを無効化できれば、二度押し防止が実現できそうです。

イベントリスナーの有効化・無効化はEventListenerクラスから継承しているsetEnabledメソッドで行えます。

アクション実行後にイベントリスナーを再度有効化するのも忘れずに行いましょう。

なお、イベントリスナーはEventDispatcherで登録後に取得する方法が見当たらないため、メンバ変数として保持します。

サンプルは以下です。


ポイント箇所を抜粋します。

// HelloWorldScene.cpp

// ...

bool HelloWorld::init()
{
    // Event Listener
    auto eventListener = EventListenerTouchOneByOne::create();
    eventListener->onTouchBegan = CC_CALLBACK_2(HelloWorld::onTouchBegan, this);
    eventListener->onTouchEnded = CC_CALLBACK_2(HelloWorld::onTouchEnded, this);
    Director::getInstance()->getEventDispatcher()->addEventListenerWithSceneGraphPriority(eventListener, this);
    
    // Set Properties
    // ...
    this->setEventListener(eventListener);
    
    return true;
}

// ...

void HelloWorld::onTouchEnded(Touch* touch, Event* event)
{
    log("=== Call HelloWorld::onTouchEnded() ===");
    
    // Create Action
    auto eventListenerDisable = CallFunc::create([&]() {
        log("eventListenerDisable()");
        this->getEventListener()->setEnabled(false);
    });
    
    auto eventListenerEnable = CallFunc::create([&]() {
        log("eventListenerEnable()");
        this->getEventListener()->setEnabled(true);
    });
    
    float deltaAngle = this->getRightFlg() ? 180.0f : -180.0f;
    auto rotateBy = RotateBy::create(2.0f, deltaAngle);
    
    // Run Action
    this->getSprite()->runAction(Sequence::create(eventListenerDisable,
                                                  rotateBy,
                                                  eventListenerEnable,
                                                  nullptr));
    
    // Set RightFlg
    this->setRightFlg(!this->getRightFlg());
}

Sequenceアクションを使い、目的のアクションの実行前後でイベントリスナーの無効化・有効化を行っています。

これにより、回転中にタッチしても、逆回転しなくなりました。

シーングラフの最上位にイベント制御のための不可視のノードを置く


前述の方法だと、回転中にタッチしても逆回転は行われませんが、右下のメニューボタンは押すことが出来てしまいます。

全てのイベントリスナーを無効にしたい場合は、イベント制御のための不可視のノードを追加します。

サンプルは以下です。


ポイント箇所を抜粋します。

// HelloWorldScene.cpp

// ...

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

    // Invisible node to prevent event listener
    auto invisibleNode = Node::create();
    this->addChild(invisibleNode, 999);
    auto preventEventListener = EventListenerTouchOneByOne::create();
    preventEventListener->onTouchBegan = [](Touch* touch, Event* event) {
        log("!!! Prevent Event Listener !!!");
        return true;
    };
    preventEventListener->setSwallowTouches(true);
    preventEventListener->setEnabled(false);
    Director::getInstance()->getEventDispatcher()->addEventListenerWithSceneGraphPriority(preventEventListener, invisibleNode);

    // Set Properties
    // ...
    this->setPreventEventListener(preventEventListener);
    
    return true;
}

// ...

void HelloWorld::onTouchEnded(Touch* touch, Event* event)
{
    log("=== Call HelloWorld::onTouchEnded() ===");
    
    // Create Action
    auto eventListenerDisable = CallFunc::create([&]() {
        log("eventListenerDisable()");
        this->getPreventEventListener()->setEnabled(true);
    });
    
    auto eventListenerEnable = CallFunc::create([&]() {
        log("eventListenerEnable()");
        this->getPreventEventListener()->setEnabled(false);
    });

    float deltaAngle = this->getRightFlg() ? 180.0f : -180.0f;
    auto rotateBy = RotateBy::create(2.0f, deltaAngle);
    
    // Run Action
    this->getSprite()->runAction(Sequence::create(eventListenerDisable,
                                                  rotateBy,
                                                  eventListenerEnable,
                                                  nullptr));
    
    // Set RightFlg
    this->setRightFlg(!this->getRightFlg());
}

不可視のノードを追加し、シーングラフの優先度がもっとも高くなるようにZオーダーを設定します。

そのノードに対し、setSwallowTouches()をtrueに設定したイベントリスナーを登録します。

そして、アクションの前後にそのイベントリスナーの有効化・無効化を行えば、アクション実行中は全てのイベントリスナーを無効化できます。

なお、ここではイベントリスナーのsetEnabled()を切り替えて実現しましたが、setSwallowTouches()のほうを切り替えても可能だと思います。

さて、ここで一つ疑問が。

ノードを置かずに、addEventListenerWithFixedPriority()で優先度が最高のイベントリスナーを設定するだけでも出来るのでは…?

と思ってそれも試したのですが、なぜか出来ませんでした。。。

イベントリスナー自体は有効になっているものの、どうもsetSwallowTouches()の設定が効いてないっぽく、イベントが伝搬してしまうんですよね…。

うーむ。。。

最後に


1つのイベントリスナーを無効化したい場合は前者、全てのイベントリスナーを無効化したい場合は後者、といった使い分けになると思います。

2015年6月7日日曜日

パスワード管理ツールにKeePass2を使ってみた

だいぶ前からパスワード管理ツールを導入しないとまずいなぁと感じていたのですが、この度KeePass2を導入してみました。

1Passwordと迷い、とりあえずKeePass2を使ってみて不都合があれば1Passwordにと思っていたのですが、KeePass2で特に問題を感じません。

Dropboxで管理ファイルを同期するようにしたのですが、クロスプラットフォームでも使えてもうめっちゃ便利ですよ…!!

なぜこれまで使ってこなかったのか…。

各プラットフォームで使用したソフトは以下の通りです。

Windows



2.x系をダウンロードします。

Ubuntu


  • KeePass2

apt-get でインストールできます。

Mac


ここに「MonoでKeePass2を動かせるよ!」とあるのですが、実際やってみたらすぐにアプリが落ちてしまい、使い物になりませんでした。

なのでこちらを使います。


2.x系に対応したバージョンがあるので、そちらをダウンロードします。

まだアルファ版ですが、特に問題なく使えます。

iPhone



iPhoneのアプリはいくつかあったのですが、私はこれを選びました。

特に不具合は感じません。しかも無料…!!

パスワード管理から解放され、脳が軽くなった気分。