【Cocos2d-x】Downloaderクラスを使って非同期でファイルをダウンロードする

•11月 12, 2014 • コメントする

cocos2d-x-3.3-rc0から、AssetsMangerExというファイルのバージョンをチェックして、アップデートされていたらファイルをダウンロードしてくるという機能のクラスが追加された。
そのクラスで利用されている、3.3rc0のリリース記事にすら通知されず、こっそり追加されていたDownloaderクラス。
これが想像以上に便利で、かつ簡単にファイルの非同期ダウンロードが実装できたのでメモ。
タイムアウトやエラー、終了のコールバックだけでなく、進行度のコールバックも受け取れるので、ダウンロード容量が多いときに進行度をグラフで見せたい時などにも簡単に対応できる。
試しにyahooのトップのロゴを表示してみたが、画像に限らずどんな形式でも可能な上、バイナリデータでのダウンロードも可能なのでいろいろと応用が利くと思われる。
以下、テストで実装してみたコード
.h



#include "cocos2d.h"
#include "Macros.h"
#include "cocos-ext.h"

class DownloadScene : public cocos2d::Layer
{
public:
    CREATE_FUNC(DownloadScene);
    
    static cocos2d::Scene* createScene();
    
    virtual bool init();
    
private:
    std::shared_ptr<cocos2d::extension::Downloader> _downloader;
    
    void onError(const cocos2d::extension::Downloader::Error &error);
    void onProgress(double total, double downloaded, const std::string &url, const std::string &customId);
    void onSuccess(const std::string &srcUrl, const std::string &storagePath, const std::string &customId);
    
};

cpp

#include "DownloadScene.h"
USING_NS_CC;


Scene* DownloadScene::createScene()
{
    auto scene = Scene::create();
    
    auto layer = DownloadScene::create();
    
    scene->addChild(layer);
    
    return scene;
    
}


bool DownloadScene::init()
{
    if (!Layer::init()) {
        return false;
    }
    
    _downloader = std::make_shared<extension::Downloader>();
    _downloader->setErrorCallback(CC_CALLBACK_1(DownloadScene::onError, this));
    _downloader->setProgressCallback(std::bind(&DownloadScene::onProgress,
                                         this,
                                         std::placeholders::_1,
                                         std::placeholders::_2,
                                         std::placeholders::_3,
                                         std::placeholders::_4));
    _downloader->setSuccessCallback(CC_CALLBACK_3(DownloadScene::onSuccess, this));
    _downloader->setConnectionTimeout(DOWNLOAD_TIMEOUT);
    
    _downloader->downloadAsync("http://k.yimg.jp/images/top/sp2/cmn/logo-ns-131205.png", FileUtils::getInstance()->getWritablePath() + "yahoo.png");
    return true;
    
}

void DownloadScene::onError(const cocos2d::extension::Downloader::Error &error)
{
    //Downloader専用のErrorCode
    CCLOG("%d", error.code);
    //CURLcodeの値
    CCLOG("%d", error.curle_code);
    //CURLMの値
    CCLOG("%d", error.curlm_code);
    //エラーメッセージ
    CCLOG("%s", error.message.c_str());
    
    
}

void DownloadScene::onProgress(double total, double downloaded, const std::string &url, const std::string &customId)
{
    //総ファイルサイズ
    CCLOG("total %lf", total);
    //ダウンロード済みサイズ
    CCLOG("downloaded %lf", downloaded);
    
}

void DownloadScene::onSuccess(const std::string &srcUrl, const std::string &storagePath, const std::string &customId)
{
    CCLOG("Success");
    
    auto yahoo = Sprite::create(FileUtils::getInstance()->getWritablePath() + "yahoo.png");
    yahoo->setPosition(Director::getInstance()->getVisibleSize() / 2);
    addChild(yahoo);
    
}

これで、非同期でのダウンロードを実装できる
一つ問題があるとすれば、そのままのエラーコードがとれないので、細かいエラー処理が難しいところかと
バイナリデータのポインタがほしい場合はdownloadToBufferAsyncを呼べば良い

【Xcode 6, iOS】An error was encountered while running (Domain = NSPOSIXErrorDomain, Code = 22)を解決する

