戦うアプリニート

cocos2d-xやRuby on Railsの備忘録

cocos2d-x v3 (というかc++) 関数ポインタのテーブルを作って、for文で「れんぞくま」する

参考にさせてもらった記事です。

メンバ関数のテーブル化

ぶっちゃけ私の理解度は低いので、正確な記述・理解を求める方は上記リンクへGO!
ただ、「こんなこともできるんか!」と感動してしまったので、記録に残しておくことにします。


例えばメソッドが5つあったとして。

// HelloWorld.cppとか

void HelloWorld::Firaga() //ファイガ
{
}

void HelloWorld::blizzaga() //ブリザガ
{
}

void HelloWorld::thundaga() //サンダガ
{
}

void HelloWorld::meteor() //メテオ
{
}

void HelloWorld::artema() //アルテマ
{
}


これを関数ポインタのテーブルMagicMenuにしちゃいます。

//これも同じHelloWorld.cpp内

void(HelloWorld::* MagicMenu[])()
{
    &HelloWorld::firaga,
    &HelloWorld::blizzaga,
    &HelloWorld::thundaga,
    &HelloWorld::meteor,
    &HelloWorld::artema
};


ほいでもって、こうだ!

// HelloWorld.cpp

for (int i = 0; i < 5; i ++)
{
        (this->*MagicMenu[i])(); // ファイガ! ブリザガ! サンダガ! メテオにアルテマ!
}

ほぼワンライナーで乱れ打ちが可能であります。
これはちょっと感動的。プログラムってスゲー!


ただ、privateメソッドの場合など、
「ようやくc++で簡単なプログラムを組めるようになってきた」
くらいの雑魚(私です)には、大変にとっつきにくい構文であります。
これを継承して、とか考えるだけで頭が痛くなりそうです。

でも、ここで挙げたサンプル程度の使い方でも、
「他クラスから順番に呼び出したいメソッドが大量にある」場合など、
コードを書く量は激減するはず。

それにウィザード気分が味わえるので、なかなか楽しいのではないでしょうか。


関数ポインタのテーブルのサンプルでした。

cocos2d-x v3 ピクセルパーフェクトなタッチ判定をする(スプライトの透過部分を無視する)

スプライトのタッチ判定を、見た目どおりに行いたい。
透明部分とそうでない部分を判別したい、ということであります。

ググると、大量のStackOverFlow記事が出てくるので、サクッと行きそうに思えたのですが……。
激ハマり!(泣) しましたので備忘録を残しておきます。

参考にしたのはこちら。discuss.cocos2d-x.org
途中の、ピクセルを読むクラスを作るアプローチでいきたいと思います。

早速その、ピクセルを読むクラスのコードです。
コピペでいけるはず。まずはヘッダーから。

//ReadNode.h

class ReadNode : public cocos2d::Node
{
public:
    static ReadNode* getInstance();
    virtual void draw(cocos2d::Renderer *renderer, const cocos2d::Mat4& transform, uint32_t flags);
    GLubyte* getPixelColor() {return _pixelColorRead; };
    void setReadPoint(cocos2d::Vec2 point);

private:
    static ReadNode* _ReadNode;
    CREATE_FUNC(ReadNode);
    
protected:
    void onDraw();
    cocos2d::CustomCommand _readPixelsCommand;
    cocos2d::Vec2 _readPoint;
    GLubyte _pixelColorRead[4];
};

privateとprotectedの分け方とかテキトーだったりします。
private部は自前のシングルトンクラスからのコピペ+書き換えだったりするので……。
しかしながら一応動くは動くはず。

続いてソース。

//ReadNode.cpp

//シングルトン
ReadNode* ReadNode::_ReadNode = NULL;

ReadNode* ReadNode::getInstance()
{
    if(_ReadNode == NULL)
    {
        _ReadNode = new ReadNode;
    }
    return _ReadNode;
}

//改悪セッターメソッド
void ReadNode::setReadPoint(cocos2d::Vec2 point)
{
    _readPoint = point;
}

