Cocos2d-x Ver 3.x 入門レシピ2

このページは、Qiita「Cocos2D-xでゲームを作ってみたのでそのメモ」を丸々掲載させてもらっています。開発時にじかに自分のメモページから参照した方が忘れないですむからです。

それだけの価値のある情報が記載されていますので利用させていただいております。

大変感謝!感謝!感謝!しております。

リンク先を掲載しておりますので原本を参照されると良いと思います。


前提条件としてはCocos2d-xをインストールして、新規プロジェクトを作成できたという段階です。またCocos2d-xのバージョンは3.0系での構文になります。


ログ出力

ログ出力はlog関数を利用します。非常にシンプル。

log("hoge");

________________________________

数値をstringに変換する

スコア表示など、intやfloatなどをstringに変換したいケースはたくさんあると思います。
Cocos2d-xではStringUtils関数群が実装されているので、これを利用すると便利です。

// `toString`を使ってスコアをラベル表示する
auto label = cocos2d::Label::createWithSystemFont(cocos2d::StringUtils::toString(this->score), "arial", 24.0f);
this->addChild(label);

________________________________

ランダムな値を得る

int randomNum = arc4random() % 10;

こうすることで0〜9のランダムな値を取得できます。

________________________________

マイナスを含んだランダムな範囲を得る

単に上記の応用ですが、範囲としたい値(例えば-10〜10)の最大値から、最大値の倍の値までのランダムな値を減算することで得られます。式にすると以下のようになります。

int randNum = 10 - (arc4random() % 21); // => -10〜10

________________________________

スプライト(画面になにか描画する)

スプライトはCocos2d-xの世界での標準的なひとつの描画単位です。
(2D系は概ねスプライトと呼ぶようです)

ラベルを作る

ラベルはcocos2d::Labelクラスを利用します。

auto label = cocos2d::Label::createWithSystemFont("hoge", "arial", 35.0f);// 文字とフォントサイズを渡して生成
label->setPosition(10, 10); // 位置を設定
this->addChild(label); // 生成したら`cocos2d::Layer`に追加する

画像からスプライトを生成

画像からも手軽にスプライトを生成することができます。

auto imageSprite = cocos2d::Sprite::create("filename.png");

cocos2d::Sprite::createメソッドに、ファイル名を指定するだけです。

プリミティブなスプライトを生成する

auto rect = cocos2d::Sprite::create();

スプライトの基準点を変更する

スプライトの基準点はanchorPointというプロパティで決まります。
デフォルトは(0.5, 0.5)のようです。(つまり中心)

ちなみに座標系としては左下が(0.0, 0.0)、右上が(1.0, 1.0)となります。
これはCocos2d-xの位置の座標系と同じですね。

________________________________

テクスチャアトラスを使ってスプライトを生成する

Cocos2d-xには「テクスチャアトラス」という仕組みがあります。
ざっくり言うと、複数ある画像をひとつの画像にまとめて、まとめた画像から座標を指定することで該当の画像を切り出す、というものです。

テクスチャアトラスを利用するメリットは複数画像をひとつにまとめられる、ということと、テキスチャがメモリにロードされるときに発生する可能性のある余分なエリアを最小限にする、というメリットがあります。

cocos2d::SpriteFrameCache

テクスチャアトラスは複数画像をひとつにまとめるため、手作業で管理するのはだいぶ骨が折れます。
しかしcocos2d::SpriteFrameCacheクラスを利用することで手軽に利用することができるようになります。

また、複数にまとめた画像を切り出すのに利用されるメタ情報を管理するものとして「plist」が使われます。
plistやテクスチャアトラスを作るにはいくつかのツールがあるようですが、FreeのものとしてはShoeboxというものがあります。
(Cocos2d-x向けに書きだす設定もあります。こちらの記事に書いてありました)

コード例

SpriteFrameCache::getInstance()->addSpriteFramesWithFile("hoge.plist");

addSpriteFramesWithFileメソッドに、plistファイル名を渡すだけで自動的にフレームキャッシュを生成してくれます。

テクスチャアトラスから切り出してスプライトを生成するには以下のようにします。

auto sprite = Sprite::createWithSpriteFrameName("hoge.png"); // ここで指定するファイル名は、テクスチャアトラスに変換する前のファイル名(plistに設定が保存されている)