•10月 31, 2014 • コメントする

Xcode6になってからiOSシミュレータが色々と挙動がおかしい(アプリを消してもデータが消えない等)が、
一番面倒なのがタイトルにある「An error was encountered while running (Domain = NSPOSIXErrorDomain, Code = 22)」というエラーだった
これが一度起きると、シミュレータを一度落としたり、Reset Content and Setting…したり、クリーンして再ビルドしたり、Xcode起動し直したり、OS再起動しても直らないという最悪の状態になる
ググっても特にこれという対処法が見つからず、1度目起こった際はいつの間にか直ったが、2度目が起こった際に解決できたのでメモ

まず以下にアクセスする
/Users/ユーザー名/Library/Developer/Xcode/DerivedData
その中に、「アプリ名-謎の文字列」みたいなフォルダがあるので、エラーが発生しているアプリ名のフォルダを削除し、Xcodeを再起動して、Runをすれば問題なく動いた
まだ一度しか解決していないので、確実に直るかわからないが、参考になれば

【Cocos2d-x】SimpleAudioEngineではなくAudioEngineを使う

•10月 28, 2014 • コメントする

Cocos2d-x3.3beta0から、サウンド再生用に新しくAudioEngineクラスが作られた
今までCocos2d-xで使っていたSimpleAudioEngineではできなかった、
再生時間の取得や終了時のコールバックなどが取れるようになったので、せっかくなので使ってみることにした
ただ、SimpleAudioEngineと違い、BGMとSEで分けられていないため、BGMを切り替える場合は前のBGMを止める必要ができたり、
音量等も個別に切り替える必要ができてきたため、そこら辺を管理するManagerクラスを作ってみた
以下コード

AudioManager.h


#include "cocos2d.h"

class AudioManager
{
public:
    enum BgmType
    {
        BGM_A = 0,
        None,
    };
    enum SeType
    {
        SE_A = 0,
    };
    
    static AudioManager* getInstance();
    AudioManager();
    void init();
    
    void playBgm(BgmType type, bool loop = true);
    void playSe(SeType type);
    
    CC_PROPERTY(float, _bgmVolume, BgmVolume);
    CC_PROPERTY(float, _seVolume, SeVolume);
    void setVolume(float volume);
    
    CC_PROPERTY(bool, _mute, Mute);
    
    void saveParams();
    
private:
    struct BgmData
    {
        BgmData()
        {
            bgmId = -1;
            type = None;
            playing = false;
        }
        int bgmId;
        BgmType type;
        bool playing;
    };
    
    std::string getBgmPath(BgmType type);
    std::string getSePath(SeType type);
    
    BgmData _bgmData;
    
    std::vector<int> _seList;
    
    float getMuteBgmVolume(){return _mute ? 0.0f : _bgmVolume;};
    float getMuteSeVolume(){return _mute ? 0.0f : _seVolume;};
    
    
    void finishCallBack(int audioID, std::string filePath);
    
    
};

AudioManager.cpp


#include "AudioManager.h"
#include "audio/include/AudioEngine.h"

USING_NS_CC;
using namespace experimental;

static AudioManager* _instance;

AudioManager* AudioManager::getInstance()
{
    if (!_instance) {
        _instance = new AudioManager();
        _instance->init();
    }
    return _instance;
}

AudioManager::AudioManager()
:_bgmData(BgmData())
, _seList(std::vector<int>())
, _bgmVolume(1.0)
, _seVolume(1.0)
{
    
}

void AudioManager::init()
{
    _mute = UserDefault::getInstance()->getBoolForKey("mute", false);
    _bgmVolume = UserDefault::getInstance()->getFloatForKey("bgm_volume", 1.0f);
    _seVolume = UserDefault::getInstance()->getFloatForKey("se_volume", 1.0f);
    
}