// draw()はオーバーライドできないので、draw(Renderer *renderer, const Mat4& transform, uint32_t flags)をいじくる
void ReadNode::draw(Renderer *renderer, const Mat4& transform, uint32_t flags)
{
    _readPixelsCommand.init(_globalZOrder);
    _readPixelsCommand.func = CC_CALLBACK_0(ReadNode::onDraw, this);
    renderer->addCommand(&_readPixelsCommand);
}

void ReadNode::onDraw()
{ 
// ここでピクセルのRGBAを取得
    glReadPixels(_readPoint.x, // タッチしたポイントのx,y
                 _readPoint.y,
                 1, // width
                 1, // height
                 GL_RGBA, //定数
                 GL_UNSIGNED_BYTE,//定数
                 &_pixelColorRead); //なんで&かよくわかっていない
}

何をやっているかというと。
RenderTextureで対象のスプライトを描画すると、そのタイミングでglReadPixelsが呼べるので、
glReadPixelsで取得できるRGBAの値を読んで、透明か否かを判定しよう、という話です。


使い方はこんな風。

//HelloWorld.cpp
bool HelloWorld::init()
{
    if (!Layer::init())
    {
        return false;
    }
    Size winSize = Director::getInstance()->getWinSize();
    
    auto sp = Sprite::create("pp_test.png");
    sp->setPosition(Vec2(winSize.width/2, winSize.height/2));
    addChild(sp);

    auto sp_listener = EventListenerTouchOneByOne::create();
    sp_listener->onTouchBegan = [this, sp](Touch *touch, Event *event){
        auto location = touch->getLocation(); // convertToNodeSpaceしない
        this->IsTouchingSprite(sp, location);

        return true;
    };
    Director::getInstance()->getEventDispatcher()->addEventListenerWithSceneGraphPriority(sp_listener, sp);
    
    return true;
}

こっちはただ対象スプライトとタッチポイントを特定して、渡してるだけであります。
spとlocationを透過判定メソッドに渡してやります。

bool HelloWorld::IsTouchingSprite(cocos2d::Sprite *sp, cocos2d::Point touch_point)
{
    bool isTouchingExactly = false;
    Size winSize = Director::getInstance()->getWinSize();
    
    RenderTexture *r_texture = RenderTexture::create(winSize.width, winSize.height, Texture2D::PixelFormat::RGBA8888); //フルサイズで
    r_texture->setPosition(Vec2(winSize.width/2, winSize.height/2));
    this->addChild(r_texture);
    
    auto readNode = ReadNode::getInstance();
    readNode->setReadPoint(touch_point); //改悪セッターメソッド……。
    
    r_texture->beginWithClear(0, 0, 0, 0);
        sp->visit();
        readNode->visit(); //作ったクラスにもvisitする
    r_texture->end();
    Director::getInstance()->getRenderer()->render(); // Renderer::render()をコール
    
    GLubyte *color = readNode->getPixelColor(); // color[0] = r, color[1] = g, color[2] = b, color[3] = a, 
    if (color[3] == 0)
    {
        isTouchingExactly = false;
        log("透明部分");
    }
    else
    {
        isTouchingExactly = true;
        log("絵がある部分");
    }
    return isTouchingExactly;
}


StackOverFlowで中の人が何やら説明しているのが、下の部分。

    r_texture->beginWithClear(0, 0, 0, 0);
        sp->visit();
        readNode->visit(); //作ったクラスにもvisitする
    r_texture->end();
    Director::getInstance()->getRenderer()->render(); // Renderer::render()をコール

nodeを作ってvisitせよとか、Renderer::render()せよとか、うんぬんかんぬん。
なんか非同期処理になったので、glReadPixels()呼ぶのに、ひと工夫必要になった感じですかね。
とりあえずこの指示通りに作った人「の真似」をしております。


