きょこみのーと

技術に関係ないほうのブログ

Cocos2d-x3.0以降のEventDispatcher制御について(その1)

はじめに

モーダルレイヤー的なことをやりたくて、色々調べてました。ソース追ったので大体わかったつもりだけど、実際の動きで検証したほうがわかりやすいかなーと。

SceneとScene上に配置したSpriteで見てみる

イメージ

数値は、ローカルZIndex

            1  | -- ActorSprite --|
                        |
BattleScene | --------------------------------------- |

サンプルコード

//
//  BattleSecne.cpp
//  Cocos2dRogueLike
//
//  Created by kyokomi on 2014/02/22.
//
//

#include "BattleSecne.h"

#include "ActorSprite.h"

BattleScene::BattleScene()
{
}

BattleScene::~BattleScene()
{
}

Scene* BattleScene::scene()
{
    Scene *scene = Scene::create();
    BattleScene *layer = BattleScene::create();
    scene->addChild(layer);
    return scene;
}

bool BattleScene::init()
{
    if ( !Layer::init() )
    {
        return false;
    }
    Size winSize = Director::getInstance()->getWinSize();
    
    // TouchEvent settings
    auto listener = EventListenerTouchOneByOne::create();
    listener->onTouchBegan = CC_CALLBACK_2(BattleScene::onTouchBegan, this);
    listener->onTouchMoved = CC_CALLBACK_2(BattleScene::onTouchMoved, this);
    listener->onTouchEnded = CC_CALLBACK_2(BattleScene::onTouchEnded, this);
    this->getEventDispatcher()->addEventListenerWithSceneGraphPriority(listener, this);

    // Spriteを配置
    ActorSprite::ActorDto actorDto = ActorSprite::createDto();
    actorDto.playerId = 4;
    auto pActorSprite = ActorSprite::createWithActorDto(actorDto, 1);
    pActorSprite->setPosition(Point(winSize.width / 2, winSize.height / 2));
    this->addChild(pActorSprite, 1, 1);
 
    pActorSprite->runBottomAction();
    
    // actorにタッチイベントを設定
    auto actorListener = EventListenerTouchOneByOne::create();
    actorListener->onTouchBegan = [pActorSprite](Touch* touch, Event* event) -> bool {
        CCLOG("%s : %s(%d)", "ActorSprite", "onTouchBegan", __LINE__);
        return true;
    };
    actorListener->onTouchMoved = [pActorSprite](Touch* touch, Event* event) {
        CCLOG("%s : %s(%d)", "ActorSprite", "onTouchMoved", __LINE__);
    };
    actorListener->onTouchEnded = [pActorSprite](Touch* touch, Event* event) {
        CCLOG("%s : %s(%d)", "ActorSprite", "onTouchEnded", __LINE__);
    };
    pActorSprite->getEventDispatcher()->addEventListenerWithSceneGraphPriority(actorListener, pActorSprite);
    
    return true;
}


bool BattleScene::onTouchBegan(Touch *touch, Event *unused_event)
{
    CCLOG("%s : %s(%d)", "BattleScene", __FUNCTION__, __LINE__);
    
    return true;
}

void BattleScene::onTouchMoved(Touch *touch, Event *unused_event)
{
    CCLOG("%s : %s(%d)", "BattleScene", __FUNCTION__, __LINE__);
    
}

void BattleScene::onTouchEnded(Touch *touch, Event *unused_event)
{
    CCLOG("%s : %s(%d)", "BattleScene", __FUNCTION__, __LINE__);
    
}

actorをタッチしたときの結果

予想通りActorのtouchEventを処理してからSceneのtouchEventが呼ばれる。

cocos2d: ActorSprite : onTouchBegan(58)
cocos2d: BattleScene : onTouchBegan(75)
cocos2d: ActorSprite : onTouchEnded(65)
cocos2d: BattleScene : onTouchEnded(89)

onTouchBeganでfalseを返してみる

変更したコード

bool BattleScene::init()
{
    // 〜 省略 〜

    actorListener->onTouchBegan = [pActorSprite](Touch* touch, Event* event) -> bool {
        CCLOG("%s : %s(%d)", "ActorSprite", "onTouchBegan", __LINE__);
        return false; // edit falseにしてみる
    };

    // 〜 省略 〜
}

結果

ActorのonTouchBeganでイベントが終了してonTouchEndedが呼ばれない。 SceneのonTouchEndedは、普通に呼ばれる。

cocos2d: ActorSprite : onTouchBegan(58)
cocos2d: BattleScene : onTouchBegan(75)
cocos2d: BattleScene : onTouchEnded(88)

ActorSpriteにSpriteをaddChildしてみる

ActorSpriteのonTouchBeganは、return falseのままです。

変更したコード

bool BattleScene::init()
{
    // 〜 省略 〜

    // add 

    // 武器をプレイヤーにadd
    auto pWeaponSprite = Sprite::create("icon_set/item_768.png");
    pActorSprite->addChild(pWeaponSprite);
    // 武器にタッチイベントを設定
    auto weaponListener = EventListenerTouchOneByOne::create();
    weaponListener->onTouchBegan = [pWeaponSprite](Touch* touch, Event* event) -> bool {
        CCLOG("%s : %s(%d)", "WeaponSprite", "onTouchBegan", __LINE__);
        return true;
    };
    weaponListener->onTouchMoved = [pWeaponSprite](Touch* touch, Event* event) {
        CCLOG("%s : %s(%d)", "WeaponSprite", "onTouchMoved", __LINE__);
    };
    weaponListener->onTouchEnded = [pWeaponSprite](Touch* touch, Event* event) {
        CCLOG("%s : %s(%d)", "WeaponSprite", "onTouchEnded", __LINE__);
    };
    pWeaponSprite->getEventDispatcher()->addEventListenerWithSceneGraphPriority(weaponListener, pWeaponSprite);

    // 〜 省略 〜    
}