void AudioManager::playBgm(AudioManager::BgmType type, bool loop)
{
    if (!_bgmData.playing || _bgmData.type != type) {
        if (_bgmData.playing) {
            AudioEngine::stop(_bgmData.bgmId);
        }
        _bgmData.bgmId = AudioEngine::play2d(getBgmPath(type), loop, getMuteBgmVolume());
        _bgmData.type = type;
        _bgmData.playing = true;
        AudioEngine::setFinishCallback(_bgmData.bgmId, CC_CALLBACK_2(AudioManager::finishCallBack, this));
    }
    
}

void AudioManager::playSe(AudioManager::SeType type)
{
    int id = AudioEngine::play2d(getSePath(type), false, getMuteSeVolume());
    _seList.push_back(id);
    AudioEngine::setFinishCallback(id, CC_CALLBACK_2(AudioManager::finishCallBack, this));
    
    
}

void AudioManager::setBgmVolume(float var)
{
    _bgmVolume = var;
    if (_bgmData.playing) {
        AudioEngine::setVolume(_bgmData.bgmId, getMuteBgmVolume());
    }
}

float AudioManager::getBgmVolume()
{
    return _bgmVolume;
}

void AudioManager::setSeVolume(float var)
{
    _seVolume = var;
    for (auto id : _seList) {
        AudioEngine::setVolume(id, getMuteSeVolume());
    }
}

float AudioManager::getSeVolume()
{
    return _seVolume;
}

void AudioManager::setVolume(float volume)
{
    setBgmVolume(volume);
    setSeVolume(volume);
}

void AudioManager::setMute(bool mute)
{
    if (_mute != mute) {
        _mute = mute;
        
        setBgmVolume(getBgmVolume());
        setSeVolume(getSeVolume());
    }
    
}

bool AudioManager::getMute()
{
    return _mute;
}

std::string AudioManager::getBgmPath(AudioManager::BgmType type)
{
    std::string path;
    switch (type) {
        case BGM_A:
            path = "bgm.mp3";
            break;
            
        default:
            path = "";
            break;
    }
    return path;
}


std::string AudioManager::getSePath(AudioManager::SeType type)
{
    std::string path;
    switch (type) {
        case SE_A:
            path = "se.mp3";
            break;
            
        default:
            path = "";
            break;
    }
    return path;
}

void AudioManager::finishCallBack(int audioID, std::string filePath)
{
    if (audioID == _bgmData.bgmId) {
        if (!AudioEngine::isLoop(audioID)) {
            _bgmData.playing = false;
            _bgmData.type = None;
            _bgmData.bgmId = -1;
        }
    } else {
        auto it = std::find(_seList.begin(), _seList.end(), audioID);
        if (it != _seList.end()) {
            _seList.erase(it);
        }
        
    }
    
    
}


void AudioManager::saveParams()
{
    UserDefault::getInstance()->setBoolForKey("mute", _mute);
    UserDefault::getInstance()->setFloatForKey("bgm_volume", _bgmVolume);
    UserDefault::getInstance()->setFloatForKey("se_volume", _seVolume);
    
}

まだSEとかは実際に再生していないので不具合がある可能性あり
自分メモ用なので関数名がおかしかったり頂けない実装があったりするかもなので、まあ参考程度にしていただければ

(cocos2d-x)CocosBuilderを使うときにメモリリークを起こさないために最低限変えておきたい部分

•8月 8, 2014 • コメントする

Cocos Studioが出てかなり経ち、ついにmac版の1.0が出て、かつSpriteStudioなどの優秀なシーンエディタがある中、
今からCocosBuilderを使うという奇特な人はまずいないだろうが、ブログを更新しようとしてなにもネタがなかったのでとりあえず書く
一応SpriteBuilderをCocos2d-xで使えるようにしてくれた人がいるので、それを使うときに役に立つかもしれない

まずはCCBMemberVariableAssigner.h内のCCB_MEMBERVARIABLEASSIGNER_GLUEの中

#define CCB_MEMBERVARIABLEASSIGNER_GLUE(TARGET, MEMBERVARIABLENAME, MEMBERVARIABLETYPE, MEMBERVARIABLE) \
if (pTarget == TARGET && 0 == strcmp(pMemberVariableName, (MEMBERVARIABLENAME))) { \

MEMBERVARIABLETYPE pOldVar = MEMBERVARIABLE; \
MEMBERVARIABLE = dynamic_cast(pNode); \
CC_ASSERT(MEMBERVARIABLE); \
if (pOldVar != MEMBERVARIABLE) { \

CC_SAFE_RELEASE(pOldVar); \
MEMBERVARIABLE->retain(); \ ←ここ!!!!!!

} \
return true; \

}