これはゲーム内で実際に使っているテクスチャアトラスのサンプルです。

Sprite Sheet Animation

SpriteFrameCacheを使うことで、テクスチャアトラスから簡単にスプライトを生成できました。
こうしたテクスチャアトラスのもうひとつのメリットとしては、スプライトアニメーションを手軽に利用できる点もあります。

以下のように各フレームのテクスチャを配列に格納し、Animationクラス、Animateクラスを利用することで簡単にスプライトアニメーションを実現することができます。

// SpriteFrameCacheにplistを登録する
SpriteFrameCache::getInstance()->addSpriteFrameWithFile("sprites.plist");

// 配列に各フレームを格納
cocos2d::Vector<cocos2d::SpriteFrame*> animFrames(10);
char str[100] = {0};

// 各フレームを、plistに登録されているファイル名を元に生成
for (int i = 1; i <= 10; i++) {
    sprintf(str, "sprite_%02d.png", i);
    auto frame = cocos2d::SpriteFrameCache::getInstance()->getSpriteFrameByName(str);
    animFrames.pushBack(frame);
}

// 上記フレーム配列からアニメーションを生成
auto animation = cocos2d::Animation::createWithSpriteFrames(animFrames, 0.1f);
auto animate   = cocos2d::Animate::create(animation);

// 無限ループのアクションを生成
auto repeat    = cocos2d::RepeatForever::create(animate);

// スプライトを生成してアクションを実行(空のスプライトでも大丈夫)
auto sprite    = cocos2d::Sprite::createWithSpriteFrame(animFrames.front());
sprite->setPosition(cocos2d::Vec2(100, 100));
sprite->runAction(repeat);
this->addChild(sprite);

上記はCocos2d-x(Sprite Sheet Animation)のサイトを参考にしました。

________________________________

cocos2d::SpriteBatchNodeを使って高速描画

背景など、ひとつの画像を使いまわして繰り返し敷き詰める、というのはよくあると思います。
しかし、cocos2d::Spriteを複数生成して描画するとコストが高くなります。
(1スプライトに1draw callがかかる)

そこで、cocos2d::SpreiteBatchNodeの出番です。
これは、生成時に渡したテクスチャの情報を複数複製しても、draw callが1回で済む、というもの。
イメージとしては複数の細かいテクスチャを巨大なテクスチャひとつにまとめてくれるものです。
以下のように利用します。

cocos2d::SpriteBatchNode *node = SpriteBatchNde::create("filename.png");
this->addChild(node);

// 10 x 10のタイル上にスプライトを並べる
for (int i = 0; i < 100; i++) {
    cocos2d::Sprite *tile = Sprite::createWithTexture(node->getTexture());
    int col = i % 10;
    int row = i / 10;
    int x = 10 * col;
    int y = 10 * row;
    tile->setPosition(x, y);
    node->addChild(tile);
}

比較的大きな画像を100,000個生成してiPhone5で試したところ、通常のスプライトだと5FPSだったのがSpriteBatchNodeにしたら15FPSくらいまでになったので、だいぶ高速化しているのが分かります。

________________________________

cocos2d::Spriteクラスを継承する

cocos2d::Spriteクラスを継承する際、コンストラクタ経由で初期化はしないようです。(そもそも、こうした継承はありなのか分かりませんが・・)
Objective-Cのように、initメソッドとコンビニメソッドを利用して初期化するのが作法のようです。

実際のコードは以下

HogeSprite* HogeSprite::create()
{
    HogeSprite *pRet = new HogeSprite();
    if (pRet && pRet->init()) {
        pRet->autorelease();
        // 他に必要な初期化処理
    }
    else {
        delete pRet;
        pRet = NULL;
        return NULL;
    }
}

上記はCREATE_FUNCマクロがやってくれることを書きだしたものです。

余談

ちなみにinitメソッド自体をオーバーライドして拡張する場合は以下のようにすればOKかも?

class HogeSprite : public cocos2d::Sprite
{
    typedef cocos2d::Sprite inherited;
    public:
        bool init();
}

/////////////////////////////////

bool HogeSprite::init()
{
    if (inherited::init()) {
        // 初期化処理
        return true;
    }

    return false;
}

________________________________

cocos2d::Directorシングルトンクラス

