こんにちは。sekineです。
今回はv2→v3で大幅に変更があったタッチイベントとイベントディスパッチャについて、記事を書こうと思います。
ちなみに基本的な使い方等は既に他の方があげているものがありますので、自分は以下について書いて見ようかと思ってます。
使用しているバージョンは3.1.1となります
- リスナNode間での関連づけについて
- 優先度について
- タッチイベント中断・タッチイベント透過
タッチイベントの使い方(参考リンク)
touch – cocos2d-x v3.0でのタッチイベントの取り方 – Qiita
Cocos2d-x 3.0 beta2 シングルタッチ&マルチタッチイベント取得まとめ | Cocoa部
タッチイベントの大体の概要や使い方については、こちらを参照された方が分かりやすいかと思います!
リスナNode間での関連づけについて
EventDispatcherはNodeに個別にあるものではない
Flasherのみなさんは結構はまりどころかと思いますが
Node::getEventDispatcher()が返すものはDirector::getEventDispatcherと同じオブジェクトです。
試しに以下のようなコードを実行してみると…
auto nodeA = Sprite::create(); auto nodeB = Sprite::create(); if(nodeA->getEventDispatcher() == nodeB->getDispatcher()){ log("SAME"); }
きっちし”SAME”というログが出力されました。
Flashのようにノード個別にディスパッチャが生成されている訳ではなさそうです。
なので、タッチイベントにおいて「どこのEventDispatcherを使用したか」は、問題にはならないようです。
(ただしsetEventDispatcherというセッタも発見しましたので、何かしら用途があるのかもしれません)
画面内で発生したタッチイベントのリスナは全て実行される
Dispatcherが個別のものではないので
基本的にタッチイベントは画面のどこをタッチしてもaddEventListenerされたリスナの数、実行されます。
なので自作ボタンなどではタッチされた座標を取得して、接触判定などを行う必要があります。
後述しますが、もちろん伝搬を中断することも可能です。
優先度について
すべて実行されるということは、当然優先度が重要になってきます。
リスナの優先度を決める要因は2種類あります。
- EventDispatcher::addEventListenerWithSceneGraphPriorityを利用して登録
- EventDispatcher::addEventListenerWithFixedPriorityを利用して登録
〜WithSceneGraphPriorityのほう
これは指定したNodeの重なり順を優先度として採用する方法です。
例えば2つのリスナlistenerA,listenerBの優先度としてスプライトfrontSprite,backSpriteのそれぞれを
指定したとします。
addEventListenerWithSceneGraphPriority(listenerA, frontSprite)
addEventListenerWithSceneGraphPriority(listenerB, backSprite)
すると前面であるfrontSpriteを優先度として採用しているlistenerAが先に実行されます。
つまり見た目の順番にリスナが実行されることになります。
〜WithFixedPriorityのほう
これは数値で優先度を決定する方法で数値の小さい方が優先して実行されます。
されますが、この方法は注意点があります。
- WithSceneGraphPriorityで追加されたリスナが混在した場合、WithSceneGraphPriorityの方が優先される
- Nodeに紐づいている訳ではないので、自動で消されない。不要になった時にremoveする必要がある。
1の方は、数値で優先度を指定してもWithSceneGraphPriorityを使って登録されたリスナより絶対に優先されることはない、ってことです。後述のイベント伝搬を中断させるようなUIパーツがあったりすると、悲惨なことになりますので注意してください。
2の方ですが、EventDispatcherがDirectorのものであるのでNodeとイベントの紐付きはWithSceneGraphPriorityでのみ行われています。
つまり数値で指定した場合、そもそもNodeと関連付いておらず、直接DirectorのEventDispatcherに紐づいているので
シーンが切り替わろうとリスナは登録されたままになっているということです。
余談ですが、EventではcurrentTargetを取得できます。
が、これもWithSceneGraphPriorityを使用して登録されたリスナ内でしか取得できないようです。
(数値の場合、そもそもcurrentTargetとなるものが無いため)
優先度のまとめ
addEventListenerWithSceneGraphPriority(listener, {前面ノード})
↓
addEventListenerWithSceneGraphPriority(listener, {背面ノード})
↓
addEventListenerWithFixedPriority(listener, {小})
↓
addEventListenerWithFixedPriority(listener, {大})
という感じですね。
優先度の参考リンク
優先度についてはこちらの記事で助けられました。感謝!
Cocos2d-x3.0以降のEventDispatcher制御について(その1) – きょこみのーと
Cocos2d-x3.0以降のEventDispatcher制御について(その2)完 – きょこみのーと
タッチイベント中断・タッチイベント透過
イベントの中断(非透過)について
たとえば、シーン遷移をするボタンの背後に、違うボタンを配置していた場合、普通にやってしまうとどちらのタッチイベントも実行してしまいます。これだと、ちょっと使いにくいです。
そこで、前面の(優先度の高い)イベントが実行された時点で伝搬を中断(非透過)する必要があります。
あるイベントリスナより優先度の低いタッチイベントを実行しない(タッチイベントを非透過にする)ためには
listener->setSwalowTouches(bool)でtrueを指定します
部分的な非透過について
上記の方法を普通に実装すると、全ての背面のタッチイベントが実行されなくなります。
(リスナは全画面のタッチイベントに対して処理されるため)
では、ボタンの領域外では、タッチイベントを普通に処理したいけど
領域内では中断(非透過)にさせたい、という時にはどうしたら良いかというと
onTouchBeganのbool返り値で制御します
onTouchBegan内で返すbool値は、このイベントを処理するかどうか、という意味合いの返り値です。
trueではイベントを処理し、またその後のmoved,endedなどを処理しますよー、ということを伝え
falseでは、イベントを処理せず、その後のmoved,endedなども受け取りませんよー、ということを伝えるbool値です。
setSwalowTouches(true)かつonTouchBegan return true;の場合は、イベントを中断(非透過)にします。
setSwalowTouches(true)かつonTouchBegan return false;の場合は
setSwalowTouchesの設定時自体も処理しないことになるので、そのまま優先度が低い(もしくは背面のリスナ)が実行されます。
なので、部分的にイベントを非透過にする場合は
onTouchBegan(Touch* t, Event* evt){ if(座標内かどうか){ return true; }else{ return false; } }
としてあげる必要があります。
モーダルレイヤ
モーダルレイヤを作るためには以下のような手順が必要です
- setSwalowTouches(true)したリスナをWithSceneGraphPriorityで登録
- リスナのonTouchBeganでは常にreturn trueする
ちなみにsetSwalowTouches(true)の設定は、そのリスナより優先度の低いリスナにしか影響しないので
前面のオブジェクトのイベントは処理されますので、ご安心を。
(おまけ)MenuやCocoStudio、UIエディタのWidgetについて
内部的にsetSwalowTouches(true)にされているケースがあります。
うかつにaddChildしてしまうと、背面のタッチイベントにも影響する可能性があるので
動作がおかしくなった時はソースなどを確認すると良いかもしれません。
さいごに
これからもCocos2d-x関連の記事をあげていくつもりですので、
ご愛読頂ければ嬉しいです!
それからCocos2d-xを使ったアプリカビDONや
開発概要をまとめた記事Cocos2d-x(v3)を使って個人ゲームを開発した話 |も、よろしくお願いいたします。
注意事項
一応、軽く実験&この記事の内容のような理解で開発はしておりますが
もしも情報が間違っていましたら、お教えて頂ければうれしいです!