なぜか取得してきたオブジェクトがretainされているため、デストラクタですべてreleaseを記述しなくてはいけなくなる
これを忘れるとかなり多くのメモリリークが発生するので注意が必要
まあCCB_MEMBERVARIABLEASSIGNER_GLUE_WEAKを使えば問題ないのだが
CCB_MEMBERVARIABLEASSIGNER_GLUEを使う場合はretainしている部分をコメントアウトした方が楽と思われる

次はCCBAnimationManager.cppのsetDelegateの中

void CCBAnimationManager::setDelegate(CCBAnimationManagerDelegate *pDelegate)
{

CC_SAFE_RELEASE(dynamic_cast<Ref*>(_delegate)); ←ここと
_delegate = pDelegate;
CC_SAFE_RETAIN(dynamic_cast<Ref*>(_delegate)); ←ここ

}

ここに関してもなぜかretainされているので、デストラクタ当たりでCCBAnimationManagerに対してsetDelegate(NULL)をしないとメモリリークが起きてしまう
それをするのも面倒なので、ちゃっちゃとコメントアウトしてしまうに限る

最後は間違った使い方をしていない限りあまり対応しなくてもいいが、念のため変えておいた方がいい
CCNodeLoaderLibrary.cppのregisterNodeLoaderの中

void NodeLoaderLibrary::registerNodeLoader(const char * pClassName, NodeLoader * pNodeLoader) {

pNodeLoader->retain(); ←ここ
this->_nodeLoaders.insert(NodeLoaderMapEntry(pClassName, pNodeLoader));

}

ここでretainするのは別にかまわないのだが、既に同じkeyのオブジェクトがあってinsertに失敗した場合メモリリークをしてしまうため
以下のように書き換えた方がいい

void NodeLoaderLibrary::registerNodeLoader(const char * pClassName, NodeLoader * pNodeLoader)

NodeLoaderMap::iterator ccNodeLoadersIterator = this->_nodeLoaders.find(pClassName);
if (ccNodeLoadersIterator == this->_nodeLoaders.end())
{

pNodeLoader->retain();
this->_nodeLoaders.insert(NodeLoaderMapEntry(pClassName, pNodeLoader));

}

}

unregisterNodeLoaderでは似たようなことをやっているのにここでやっていないのは謎である

(cocos2d-x)ScrollView(CCScrollView)の動きをiOSのUIScrollViewっぽくする

•12月 18, 2013 • 3件のコメント

最近cocoStudioが出て、cocos2d-x用のUIScrollViewやらUIListViewやらが出たので、今後使わなくなる可能性もあるが、現状まだScrollViewの方を多く使うのでメモ。

ScrollViewは初期状態のままだとiOSのUIKit.frameworkのUIScrollViewと違い、限界以上にスクロールを引っ張ったときのバウンスの挙動が現在タップしている地点まで移動してしまうので、ScrollViewの中を直接いじって限界以上に引っ張るほど移動しにくくなる用にしてUIScrollViewっぽくした。