画面サイズなどの様々な状態を管理するクラス。
ヘッダを読むと、OpenGLのcontextの初期化なども担っている様子。
インスタンスはgetInstanceメソッドを利用して取得します。

cocos2d::Director::getInstance()->methodName();

ゲームを一時停止(pause)/再開(resume)する

ゲームを一時停止したり、ということはよくある欲求だと思います。
その場合、手軽に全体の更新を止めるにはcocos2d::Directorクラスのメソッドで実現できます。

// 一時停止
cocos2d::Director::getInstance()->pause();

// 一時停止を再開
cocos2d::Director::getInstance()->resume();

________________________________

イベントを設定する

タッチイベントをスプライトに設定する

下記コードはこちらの記事を参考にさせて頂きました。

onTouchBeganで透明度を変更

タッチを開始したときに、対象オブジェクトの透明度を下げるサンプルです。
また、ヒットテストを行い、対象のスプライトをタッチしていた場合に限りtrueを返します。
こうすることで、対象スプライト以外がタッチされていた場合にその後のイベント(onTouchMovedなど)が実行されなくなります。

using namespace cocos2d;

// シングルタッチイベントリスナーを作成
auto listener1 = EventListenerTouchOneByOne::create();
listener1->setSwallowTouches(true);

// onTouchBeganのイベントコールバック(ラムダ式)
listener1->onTouchBegan = [](Touch *touch, Event *event) {
    auto target = static_cast<Sprite *>(event->getCurrentTarget());

    // ボタンに対する相対的な位置を取得する
    // `convertToNodeSpace`で、対象ノードの座標系での位置に変換
    Point locationInNode = target->convertToNodeSpace(touch->getLocation());

    // コンテンツのサイズ
    Size s = target->getContentSize();

    // ヒットテスト用コンテンツ矩形を作成
    Rect rect = Rect(0, 0, s.width, s.height);

    // 生成したヒットテスト用矩形内にタッチポイントがあるかチェック
    if (rect.containsPoint(locationInNode)) {
        log("Touch Began at x: %f, y: %f", locationInNode.x, locationInNode.y);
        return true;
    }

    return false;
};

// `sprite`ノードに、イベントリスナーを設定
aNode->getEventDispatcher->addEventListenerWithSceneGraphPriority(listener1, sprite);

タッチ位置の判定について

ここの処理で覚えておきたいのはタッチ位置の変換処理の部分。
具体的に言うと以下。

Point locationInNode = target->convertToNodeSpace(touch->getLocation());

// --- 中略 ---

if (rect.containsPoint(locationInNode)) {
    // hit!
}

target(aNode->getEventDispatcherで登録した対象のノード)の座標空間に変換するために、convertToNodeSpaceメソッドを実行して、タッチ位置を変換しています。

そしてヒットテスト用の矩形を作成し、変換後の位置(要は原点がヒットテスト用矩形と同じになる)が含まれているかを確認しています。
もし含まれていたらtrueを返し、ヒットしたことを知らせます。

ちなみに「左下」が原点になるようです。
原点についてはあまり直感的な位置ではないので注意が必要ですね。

onTouchMovedで要素を移動してみる

さて、上記処理でスプライトをタッチした場合にさらにドラッグでスプライトを移動できるようにしてみます。

listener1->onTouchMoved = [](Touch *touch, Event *event) {
    auto target = static_cast<Sprite*>(event->getCurrentTarget());
    auto delta  = touch->getDelta();
    Vec2 currentPos = target->getPosition();
    currentPos += delta;
    target->setPosition(currentPos);
};

onTouchBeganと同様にターゲットを取得します。
そしてtouchオブジェクトのgetDeltaメソッドで、前回との差分位置を取得し、ターゲットの現在の位置に足し込みます。
そしてそれを再セットすれば、ドラッグ処理の完成です。

クラスのメソッドをコールバックに指定する

CC_CALLBACK_#というマクロを使って、簡単に(ラムダ式ではなく)メソッドをコールバックに指定することができます。

ちなみに#の部分は0〜3の数字が入り、コールバックが受け取る引数の数によって使い分けるようです。
(コールバック自体の引数が1つの場合はCC_CALLBACK_1を使う)

ちなみにStack overflowで説明されているのを見つけました → What is the difference between all the CC_CALLBACK_# macros?

今回作ったゲーム内でのサンプルですが、以下のように指定します。

