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

2014年02月19日

[Cocos2d-x] std::vectorの中身の保存

CCHttpClientを使って取得したデータをファイルに保存するコードを書いていました。

std::vector<char> *data = response->getResponseData();

std::string path = CCFileUtils::sharedFileUtils()->getWritablePath() + "hoge.txt";
FILE *fp = fopen(path.c_str(), "w");
size_t count = fwrite(&data[0], sizeof(char), data->size(), fp);
fclose(fp);

こんなコードを書いたのですが、ファイルの内容は正しくありませんでした。
しかし、fwrite()の戻り値はdata->size()と一致するのです。
しばらく悩んで気付きました。
size_t count = fwrite(&(*data)[0], sizeof(char), data->size(), fp);

こうですね。
そりゃあ変なデータが書き込まれるわけですね。

posted by t2low at 22:00| Cocos2d-x

2014年02月18日

[Cocos2d-x] OS毎に処理を分ける

「Cocos2d-x 開発のレシピ」にOS毎に処理を分ける方法が載っていました。

マクロで分岐させる方法と
#if (CC_TARGET_PLATFORM == CC_PLATFORM_IOS)
...
#elif (CC_TARGET_PLATFORM ==CC_PLATFORM_ANDROID)
...
#endif

実行時に分岐させる方法
TargetPlatform platform = CCApplication::sharedApplication()->getTargetPlatform();
if (platform == kTargetIphone || platform == kTargetIpad) {
...
} else if (platform == kTargetAndroid) {
...
}

使い分けとしては、関数でできるなら関数、ヘッダで使うとき等はマクロで、とのことだけど、実行時に分岐させたいことってあるのかな?
すぐには思いつかないな…。
可読性が落ちるというのはわかる。




posted by t2low at 22:00| Cocos2d-x

2014年02月17日

[Cocos2d-x] C++で忘れがちなこと

Javaをやってた時間が長いので、よく忘れてしまうのです。
staticなメンバ変数の使い方。

Hoge.h
class Hoge {
public:
static Hoge *getInstance();
private:
Hoge() {};
static Hoge *instance;
};

Hoge.cpp
Hoge *Hoge::getInstance() {
if (!instance) {
instance = new Hoge();
}
return instance;
}

こんなコードを書くとエラーになりますよね。
Hoge.cppに
Hoge *Hoge::instance;

の1行が必要です。
エラーが出てstaticなメンバ変数が原因だということがわかっても、毎度のようにしばらく解決できないことがあるので、今度こそ忘れないようにとメモしておきます。
posted by t2low at 22:00| Cocos2d-x

2014年02月12日

[Cocos2d-x] 階層化されたCCLayerの内側からモーダルレイヤを画面の中心に表示する(改)

以前、以下のような記事を書きました。
tappli blog: [Cocos2d-x] 階層化されたCCLayerの内側からモーダルレイヤを画面の中心に表示する
親階層との位置を計算してモーダルレイヤを中心に表示するというものでした。
今日ふと気づいたのですが、そんな面倒な計算をしなくても親階層に追加(addChild)すれば良いだけだったのでは…。
CCNode *parent = this;
while (parent->getParent()) {
parent = parent->getParent();
}
parent->addChild(modal);

こんな感じ。
問題なさそう。

posted by t2low at 22:00| Cocos2d-x

2014年02月05日

[Cocos2d-x] Classesの中を整理する

Cocos2d-xの開発をEclipseで行うときの設定です。
Xcodeだとこの辺はあまり気にしなくても良い感じになってますね。

Cocos2d-xでプロジェクトを作ると、Classesに4つのファイルができます。
Classes/
├AppDelegate.cpp
├AppDelegate.h
├HelloWorldScene.cpp
└HelloWorldScene.h

最初のうちは良いのですが、ファイルが増えてくるとわかりにくくなります。
↓こんな感じ。わかりにくいですね。
Classes/
├AppDelegate.cpp
├AppDelegate.h
├Fuga.cpp
├Fuga.h
├HelloWorldScene.cpp
├HelloWorldScene.h
├Hoge.cpp
├Hoge.h
├Piyo.cpp
└Piyo.h

こうなってしまう前にフォルダにまとめたいですね。
Classes/
├AppDelegate.cpp
├AppDelegate.h
├HelloWorldScene.cpp
├HelloWorldScene.h
└HogeHoge/
 ├Fuga.cpp
 ├Fuga.h
 ├Hoge.cpp
 ├Hoge.h
 ├Piyo.cpp
 └Piyo.h

しかし、ファイルの場所を変えてしまうとビルドできなくなってしまいます。
ファイルが参照できるようにしましょう。
// before
#include "Hoge.h"
// ↓
// after
#include "HogeHoge/Hoge.h"

としても参照できますが、一つ一つ変更するのはめんどくさいです。
Makefile(Android.mk)の中にHogeHogeからもincludeできるように指定するのが良い気がします。
↓このようにしておけば、#include "Hoge.h"のまま参照可能です。
LOCAL_C_INCLUDES := $(LOCAL_PATH)/../../Classes \
$(LOCAL_PATH)/../../Classes/HogeHoge/

ソースファイルの場所も修正しておきましょう。
LOCAL_SRC_FILES := hellocpp/main.cpp \
../../Classes/AppDelegate.cpp \
../../Classes/HelloWorldScene.cpp \
../../Classes/HogeHoge/Hoge.cpp \
../../Classes/HogeHoge/Fuga.cpp \
../../Classes/HogeHoge/Piyo.cpp

これでビルドできるようになるはずです。
ソースをグループ化できるのでわかりやすくなりますね。
posted by t2low at 22:00| Cocos2d-x

2014年02月03日

[Android] アプリの背景に設定されている壁紙(ライブ壁紙)を使う

アプリの背景に端末に設定された壁紙を利用したくて、以前こんな記事を書きました。

tappli blog: [Android] 壁紙を取得する

この方法で(静止画の)壁紙を取得することができるのですが、ライブ壁紙は取得できません。
ライブ壁紙が設定されていても、静止画の壁紙が返ってくるのです。
WallpaperManager#getWallpaperInfo()を使えば、ライブ壁紙の情報やサムネイル画像は取得できるのですが、背景として使うための画像は取得できそうにありませんでした。

いつものように検索してみると…

【ご助言お願いします】ホームアプリ作成での、ライブ壁紙を壁紙に配置する方法について - Google グループ

いつもいつも先達の知識に助けられてばかりです。ありがたいことです。
上のリンク先にも書かれていますが、テーマを設定するだけっぽいですね。
android:theme="@android:style/Theme.Wallpaper"

これだけでアプリの背景が壁紙と同じものになります。

posted by t2low at 22:00| Android