ただ、減速度などの計算をするのが面倒だったので簡易的なものにしたので、きちんと実装したい人は減速度とかをちゃんと計算した方がいいと思います。
以下コード(CCScrollView.cppのonTouchMoved一部を書き換えています)


		newX     = _container->getPosition().x + moveDistance.x;
                newY     = _container->getPosition().y + moveDistance.y;

                //追加ここから
                const Point minOffset = this->minContainerOffset();
                const Point maxOffset = this->maxContainerOffset();

                int divisionNum = 20;
                float offsetWeight;

                float widthDiff = 0.0f;
                if (getContentSize().width < getViewSize().width) {
                    widthDiff = getContentSize().width - getViewSize().width;
                }
                if (minOffset.x > newX) {
                    offsetWeight = newX - minOffset.x;
                    offsetWeight /= divisionNum;
                    if (offsetWeight > -1)
                        offsetWeight = -1;
                    newX = newX - (moveDistance.x + (moveDistance.x / offsetWeight)) ;
                    moveDistance.x = 0.0f;
                } else if (maxOffset.x < newX){
                    offsetWeight = newX - maxOffset.x;
                    offsetWeight /= divisionNum;
                    if (offsetWeight < 1)
                        offsetWeight = 1;
                    newX = newX - (moveDistance.x - (moveDistance.x / offsetWeight)) ;
                    moveDistance.x = 0.0f;
                }

                if (minOffset.y > newY) {
                    offsetWeight = newY - minOffset.y;
                    offsetWeight /= divisionNum;
                    if (offsetWeight > -1)
                        offsetWeight = -1;
                    newY = newY - (moveDistance.y + (moveDistance.y / offsetWeight)) ;
                    moveDistance.y = 0.0f;
                } else if (maxOffset.y  < newY){

                    offsetWeight = newY - maxOffset.y;
                    offsetWeight /= divisionNum;
                    if (offsetWeight < 1)
                        offsetWeight = 1;
                    newY = newY - (moveDistance.y - (moveDistance.y / offsetWeight)) ;
                    moveDistance.y = 0.0f;
                }
                //ここまで
 

                _scrollDistance = moveDistance;
                this->setContentOffset(Point(newX, newY));


divisionNumの値を変えると減速の度合いを変えられるようになっています。
ただ、まだ縦の挙動しか確認してないので他のスクロールにするとバグるかもしれないです。

//2014/1/9 追記
2点修正しました。
ContainerのサイズがViewSize以下だと上に引っ張ったときの動きがおかしくなっていたところと、
小数点以下同士の割り算になっていた部分の修正をしました

//2014/8/7 追記2
コメントいただいていた部分(半年ぐらいブログを放置していたので今更ですが)の修正を行いました
ただ、cocos2d-x-3.0rc1以降では、既にバウンスの機能が実装されているので、
上記実装を行わずともCCScrollView.cppのBOUNCE_BACK_FACTORをいじってやれば
割と望み通りのバウンスの挙動ができると思います(戻るときの動きが少し気になりますが)

(cocos2d-x, C++)現在地から360度いずれかの方向に移動したときのPointを求める

•12月 18, 2013 • コメントする

現在地からランダムな方向にラベルが動くという作りを実装しているうちに、結構手間取ったのでメモ。

基本的には「http://keisan.casio.jp/exec/system/1177469593」の計算を参考にして実装しました。

ちなみにcocos2d-xはver3.0alpha1で実装しています。

以下コード



Point HelloWorld::getPositionDelta(cocos2d::Point point, float distance, float angle)

{
    //ラジアンを取得
    float radian = CC_DEGREES_TO_RADIANS(angle);
    
    float x = distance * cos(radian);
    
    float y = distance * sin(radian);
    
    point += Point(x, y);
    
    return point;
}

これで現在地から移動した分が加算されたPointが返ってくるようになります。


    auto moveLabel = LabelTTF::create("aaaaaaaa", "Helvetica", 18);
    addChild(moveLabel);
    //方向を乱数で決める
    random_device rd;
    mt19937 rand = mt19937(rd());
    
//100ポイント分ランダムな方向に移動させる
    Point movePoint = getPositionDelta(moveLabel
->getPosition(), 100, uniform_int_distribution(0, 360)(rand));
    //特定の位置に移動させる
    auto move = MoveTo::create(1.0f, movePoint);

    moveLabel->runAction(move);

これでラベルがランダムな方向に動くようになるはずです。

方向を求める部分や乱数は基本C++なので応用は利くと思います

(cocos2d-x)画面の全体、あるいは一部のみタップを無効化する

•10月 18, 2013 • コメントする

(Cocos2d-x)CCLayer上のボタンを無効化(disable)するにおいて、タップの無効をsetEnableで切り替えているが、もっと簡単な方法があるので紹介する。
cocos2d-xでは、どこをタップされたかについてはほぼ全てCCDIrectorで管理しており、それぞれに優先度があるため、優先度を高く設定して、その優先度以下のタップを無効にしてやればタップを無効にできる
以下は、CCNodeの範囲のタップを無効にしたもの。

