過去アプリで実装した Sprite 関係の処理をまとめました。

親子関係

Cocos2d-xではNodeにaddChildするだけで簡単に親子関係を構築できます。

アプリのタップターゲットとなる札は、枠と文字の2つのSpriteを重ねて表示しています。

札のSprite構成
札のSprite構成

 1auto pFrame    = Sprite::create("img/frame.png");
 2Size frameSize =  pFrame->getContentSize();
 3this->addChild(pFrame);
 4
 5auto pChar  = CardSprite::create("img/char/E4BB8F.png");
 6pChar->setPosition(Vec2(
 7    frameSize.width  * 0.5,
 8    frameSize.height * 0.5
 9));
10pFrame->addChild(pChar);
11

透過・透明度

アプリでは、ダミーやタップできない位置のターゲットを半透明にすることで判別が付くようにしました。

実装はSpriteのOpacityを変更するだけです。
Sprite.setOpacity()引数は0 - 255の範囲で、数値が大きいほど色が濃くなります。

1// Spriteを半透明で表示
2auto pSprite = Sprite::create("img/image.png");
3p->setOpacity(128);
4

タップ判定

イベントリスナにタップイベントをdispachし、イベント発火を検知します。
アプリでは、タップされた場合に、どの札がタップされたかを判定して処理分けをしています。

  • GameScene.h
1public:
2    virtual bool onTouchBegan(cocos2d::Touch *pTouch, cocos2d::Event *pEvent);
3    virtual void onTouchEnded(cocos2d::Touch *pTouch, cocos2d::Event *pEvent);
4
  • GameScene.cpp
 1bool GameScene::init()
 2{
 3    // -- 省略 --
 4    // イベントリスナの処理
 5    auto dispacher     = Director::getInstance()->getEventDispatcher();
 6    auto eventListener = EventListenerTouchOneByOne::create();
 7
 8    eventListener->onTouchBegan = CC_CALLBACK_2(GameScene::onTouchBegan, this);
 9    eventListener->onTouchEnded = CC_CALLBACK_2(GameScene::onTouchEnded, this);
10
11    dispacher->addEventListenerWithSceneGraphPriority(eventListener, this);
12    
13    
14}
15
16void GameScene::onTouchEnded(Touch* pTouch, Event* pEvent)
17{
18    auto pSprite = this->getChildByTag(targetFrameTag);
19    if (pSprite == NULL)
20    {
21        return;
22    }
23
24    // タッチされたポイントとSpriteが重なるかを判定
25    Point touchPoint = Director::getInstance()->convertToGL(pTouch->getLocationInView());
26    Rect  spriteRect = pSprite->getBoundingBox();
27    if (spriteRect.containsPoint(touchPoint) == false)
28    {
29        return;
30    }
31    // 正答がタップされた場合の処理
32    
33    
34

子Spriteの操作

アプリでは正解をタップした場合にすべての札を手前に移動し、次の問題へと進みます。
子要素の操作はNodeに対してgetChildren()を行ってループにかければ良いです。
簡単ですね。

アプリの実装コードです。

1Sprite* s;
2auto rect = this->getChildByName("GameLayer");
3for (auto target : rect->getChildren()) {
4    s = (Sprite*)target;
5    s->runAction(MoveBy::create(0.015, Point(0, -1 * m_cardSize.height)));
6}
7

失敗談

「ターゲットを移動する」という実装で次のような躓きがありました。

つまずきポイント1、this->getChildren()

一番最初にthis->getChildren()に対して移動処理を行ったところ、ターゲットだけではなく、背景もデバッグ表示もすべて移動し 最終的にすべて画面からフェードアウトしてしまいました。

thisはつまりSceneですから、シーン上のすべての子要素に処理を行っていたのですね。

それはそうです。
cocosは悪くない。

つまずきポイント2、親要素を移動

上記の対応として、空のSpriteを作成しすべてのターゲットをこれの子要素 としました。

この際、Spriteをそれぞれ移動するよりも、親要素をの1つを移動する方がコスト的にもコード的にも良いという判断で、親要素に対して移動処理を行いました。
そのコードがコチラです。

1auto rect = this->getChildByName("GameLayer");
2rect->runAction(MoveBy::create(0.1f, Point(0, -1 * cardSize.height)) );
3

この実装の場合、画面表示はOKでしたが タップ判定の位置が元の位置にとどまってしまう という期待しない挙動となってしまいました。
Spriteを拡張した実装のせいかもしれませんが、原因がよく分からなかったため、子要素をすべて移動するという乱暴な手段を取りました。


単純なアプリにも苦労やドラマがあるんですよ。。。