// Touch event listenerのインスタンスを生成
auto *touchListener = EventListenerTouchOneByOne::create();

// onTouchBeganにメソッドを指定(onTouchBeganの引数は2個なので`CC_CALLBACK_2`を使う)
touchListener->onTouchBegan = CC_CALLBACK_2(GameLayer::onTouchBegan, this);

________________________________

タイマー(schedule)の使い方

Cocos2d-xではいわゆるタイマー処理はscheduleという機能を使って実現します。
具体的には以下のようにします。

this->schedule(schedule_selector(Hoge::method), 5.0f);

ここでのthisはLayerクラスを継承したクラスです。
schedule_selectorはマクロになっていて実際にはstatic_cast<cocos2d::SEL_SCHEDULE>(&_SELECTOR)と変換されるようです。

第二引数はインターバルで、ここで指定した秒数間隔で渡した関数が繰り返し実行される仕組みになっています。

一度だけ実行するタイマーを設定する「scheduleOnce」

scheduleを使うとインターバルに設定した時間間隔でずっと処理を実行してくれます。
定期的に実行したい場合には便利ですが、一定時間後に一度だけなにかを実行したい、という欲求はゲームではよくあります。
その場合はscheduleではなくscheduleOnceメソッドを使います。

使い方はscheduleとほぼ同じで、インターバルの代わりに実行を遅延させたい時間を指定します。

this->scheduleOnce(schedule_selector(Hoge::method), 3.0f);

一度だけ実行するタイマーを設定する「ラムダ式版」

DelayTimeとCallFuncを使います。
スプライトにはアクションを実行するrunActionメソッドがあります。
それに、いくつかのクラスを併用することでラムダ式で一定時間後の処理を書くことが出来ます。

利用するクラスは以下の3つ。

クラス説明
cocos2d::SequenceSequenceは引数に渡されたFiniteTimeActionクラスのオブジェクトを「順次」実行していく
cocos2d::DelayTime引数に渡された秒数分、処理を遅延させる
cocos2d::CallFunc引数に渡されたラムダ式関数をFiniteTimeActionとして実行する

実際にこれを使った例が以下です。

this->runAction(cocos2d::Sequence::create(cocos2d::DelayTime::create(2), cocos2d::CallFunc::create([this]() {
    this->hogeMethod();
}), NULL));

フレーム単位での繰り返し処理

ゲームを作る上で欠かせないループ処理。
Cocos2d-xでは、scheduleUpdateメソッドを実行するとループが開始されるようになっています。
上記メソッドを実行すると毎フレームにupdateメソッドを実行しようとします。

そのため、Layerクラスを継承したクラスは、updateメソッドを実装しておかないとなりません。
Objective-Cのdelegateをイメージすると分かりやすいと思います。

// 定義
class HogeLayer : public cocos2d::Layer
{
private:
    void udpate(float frame);
}