HelloWorld.h



class HelloWorld :
public cocos2d::CCLayer
//, public cocos2d::CCTouchDelegate
{
public:
    virtual bool init();
    static cocos2d::CCScene* scene();
    void menuCloseCallback(CCObject* pSender);

    virtual void onEnter();
    virtual void onExit();

    cocos2d::CCLayerColor* noTouchNode_;

    virtual bool ccTouchBegan(cocos2d::CCTouch *pTouch, cocos2d::CCEvent *pEvent);

    CREATE_FUNC(HelloWorld);
};

noTouchNode_の範囲を無効化にする。
noTouchNode_がCCLayerColorなのは見た目的にわかりやすくするためなので、
実際はCCNodeを継承しているものであればどのクラスでも問題はない
また、HelloWorldがCCLayerではなく、CCNodeを継承していた場合、CCTouchDelegateを多重継承する必要がある。

HelloWorld.cpp



bool HelloWorld::init()
{
    //////////////////////////////
    // 1. super init first
    if ( !CCLayer::init() )
    {
        return false;
    }

	CCSize winSize = CCDirector::sharedDirector()->getWinSize();

    noTouchNode_ = CCLayerColor::create(ccc4(100,100 , 100, 255));
    noTouchNode_->setPosition(CCPoint(winSize.width / 2, winSize.height / 2));
    noTouchNode_->ignoreAnchorPointForPosition(false);
    noTouchNode_->setAnchorPoint(CCPoint(0.5f, 0.5f));
    noTouchNode_->setContentSize(CCSize(100.0f, 100.0f));
    this->addChild(noTouchNode_);

    return true;
}

void HelloWorld::onEnter(){
    CCLayer::onEnter();
//priorityをCCMenuよりも高くし、SwallowsTouchesをtrue(優先度以下を無効)にする
    CCDirector::sharedDirector()->getTouchDispatcher()->addTargetedDelegate(this, kCCMenuHandlerPriority - 1, true);

}
void HelloWorld::onExit(){
    CCLayer::onExit();

    CCDirector::sharedDirector()->getTouchDispatcher()->removeDelegate(this);

}

bool HelloWorld::ccTouchBegan(cocos2d::CCTouch *pTouch, cocos2d::CCEvent *pEvent){

    //タップした場所がnoTouchNode_の範囲内だった場合true
    bool touchFlag = noTouchNode_->boundingBox().containsPoint(convertTouchToNodeSpace(pTouch));
    return touchFlag;

}

これでnoTouchNode_の範囲のタップが無効になる。
addTargetedDelegateでkCCMenuHandlerPriorityからを1を引いているのは、CCMenuのタップよりも優先度を高くするため。
また、ccTouchBeganのreturnを逆にすれば、noTouchNode_の範囲のみタップさせるということもできる。

ついでに、これは直接Sceneで実装しているが、上記機能を持ったCCLayerを継承したカスタムクラスを作れば、
それをaddとremoveをするだけでタップの有効、無効を切り替えることができる(onExitでremoveDelegateを呼んでいるため)

CCMenuやCCControlButtonの優先度をsetTouchPriorityでkCCMenuHandlerPriority – 2に設定すれば、noTouchNode_の範囲内にいてもタッチを有効にすることもできる

(cocos2d-x)CCTableViewで、特定の行のみcellのサイズを変更する

•10月 18, 2013 • コメントする

cocos2d-x2.1.2から、CCTableViewにtableCellSizeForIndexが実装され、行によってcellのサイズを変更することができるようになった。

ただ、CCTableViewにはdequeueCellというcellを使い回すことで挙動を軽くする機能がついているため、基本はこれを使う(TestCppもそうなっている)のだが、

これをそのまま使おうとすると、サイズの変更されたcellをそのまま使い回してしまうため、表示がおかしくなってしまう。

cellを毎回newで作り直せば問題は無いのだが、できればdequeueCellを使いたいので、cellに対してタグを持たせ、目的のcellが出るまでdequeueCellを繰り返すことで解決した。

