2014年02月26日

[Cocos2d-x] CCMenuItemをタッチしてもスクロールできるようにしたい

以下のソースには不具合があります。
修正したソースは以下の記事から参照できます。
tappli blog: [Cocos2d-x] CCMenuItemをタッチしてもスクロールできるようにしたい(やり直し)


CCScrollViewとCCMenuを組み合わせて利用した時に、最初のタッチ位置がCCMenuItemだとスクロールさせることが出来ません。
CCMenuItemがタッチイベントを奪ってしまって、CCScrollViewまで届いていないようです。
解決策がないかと検索してみたところ、以下の記事が見つかりました。
というよりもこれくらいしか見つけられませんでした。
あまりCCScrollViewとCCMenuを組み合わせて使うような使い方はされていないのかも知れません。

Make Ccscrollview Work With Ccmenuitemimage - iOS Coder Talk

全てのコードは載っていないのですが、やっていることは単純でした。

  • CCMenuItemより先にCCScrollViewがタッチイベントを取得するようにする

  • ccTouchEnded時にタッチの移動距離を計算し、一定以下ならCCMenuにタッチイベントを渡す

  • CCMenu側では指定された範囲外だったら無視する


という感じ。

足りないコードを補完して、この実装をしてみると一つ問題がありました。
スクロールはできるし、ボタン押下のイベントも来るのですが、ボタンの見た目が変化しないのです。
それはそのはずで、CCScrollViewExのccTouchEndedでCCMenuのccTouchBeganとccTouchEndedを両方処理しているのです。
これではダメだと書き換えたのが以下のコードです。

.h


class CCMenuEx : public cocos2d::CCMenu {
public:
CREATE_FUNC(CCMenuEx);
virtual bool ccTouchBegan(cocos2d::CCTouch *touch, cocos2d::CCEvent *event);
void setValidTouchRectInWorldSpace(cocos2d::CCRect rect) { this->validTouchRectInWorldSpace = rect; };
private:
cocos2d::CCRect validTouchRectInWorldSpace;
};

class CCScrollViewEx : public cocos2d::extension::CCScrollView {
public:
virtual void registerWithTouchDispatcher();
virtual bool ccTouchBegan(cocos2d::CCTouch *pTouch, cocos2d::CCEvent *pEvent);
virtual void ccTouchMoved(cocos2d::CCTouch *pTouch, cocos2d::CCEvent *pEvent);
virtual void ccTouchEnded(cocos2d::CCTouch *pTouch, cocos2d::CCEvent *pEvent);
virtual void ccTouchCancelled(cocos2d::CCTouch *pTouch, cocos2d::CCEvent *pEvent);
CREATE_FUNC(CCScrollViewEx);
void setMenu(cocos2d::CCMenu *menu);

enum {
kCCScrollViewExPriority = (cocos2d::kCCMenuHandlerPriority - 1)
};
protected:
cocos2d::CCPoint pressPoint;
cocos2d::CCMenu *menu;
bool waitingTouchEnd;
};

.cpp


bool CCMenuEx::ccTouchBegan(CCTouch *touch, CCEvent *event) {
if (!validTouchRectInWorldSpace.size.width || !validTouchRectInWorldSpace.size.height) {
return CCMenu::ccTouchBegan(touch, event);
}

CCPoint touchLocation = touch->getLocation();
if (!validTouchRectInWorldSpace.containsPoint(touchLocation)) {
return false;
}

return CCMenu::ccTouchBegan(touch, event);
}

void CCScrollViewEx::registerWithTouchDispatcher() {
CCTouchDispatcher *dispatcher = CCDirector::sharedDirector()->getTouchDispatcher();
dispatcher->addTargetedDelegate(this, kCCScrollViewExPriority, true);
}

bool CCScrollViewEx::ccTouchBegan(CCTouch *pTouch, CCEvent *pEvent) {
pressPoint = pTouch->getLocationInView();
if (menu) {
waitingTouchEnd = menu->ccTouchBegan(pTouch, pEvent);
}
return CCScrollView::ccTouchBegan(pTouch, pEvent);
}

void CCScrollViewEx::ccTouchMoved(CCTouch *pTouch, CCEvent *pEvent) {
if (waitingTouchEnd) {
const float MIN_DISTANCE = 10;
CCPoint endPoint = pTouch->getLocationInView();
float distance = sqrtf((endPoint.x - pressPoint.x) * (endPoint.x - pressPoint.x) + (endPoint.y - pressPoint.y) * (endPoint.y - pressPoint.y));

if(distance < MIN_DISTANCE) {
if (menu) {
menu->ccTouchMoved(pTouch, pEvent);
}
} else {
if (menu) {
menu->ccTouchCancelled(pTouch, pEvent);
waitingTouchEnd = false;
}
}
}
CCScrollView::ccTouchMoved(pTouch, pEvent);
}

void CCScrollViewEx::ccTouchEnded(CCTouch *pTouch, CCEvent *pEvent)
{
if (waitingTouchEnd) {
const float MIN_DISTANCE = 10;
CCPoint endPoint = pTouch->getLocationInView();
float distance = sqrtf((endPoint.x - pressPoint.x) * (endPoint.x - pressPoint.x) + (endPoint.y - pressPoint.y) * (endPoint.y - pressPoint.y));

if(distance < MIN_DISTANCE) {
if (menu) {
menu->ccTouchEnded(pTouch, pEvent);
}
} else {
if (menu) {
menu->ccTouchCancelled(pTouch, pEvent);
waitingTouchEnd = false;
}
}
}
CCScrollView::ccTouchEnded(pTouch, pEvent);
}

void CCScrollViewEx::ccTouchCancelled(CCTouch *pTouch, CCEvent *pEvent) {
if (menu && waitingTouchEnd) {
menu->ccTouchCancelled(pTouch, pEvent);
waitingTouchEnd = false;
}
CCScrollView::ccTouchCancelled(pTouch, pEvent);
}

void CCScrollViewEx::setMenu(CCMenu *menu) {
this->menu = menu;
CCMenuEx *ex = dynamic_cast<CCMenuEx *>(menu);
if (ex) {
ex->setValidTouchRectInWorldSpace(getViewRect());
}
}


ccTouchMovedとccTouchEndedにコピペコードがあるのはご愛嬌。
変更したのは、以下のとおり。

  • CCMenuにはccTouchBeganでイベントを流す

  • タッチ位置が一定以上移動したらCCMenuのタッチイベントはキャンセル(ccTouchCancelled)する

  • setMenu()にCCMenuExが渡されたら、CCScrollViewExの表示サイズをタッチ可能領域とする


期待通りの動作はしているので、これで良しとします。

確認はしていないですが、Cocos2d-x 3.0ではタッチイベント周りがかなり改善されているようなので、こんなことしなくても良いかも知れません。
3.0はまだβ版のようなので、まだ必要としている人がいるかも、と記事にしてみました。


posted by t2low at 22:00| Cocos2d-x