ちなみにセッターメソッド作ってるのは、上記の指示通りに作った人のコードが理解できなかったためです(汗
オリジナルはもっとスマートでしたが、泥臭く改悪しております。

    auto readNode = ReadNode::getInstance();
    readNode->setReadPoint(touch_point); //改悪セッターメソッド……。


何気に最後に時間を浪費してしまったのがこれ。
日本語の記事、例えば↓なんかだと、wonderpla.net
convertToNodeSpaceせよ
と仰ってるのですが、しなくてOK、
というかするとオカシくなってしまうので、touch->getLocation()した値を直渡しすべし。


最終的にcolorの第4要素、color[3]の値で透明か否かを判定しております。
color[0] = r // 赤
color[1] = g // 緑
color[2] = b // 青
color[3] = a // アルファ
ですな。


複数判定とかはもうチョイいじくり回さないとですが、
とりあえず動いたので、一安心です。

cocos2d-x v3 Labelの一部の文字の色を変える

初のはてなブログです。唐突にcocos2d-xのTipsです。

常々、はてなブログ書きたいなあと思っていたので、
「あまり構えずに、思いついたときが吉日や! 新年やし!」
という勢いで書いてしまいたいと思います。

で、表題の件。Labelの一部だけ、色を変更したい時のTipsです。

RPGやADV風に1文字ずつ文字送りしていて、
重要なワードだけハイライトしたい時に使ってます。

「ええっ! プリンがなくなっていた!?」

みたいな感じで。

    Size visibleSize = Director::getInstance()->getVisibleSize();
    
    // Label
    TTFConfig conf_test("fonts/rounded-mplus-1m-regular.ttf", 48);
    auto lbl_test = Label::createWithTTF(conf_test, "「ええっ! プリンがなくなっていた!?」");
    lbl_test->setColor(Color3B::WHITE);
    lbl_test->setPosition(Vec2(visibleSize.width/2, visibleSize.height/2));
    this->addChild(lbl_test);
    
    // Menu
    auto btn_change_color = MenuItemFont::create("着色", [lbl_test](Ref* sender)
                                                 {
                                                     auto pu = lbl_test->getLetter(6); // プ
                                                     auto ri = lbl_test->getLetter(7); // リ
                                                     auto nn = lbl_test->getLetter(8); // ン
                                                     
                                                     pu->setColor(Color3B::RED);
                                                     ri->setColor(Color3B::RED);
                                                     nn->setColor(Color3B::RED);
                                                     
                                                 });
    btn_change_color->setPosition(Vec2(visibleSize.width/2, visibleSize.height/3));
    
    auto menu_test = Menu::create(btn_change_color, NULL);
    menu_test->setPosition(Vec2::ZERO);
    this->addChild(menu_test);

こんな感じです。

f:id:masahirosaito:20150103162004p:plain
f:id:masahirosaito:20150103162012p:plain

pu, ri, nnはSpriteです。

ポイントとしては、「プ」「リ」「ン」がそれぞれ7,8,9文字目なのですが、
Arrayのカウント的に0スタートなので、1小さいindexを渡します。

「ええっ! プリンがなくなっていた!?」
012345678

プログラムで動的にやるなら、lengthから1引く感じで。

    auto pu = lbl_test->getLetter(lbl_test->getStringLength()-1);
    pu->setColor(Color3B::RED);

あとは、一旦こうやって色を変えたLabelに、後からsetStringなどすると、index設定が残っていて
TextureAtlasが激おこで「Assertion failed!」とか言ってきます。

cocos2d: Assert failed: removeQuadAtIndex: Invalid index
Assertion failed: (index>=0 && index<_totalQuads), function removeQuadAtIndex,

この辺は根本までさかのぼれていないのですが、とりあえずの案としては、
新しい文字列の表示に移る前に、

    lbl_test->removeAllChildrenWithCleanup(true);

とすると大丈夫です。
ただ、負荷が高くなりそうなので、パフォーマンスを出したければもっと細かい制御が必要かもしれません。