以下がそのコード



namespace {
    const CCSize labelSize = CCSize(320.0f, 30.0f);
    const CCSize imageSize = CCSize(320.0f, 200.0f);
    const int cellNum = 10;
}

enum TAG {
    image_tag = 0,
    label_tag,
    image_cell_tag = 100,
    label_cell_tag
};

bool HelloWorld::init()
{
    //////////////////////////////
    // 1. super init first
    if ( !CCLayer::init() )
    {
        return false;
    }
    
	CCSize winSize = CCDirector::sharedDirector()->getWinSize();
    
    CCTableView* tableView = CCTableView::create(this, CCSizeMake(winSize.width, winSize.height));
    tableView->setDirection(kCCScrollViewDirectionVertical);
    this->addChild(tableView);
    tableView->reloadData();
    
    return true;
}




CCSize HelloWorld::tableCellSizeForIndex(CCTableView *table, unsigned int idx){
    //特定の行のときのみセルのサイズを変える
    if (idx == 0) {
        return imageSize;
    }
    
    return labelSize;
}

CCTableViewCell* HelloWorld::tableCellAtIndex(CCTableView *table, unsigned int idx)
{
    CCTableViewCell *cell = table->dequeueCell();
    
    int tag = label_cell_tag;
    //特定の行のときにcellのタイプを変える
    if (idx == 0) {
        tag = image_cell_tag;
    }
    
    //cellがNULLでない限りまわし続ける
    while (cell) {
        //cellのタグがあっていた場合break;
        if (cell->getTag() == tag) {
            break;
        }
        cell = table->dequeueCell();
    }
    
    if (!cell) {
        cell = new CCTableViewCell();
        cell->autorelease();
        
        if (tag == image_cell_tag) {
            CCSprite *sprite = CCSprite::create("Icon-72.png");
            sprite->setPosition(ccp(imageSize.width / 2, imageSize.height / 2));
            sprite->setAnchorPoint(ccp(0.5, 0.5));
            sprite->setTag(image_tag);
            cell->addChild(sprite);
        } else if (tag == label_cell_tag){
            CCLabelTTF* label = CCLabelTTF::create(CCString::createWithFormat("%d", idx)->getCString(), "Helvetica", 11);
            label->setPosition(ccp(labelSize.width / 2, labelSize.height / 2));
            label->setAnchorPoint(ccp(0.5, 0.5));
            label->setTag(label_tag);
            cell->addChild(label);
            
        }
        cell->setTag(tag);
    } else {
        //行によって動作を変えたい場合ここで処理
        if (tag == image_cell_tag) {
            CCSprite *sprite = (CCSprite*)cell->getChildByTag(image_tag);
        } else if (tag == label_cell_tag){
            CCLabelTTF* label = (CCLabelTTF*)cell->getChildByTag(label_tag);
            label->setString(CCString::createWithFormat("%d", idx)->getCString());
            
        }
    }
    
    
    return cell;
}

unsigned int HelloWorld::numberOfCellsInTableView(CCTableView *table)
{
    return cellNum;
}

1行目のみcellの高さが200で画像を表示するcell、それ以外をcellの高さが30でラベルを表示する動きにしている。

(Cocos2d-x)CCLabelTTFを文字の長さに合わせて自動改行させる

•9月 25, 2013 • コメントする

CCLabelTTFは、表示する範囲があらかじめ決まっていれば、setDimensionsを指定していれば横幅に合わせて自動改行してくれる。

ただ、表示する高さが決まっていればそれで問題ないのだが、

CCScrollView等を使って表示する高さを可変にしたい場合、あらかじめ高さを指定すると、高さをオーバーした部分が表示されないため、非常に困る。

その場合の対処法として、一度setStringをした後、dimensionsの高さを0に指定することでCCLabelTTFのContentSizeが自動改行された後のサイズになるので、

そこから再度dimensionsを指定し直すことで、CCLabelTTFを横のサイズで自動改行した後のサイズに変更することができる。

実際にコードにすると以下のようになる。



const char* str = "hogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehoge";
CCLabelTTF* label = CCLabelTTF::create(str, "Helvetica", 12);

