[Cocos2d-x] イベントドリブンの仕組みとイベントリスナの設定方法(v3.x):C++とCocos2d-JSで微妙に異なる

基本

EventDispatcher

イベントディスパッチャ(イベントマネージャ)は全体でひとつのみ(Singleton的)。

Node#getEventDispatcher()とDirector#getEventDispatcher()で取得できるのは同じもの。

C++

auto node = cocos2d::Node::create();

if (cocos2d::Director::getInstance()->getEventDispatcher()
		== node->getEventDispatcher() {
	log("same");
}

JavaやFlash(ActionScript)のイベントドリブンの仕組みとは考え方が異なるので注意。

EventListener

イベントリスナの登録は、イベントディスパッチャに直接メソッド(関数)を登録するのではなく、各種EventListenerオブジェクトをつくってそれにコールバック(ユーザー関数)を登録したうえで、イベントディスパッチャにそのEventListenerオブジェクトを登録する。

EventListenerクラスには以下の6種類があり、状況によって使い分ける。

・EventListenerTouch – responds to touch events
・EventListenerKeyboard – responds to keyboard events
・EventListenerAcceleration – responds to accelerometer events
・EventListenMouse – responds to mouse events
・EventListenerCustom

コールバック

第1引数にイベントに関連したオブジェクト(タッチイベントならTouch)、第2引数に下記のEventオブジェクトが返ってくる。

JavaScript

function myCallback(info, event){ }

イベントリスナの2種類の登録方法

EventDispatcher#addEventListenerWithSceneGraphPriority(listener, node)

登録したNodeのSceneにおける重なりが上のオブジェクトのイベントリスナから順に実行される。

下記のaddEventListenerWithFixedPriority()の指定より優先される。

Sceneが切り替わるときに、自動的にremoveEventListener()される

(Nodeにひも付けされているので、そのインスタンスが破棄されるときに自動で解除されるため)

EventDispatcher#addEventListenerWithFixedPriority(listener, fixedPriority)

イベントリスナのプライオリティ(優先順位)を整数値で指定し、それが小さいイベントリスナから順に実行される。

上記のaddEventListenerWithSceneGraphPriority()のほうが優先される。

Sceneが切り替わっても、自動的にremoveEventListener()されることはない

イベントリスナを複数Nodeに登録する

この場合、EventListener#clone()で複製しなければならない。

    _eventDispatcher->addEventListenerWithSceneGraphPriority(listener1, sprite1);
    _eventDispatcher->addEventListenerWithSceneGraphPriority(listener1->clone(), sprite2);
    _eventDispatcher->addEventListenerWithSceneGraphPriority(listener1->clone(), sprite3);

Touchイベント伝播の優先順位

EventListener#setSwallowTouches(bool)

swallowは「取り消す」という意味。すなわちtrueを設定した場合、該当するイベントリスナより低い優先順位の(=あとに実行される)リスナには、イベントが伝播しない(実行されない)。

あくまで該当イベントリスナはonTouchBeganからonTouchEndedまで実行される。

onTouchBeganに設定したコールバックで「return false;」する

これは、onTouchBegan以降つづけて起こるはずのonTouchMoved、onTouchEndedを取り消して実行させないが、あくまで特定のEventListenerインスタンスの内部の話。

他のリスナにはイベントが通知される。

また、EventListener#setSwallowTouches(bool)の設定を無視するようになる。そのため、setSwallowTouches(true)としても以降のイベントリスナは実行される

解除方法

EventDispatcherの各種メソッドを使う。

void removeEventListener (EventListener *listener)
Remove a listener.

void removeEventListenersForType (EventListener::Type listenerType)
Removes all listeners with the same event listener type.

void removeEventListenersForTarget (Node *target, bool recursive=false)
Removes all listeners which are associated with the specified target…

void removeCustomEventListeners (const std::string &customEventName)
Removes all custom listeners with the same event name.

void removeAllEventListeners ()
Removes all listeners.

Event

リスナの第2引数にEventオブジェクトが返ってくる。

このオブジェクトから、イベントのターゲットなどの各種情報を取得する。

カスタムイベントの送信と受信

Cocos2d-xでは、ユーザーが独自にイベントを作成してイベントディスパッチャから送信(ディスパッチ)できる仕組みもある。

イベントリスナの登録

EventDispatcher#addCustomEventListener(string& eventType, function<void(EventCustom *)>& callback)

第1引数に独自イベントのタイプを文字列で指定し、第2引数にコールバック関数を登録する。

カスタムイベントの場合、コールバックに渡される引数はひとつのみなので、CC_CALLBACK_*マクロ(後述)を使う場合は「CC_CALLBACK_1」を使う。

ラムダ関数を使えば、マクロなしでもそのまま指定できる。

this->getEventDispatcher()->addCustomEventListener("myEvent", CC_CALLBACK_1(Sample::onMyEvent, this));

this->getEventDispatcher()->addCustomEventListener("myEvent", [](EventCustom* evt){
    auto data = evt->getUserData();
});
EventDispatcher#addEventListenerWithFixedPriority(listener, fixedPriority)

EventListenerCustomを使って、EventListenerインスタンスを生成する。あとは、Touchイベントなどと同じ。

auto listener = cocos2d::EventListenerCustom::create("myEvent", [](EventCustom* evt){
    auto data = evt->getUserData();
});
listener->

もちろん、これを継承して独自クラスをつくってもいい。

例1:タッチイベントの場合

タッチイベントは、シングルタッチのEventListenerTouchOneByOneとマルチタッチのEventListenerTouchAllAtOnceに分かれている。

以下ではNode関連クラスの内部で処理しているものとする(this == Nodeインスタンス)。

Cocos2d-x C++

	 auto listener = cocos2d::EventListener.create();
	 listener->onTouchEnded = CC_CALLBACK_2(Sample::onTouchEnded, this);
	 this->getEventDispatcher()->addEventListenerWithSceneGraphPriority(listener, this);

リスナにコールバックを登録する場合、以下の方法がある。

CC_CALLBACK_*マクロを使うと簡単。これは、std::bindをラップしただけのもの。

コールバックの引数の数に応じて、CC_CALLBACK_0からCC_CALLBACK_3まである。

std::bindは本来、関数の引数を特定の値に束縛(固定)したオブジェクトを作成するためにあるが、ここでは関数オブジェクトのようなものをつくるためにあると考えるとわかりやすい。

引数を束縛しない場合は、以下のようにいちいちstd::placeholder::_1、std::placeholder::_2などを指定しなければならず、面倒なのでCC_CALLBACK_*マクロがつくられているにすぎない。

listener->onTouchEnded = std::bind(&Sample::onTouchEnded, this,
		std::placeholder::_1, std::placeholder::_2);

Cocos2d-JS(Cocos2d-html)

Cocos2d-JSでは、イベントディスパッチャがEventDispatcherではなく、cc.eventManagerになっているので要注意。

    var listener1 = cc.EventListener.create({
        event: cc.EventListener.TOUCH_ONE_BY_ONE,
        // When "swallow touches" is true, then returning 'true' from the onTouchBegan method will "swallow" the touch event, preventing other listeners from using it.
        swallowTouches: true,
        //onTouchBegan event callback function                      
        onTouchBegan: function (touch, event) { 
            // event.getCurrentTarget() returns the *listener's* sceneGraphPriority node.   
            var target = event.getCurrentTarget();  

            //Get the position of the current point relative to the button
            var locationInNode = target.convertToNodeSpace(touch.getLocation());    
            var s = target.getContentSize();
            var rect = cc.rect(0, 0, s.width, s.height);

            //Check the click area
            if (cc.rectContainsPoint(rect, locationInNode)) {       
                cc.log("sprite began... x = " + locationInNode.x + ", y = " + locationInNode.y);
                target.opacity = 180;
                return true;
            }
            return false;
        }
	});

C++版と同じように、イベントごとにEventListenerのサブクラスが用意されているが、EventListenerクラスはC++版が抽象クラスであるのに対し、Cocos2d-JSでは通常のクラスなので、上記のようにeventプロパティにイベントのタイプを指定すればそのまま使える。

またオブジェクト・リテラルが使えるので、全体としてC++版よりも簡単にイベントリスナの登録ができる。

【確認途中】 cc.EventListener.create()でインスタンスを生成しなくても、普通のオブジェクトで同名の必要なプロパティが設定されていれば、Cocos2d-JS v3.2までは動いた。