// 実装
// this->scheduleUpdate()を実行するとフレーム単位で以下が自動的に呼ばれるようになる
void HogeLayer::update(float frame
{
    log("frame is %f", frame);
}

cocos2d::Sequenceを使って繰り返し処理

メソッドとして定義するのではなく、ラムダ式でさくっと繰り返し処理を書きたい場合があるかと思います。
そんなときは、SequenceとRepeat、DelayTimeを使って細かい繰り返し処理を書くことが出来ます。

cocos2d::FiniteTimeAction *repeatAction = cocos2d::Sequence::create(cocos2d::CallFunc::create([]() {
    int x = arc4random() % 5;
    int y = arc4random() % 5;
    auto pos = Vec2(x, y);
    this->setPosition(pos);
}), cocos2d::DelayTime::create(0.1f), NULL);

this->runAction(Repeat::create(repeatAction, 10));

上記サンプルはランダムに生成したx、yの位置に0.1秒間隔で10回処理を実行するものです。
例えばブルブル震えるようなアニメーションなどに使えますね。
ちなみにRepeat::createの第二引数は繰り返す回数ですが、-1を指定することで 無限回のループ を作ることもできます。

________________________________

音を再生する

やはり、ゲームの出来を左右するのは音と言っても過言ではありません。
効果音が入るだけでぐっとゲームらしくなるし、ぜひとも入れたい要素のひとつです。
Cocos2d-xでは音も手軽に利用できるようになっています。

BGMを再生する

ゲームに音楽は欠かせません。
BGMはCocosDenshion::SimpleAudioEngineを使い、以下のようにすることで簡単に再生できます。

// BGMのボリュームを1.0に設定(設定値は0.0〜1.0の間)
CocosDenshion::SimpleAudioEngine::getInstance()->setBackgroundMusicVolume(1.0);

// BGMを再生する
CocosDenshion::SimpleAudioEngine::getInstance()->playBackgroundMusic("hoge.mp3", true);

SE(効果音)を再生する

SE (Sound Effect) も大事な要素です。
BGMよりも利用する機会が多いでしょう。
BGMと同様、CocosDenshion::SimpleAudioEngineを使って以下のようにします。

// preload
CocosDenshion::SimpleAudioEngine::getInstance()->preloadEffect("se.mp3");

// SEを再生
CocosDenshion::SimpleAudioEngine::getInstance()->playEffect("se.mp3");

その他、制御メソッド

メソッド/変数名意味
rewindBackgroundMusic()BGMを先頭から再生
pauseBackgroundMusic()BGMの再生を一時停止
resumeBackgroundMusic()BGMを再開
isBackgroundMusicPlayingBGM再生中か
stopEffect(soundID)再生時に取得したint型のIDを指定してSEを止める
pauseEffect(soundID)IDを指定して一時停止
resumeEffect(soundID)IDを指定して再生を再開

ちなみにDenshionはなんかの英単語かと思ったら「電子音」という意味でした( ;´Д`)

________________________________

物理エンジン

※ Cocos2d-xから、物理エンジンは「Chipmunk2D」が標準となったようです。

やはりゲームと言ったら物理エンジンは重要な要素です。
物理エンジンは様々ありますが、基本的な概念はどれも同じです。
剛体と呼ばれる、衝突判定を持つオブジェクト、重力を表す「世界」、そして物体同士がぶつかったか判定するContact(Collision)などです。

(ちなみに過去に物理エンジンの仕組みを理解するため、本を読みながら物理エンジンを自作した際にまとめた記事があるので、興味がある方はこちら(自作2D物理エンジンを作った話)を参照ください)

大まかなセットアップの流れは以下になります。

物理演算される世界を作る
剛体の元となるスプライトを作成する
物理演算の対象となる物体を定義する(PhysicsBody)
物体の重さや形状などを設定
(3)をスプライトに設定(sprite->setPhysicsBody())

物理演算される世界を作る

実際のところ、物理演算世界の生成は簡単です。
以下のように、Sceneの生成メソッドを変えるだけです。

auto scene = cocos2d::Scene::createWithPhysics();
auto world = scene->getPhysicsWorld(); // 物理演算する「世界」の取得
auto gravity = cocos2d::Vec2(0, -50);  // 下方向に向かって重力を設定する
world->setGravity(gravity);
world->setSpeed(6.0f); 

余談

ちなみに、以下のようにすることでデバッグモードでレンダリングされるようになります。
デバッグモードでは、普段は可視化されない物理演算の対象となる形を表示してくれます。

world->setDebugDrawMask(PhysicsWorld::DEBUGDRAW_ALL);

衝突判定(BitMask)

物理エンジンには一般的に、衝突判定にBitMaskを用います。
Cocos2d-xも同様で、以下のように3つのBitMaskが用意されています。

Bit mask type意味
CategoryBitMaskカテゴリ
ContactBitMask接触判定
CollisionBitMask衝突判定

それぞれの意味は、Cocos2d-xのwikiから引用すると、

There are three values: CategoryBitmask, ContactTestBitmask and CollisionBitmask. you can use corresponding get/set method to get/set them. They are tested by logical and operation. When CategoryBitmask of one body and with ContactTestBitmask of another body with the result doesn't equal to zero, the contact event will be sended, overwise the contact event won't be sended. When CategoryBitmask of one body and with CollisionBitmask of another body with the result doesn't equal to zero, they will collied, overwise it won't. be ware, in default, CategoryBitmask value is 0xFFFFFFFF, ContactTestBitmask value is 0x00000000, and CollisionBitmask value is 0xFFFFFFFF, it means all body will collide with each other but with out send contact event by default.

と記載があります。
それぞれのデフォルト値は

Bit mask typeデフォルト値
CategoryBitmask0xFFFFFFFF
ContactTestBitmask0x00000000
CollisionBitmask0xFFFFFFFF

になります。

基本的にCategoryBitMaskとContactTestBitmaskか、CollisionBitmaskが評価されます。つまり、

カテゴリ x コンタクト
カテゴリ x コリジョン

のふたつです。

そしてそれぞれのBitmaskは理論積として計算され、結果がゼロ以外の場合に検出対象となります。

ContactとCollisionの違い

Contactは接触、Collisionは衝突を意味する単語です。
なんとなくどちらも同じような感じに思えますが、前者は「接触したかどうか」の判定に対し、後者は「衝突するか」の判定です。
つまり、接触したものの衝突を行わない場合は、接触したことを検知しつつも跳ね返ったりしない、といったことができます。

上記の具体的な例で言えば、とあるエリアにオブジェクトが侵入したか、といった判定を行いたい場合に有効です。
その場合、とあるエリアを表す範囲を設定し、接触判定のみ有効になるようにします。
すると接触判定された場合にイベントが発生しますが、衝突処理はされないため、とあるエリアにオブジェクトが侵入した、というイベントだけを受け取ることができるわけです。

衝突した際にイベントを受け取る

タッチイベントなどと同様、物理エンジンによって衝突が検知された際にもイベントを受け取って処理を行うことができます。
衝突イベントはいくつか種類があり、衝突開始時、衝突中、衝突の解消前、衝突の解消後(跳ね返りなどが計算されたあと)の4つが用意されています。

最初の衝突開始時など、boolを返すイベントは、falseを返すことでその後のフェーズのイベントをスキップさせることができます。また、衝突開始時のコールバック内でfalseを返すと、衝突そのものを無効にすることができます。

衝突を検知したが、特定の条件下では衝突をなかったことにしたい、などという場合に利用できます。

実装コード

// Listenerインスタンスを生成する
auto contactListener = EventListenerPhysicsContact::create();

// `onContactBegin`イベントにメソッドを設定
contactListener->onContactBegin = CC_CALLBACK_1(GameLayer::onContactBegin, this);

________________________________

CCControl系を使用する

こちらの記事(cocos2d-xのGUIを使う)を参考にさせて頂きました。
どうやら、CCControl系のクラスを利用する場合は、Extensionを別途追加する必要があるようです。

Extensionは、ダウンロードしてきたCocos2d-xのディレクトリの中にextensionsというディレクトリがあるので、その中から必要なものをコピーします。
同じディレクトリ内にあるcocos-ext.hを読み込ませることでExtensionが利用できるようになります。

ControlSliderを使う

以下のように生成し、イベントを登録します。

auto aSlider = cocos2d::ControlSlider::create("slider-background.png", "slider-progress.png", "slider-thumbnail.png");
aSlider->addTargetWithActionForControlEvents(this, cccontrol_selector(aClass::method), cocos2d::extension::Control::EventType::VALUE_CHANGED);

createメソッドに指定している3つの画像はそれぞれ、「スライダーの背景」「スライダーを伸ばした際のゲージ」「スライダーのボタン」です。
今回作成したゲームで実際に使っているのは以下の画像です。


スライダー背景


スライダープログレス


スライダーボタン

MotionStreakクラスでエフェクトを作る

cocos2d::MotionStreakクラスを使うと、剣の軌跡のようなエフェクトを表現できます。
(今回作ったゲームには採用していませんが・・)

static int streak_tag   = 1000;
static int streak_layer = 10000;
auto listener = cocos2d::EventListenerTouchOneByOne::create();
listener->onTouchBegan = [this](Touch *touch, Event *event) -> bool {
    this->removeChildByTag(streak_tag);

    auto streak = cocos2d::MotionStreak::create(0.8f, 1, 20, Color3B(200, 200, 250), "streak_texture.png");
    streak->setBlendFunc(BlendFunc::ADDITIVE);
    streak->setPosition(300, 300);
    this->addChild(streak, streak_layer, streak_tag);
    return true;
};
listener->onTouchMoved = [=](Touch *touch, Event *event) {
    auto streak = (cocos2d::MotionStreak*)this->getChildByTag(streak_tag);
    streak->setPosition(touch->getLocation());
};
this->getEventDispatcher()->addEventListenerWithSceneGraphPriority(listener, this);