//テキストの縦の長さを取得するためにCCLabelTTFを横のみ指定する
label->setDimensions(CCSize(100,0));

label->setDimensions(CCSize(label->getContentSize().width, label->getContentSize().height));

これでhogeが100pxごとに自動改行され表示されるようになる

(Cocos2d-x, C++)tinyxml2を使いXMLをparseする

•1月 25, 2013 • 1件のコメント

Cocos2d-xで使いやすいXMLパーサーを探したところ、CCSAXParserはどうも使いづらそうだったので、C++のパーサーであり、スマートフォン向けとされるtinyxml2を使う事にした。
ただ毎回要素にアクセスしていくのは億劫なので、CCDictionaryに全ての要素を入れて返ってくるようなものを作成した。

以下コード



CCDictionary* XMLParse::parseAllDictionary(const char* data)
{
   tinyxml2::XMLDocument doc;
    doc.Parse(data);
//    doc.Print();

    tinyxml2::XMLElement * elem = doc.RootElement();
    tinyxml2::XMLElement* beforeClass = NULL;
    CCDictionary* rootDic = CCDictionary::create();
    int childCount = 0;

    //Elementがある限りまわし続ける
    while (elem) {

        //        CCLOG("%s", elem->Name());
        //下に潜れるか
        if (elem->FirstChildElement()) {
            CCDictionary* childDic;
            CCArray* childArray;
            std::string parentName;
            if (!childCount) {
                childDic = rootDic;

            } else {
                for (int i = 0; i < childCount; i++) {
                     if (i) { 
                        CCString* key = (CCString*)childDic->allKeys()->lastObject();
                        childArray = (CCArray*)childDic->objectForKey(key->getCString());
                    } else {
                        CCString* key = (CCString*)rootDic->allKeys()->lastObject();
                        childArray = (CCArray*)rootDic->objectForKey(key->getCString());
                    }
                    childDic = (CCDictionary*)childArray->lastObject();
                }
            }

            CCDictionary* dic = CCDictionary::create();
            CCArray* array;

            std::string className = elem->Name();

            if (beforeClass && !className.compare(beforeClass->Name())) {
                array = (CCArray*)childDic->objectForKey(elem->Name());
            } else {
                array = CCArray::create();
            }

            array->addObject(dic);
            childDic->setObject(array, elem->Name());

            beforeClass = elem;

            elem = elem->FirstChildElement();
            childCount++;

            if (elem->GetText()) {
                while (elem) {
                    dic->setObject(CCString::create(elem->GetText()), elem->Name());
                    if (elem->NextSiblingElement()) {
                        elem = elem->NextSiblingElement();
                    } else {
                        break;
                    }
                }
            }
        } else if(elem->NextSiblingElement()) {
            elem = elem->NextSiblingElement();

        } else {
            while (elem->Parent()->ToElement() && !elem->NextSiblingElement()) {
                elem = elem->Parent()->ToElement();
                childCount--;
            }
            if (elem->NextSiblingElement()) {

                elem = elem->NextSiblingElement();
            } else {
                break;
            }
        }
    }
    return rootDic;

}

この関数に、例えば<test><elem><out>1</out></elem><elem><out>2</out></elem></test>といったxmlの文字列を渡すとすると、

CCDictionary* dic = XMLParse::parseAllDictionary(“<test><elem><out>1</out></elem><elem><out>2</out></elem></test>”);
CCArray* array = (CCArray*)dic->objectForKey(“test”);
CCDictionary* testDic = (CCDictionary*)array->objectAtIndex(0);
CCArray* elemArray = (CCArray*)testDic->objectForKey(“elem”);
CCDictionary* elemDic = (CCDictionary*)elemArray->objectAtIndex(1);
CCString* outString = (CCString*)elemDic->objectForKey(“out”);
CCLOG(“%s”, outString->getCString());

とすれば、2が表示されるはず。
また、CCDictionaryとCCArrayをmapやvectorに変えれば普通のC++にも使えると思います。

 

追記:

公開してから半年以上経っているのでかなり今更ですが、

一部記述が消えてしまっていた部分を修正しました