Cocos2d-xでスプライトがタッチされたかどうかを判定する

Cocos2d-x でスプライトがタッチされたかどうかを判定する場合、タッチされた座標がスプライトの中か外かを判定して行います。便利なメソッドなどが存在しないため、定型的なコードを書く必要があります。

以下の環境で確認しています。

  • Cocos2d-x 3.16
  • Win32 - VisualStudio2017

判定するためのコードは以下の通りです。

// タッチ判定したいスプライト
Sprite* targetSprite = Sprite::create("画像のパス");
targetSprite->setPosition(Vec2(100, 100));

// レイヤーにスプライトを追加
this->addChild(targetSprite)

// タッチイベントの作成
EventListenerTouchOneByOne* touchListener = EventListenerTouchOneByOne::create();

// タッチ開始時の処理
listener->onTouchBegan = [this](Touch* touch, Event* event)
{
    // スプライトのインスタンスの取得
    Node* target = event->getCurrentTarget();

    // スプライトの矩形を取得
    Rect spriteRect(0, 0, target->getContentSize().width, target->getContentSize().height);

    if (spriteRect.containsPoint(target->convertTouchToNodeSpace(touch)))
    {
        // タッチされたときの処理
    }
}

// タッチ処理を追加
Director::getInstance()->getEventDispatcher()->addEventListenerWithSceneGraphPriority(listener, targetSprite);
// 以下のように書いても同じ動作
//this->getEventDispatcher()->addEventListenerWithSceneGraphPriority(listener, targetSprite);
//this->_director->getEventDispatcher()->addEventListenerWithSceneGraphPriority(listener, targetSprite);

タッチ登録の際の addEventListenerWithSceneGraphPriority の第2引数に、対象にしたいスプライトを指定する事で、onTouchBegan の touch->getCurrentTarget でスプライトが取れるようになります。

自分の環境だと target->getBoundingBox は位置がずれてしまうため target->getContentSize() から Rect を作成しています。

余談ですが、Spriteに対して、setScale()で表示倍率を変えている場合、getBoundingBox()でとれる矩形とconvertTouchToNodeSpace()で変換される座標系が一致しなくなるため、SetScaleでSpriteのサイズを変更するときのRectはgetBoundingBox()を使用しないほうが良いです。CocosStudioなんかで画面作るとscaleしかサイズ変更がないため特にそうだと思います。

タッチ判定したら後続のリスナーを処理させない

複数のスプライトに対してタッチ処理を追加するとタッチしたときに登録した全てのハンドラの実行がされます。

スプライトがタッチされた判断をしたらそれ以降リスナーが反応してほしくない場合、Event::stopPropagation() を使用します。上記の onTouchBegan に以下の記述を追加します。

    if (spriteRect.containsPoint(target->convertTouchToNodeSpace(touch)))
    {
        // タッチされたときの処理

        // ★★★後続のタッチイベントを発生させない
        event->stopPropagation();
    }

3つのスプライトにタッチイベントを登録して画面をタッチした場合、何も書かないと 1つ目 → 2つ目 → 3つ目 のようにすべてのリスナーが実行されますが、上記のように記述すると 1つ目 だけが処理されて残りは実行されなくなります。