イメージ

数値は、ローカルZIndex

               0   | - WeaponSprite -|
                           |
            1  | ----- ActorSprite ----- |
                           |
BattleScene | --------------------------------------- |

結果

Weapon -> Actor -> Sceneの順番にイベントが実行される。 Actorは、onTouchBeganでfalseを返しているのでonTouchEndedが呼ばれない。

cocos2d: WeaponSprite : onTouchBegan(75)
cocos2d: ActorSprite : onTouchBegan(58)
cocos2d: BattleScene : onTouchBegan(92)
cocos2d: WeaponSprite : onTouchEnded(82)
cocos2d: BattleScene : onTouchEnded(105)

Sceneの新しくLayerを追加する(ZIndexは2)

わかりにくくなるので、一旦ActorSpriteのonTouchBeganはtrueに戻す。

変更したコード

bool BattleScene::init()
{
    // 〜 省略 〜

    actorListener->onTouchBegan = [pActorSprite](Touch* touch, Event* event) -> bool {
        CCLOG("%s : %s(%d)", "ActorSprite", "onTouchBegan", __LINE__);
        return true; // edit trueに戻す
    };

    // 〜 省略 〜

    // add 

    // 青の半透明レイヤーを追加
    auto pLayer = LayerColor::create(Color4B::BLUE);
    pLayer->setOpacity(128);
    pLayer->setContentSize(winSize);
    this->addChild(pLayer, 2, 2);
    // 武器にタッチイベントを設定
    auto layerListener = EventListenerTouchOneByOne::create();
    layerListener->onTouchBegan = [pLayer](Touch* touch, Event* event) -> bool {
        CCLOG("%s : %s(%d)", "LayerColor", "onTouchBegan", __LINE__);
        return true;
    };
    layerListener->onTouchMoved = [pLayer](Touch* touch, Event* event) {
        CCLOG("%s : %s(%d)", "LayerColor", "onTouchMoved", __LINE__);
    };
    layerListener->onTouchEnded = [pLayer](Touch* touch, Event* event) {
        CCLOG("%s : %s(%d)", "LayerColor", "onTouchEnded", __LINE__);
    };
    pLayer->getEventDispatcher()->addEventListenerWithSceneGraphPriority(layerListener, pLayer);


    // 〜 省略 〜    
}

イメージ

数値は、ローカルZIndex

            2  | --------------------------- Layer --------- |
                                               |
               0   | - WeaponSprite -|         |
                           |                   |
            1  | ----- ActorSprite ----- |     |
                           |                   |
BattleScene | ---------------------------------------------------- |

結果

予想どおりZIndexに従って順番に呼ばれている。

LayerColor -> Weapon -> Actor -> Scene

cocos2d: LayerColor : onTouchBegan(94)
cocos2d: WeaponSprite : onTouchBegan(75)
cocos2d: ActorSprite : onTouchBegan(58)
cocos2d: BattleScene : onTouchBegan(111)
cocos2d: LayerColor : onTouchEnded(101)
cocos2d: WeaponSprite : onTouchEnded(82)
cocos2d: ActorSprite : onTouchEnded(65)
cocos2d: BattleScene : onTouchEnded(124)

LayerColorのonTouchBeganでfalseを返す

Layer以降touchイベントはどうなるか?

変更したコード

bool BattleScene::init()
{
    // 〜 省略 〜

    layerListener->onTouchBegan = [pLayer](Touch* touch, Event* event) -> bool {
        CCLOG("%s : %s(%d)", "LayerColor", "onTouchBegan", __LINE__);
        return false; // edit falseにしてみる
    };

    // 〜 省略 〜    
}

結果

LayerColorのonTouchEndedが呼ばれなくなる。 Sceneが呼ばれるのと同じで、Actorには影響しない。

cocos2d: LayerColor : onTouchBegan(94)
cocos2d: WeaponSprite : onTouchBegan(75)
cocos2d: ActorSprite : onTouchBegan(58)
cocos2d: BattleScene : onTouchBegan(111)
cocos2d: WeaponSprite : onTouchEnded(82)
cocos2d: ActorSprite : onTouchEnded(65)
cocos2d: BattleScene : onTouchEnded(124)

画面キャプチャー(最終版)

f:id:kyokomi:20140222174143p:plain

まとめ

  • ローカルZIndexの順番にTouchイベントが呼ばれ、addChild元の親要素は一番最後に呼ばれる
  • onTouchBeganでのreturn false;の影響は、listener内のみ有効

思った以上に長くなったので、次回に続きます。

次回は、addEventListenerWithSceneGraphPriorityではなく、addEventListenerWithFixedPriorityを利用してEventの優先順位の検証をします。

あとMenuItemを混ぜたりする予定。 まあMenuItemは、addEventListenerWithSceneGraphPriorityを使ってるので今回のSpriteとかと同じ感じになるけど。