Cocos2d-xでRx.cppを使うためのライブラリCocosRx


Cocos2d-xとRx.cppでリアクティブプログラミングを始めるためのライブラリCocosRxを作りました。

<https://github.com/gamako/CocosRx>

はじめに
-------

[Rx.cpp](https://rxcpp.codeplex.com/)というC++用のリアクティブプログラミングのライブラリがあります。

Cocos2d-xで使うには、少なくともイベントをObservableとして扱えないと始まりません。とっかかりとしてタッチイベントと、フレーム更新のイベントをObservableにするライブラリをつくってみました。これだけでもいくらか遊べると思います。

ただし、Cocos2d-xで（というかc++で）リアクティブプログラミングをすることに意味があるのかはわかりません。もしかしたら、とても不毛なことをしているのかもしれませんがw

導入方法
--------

Cocos2d-xのプロジェクトと一緒に/Classes以下のcppをコンパイルしてください。
もちろんRx.cppも必要です。Rx.cppの/Rx/v2/src/rxcppをインクルードパスに設定しましょう。

タッチイベントを扱う
----------------

CCRxTouchEvent.hのCCRx::touchEventObservableを使用すると、タッチイベントをObservableとして扱うことができます。
内部ではcocos::EventDispatcher::addEventListenerWithSceneGraphPriorityでタッチイベントを登録しています。


``` cpp
namespace CCRx {

    typedef rxcpp::observable<cocos2d::Touch*> TouchEventObservable;

    rxcpp::observable<std::tuple<cocos2d::Touch*, TouchEventObservable>>
        touchEventObservable(
            cocos2d::Node* targetNode,
            std::function<bool(cocos2d::Touch*)> isBegan = nullptr,
            bool isSwallow = false);
}
```

返すObservableは、タッチが始まった場所のタッチ情報クラスと、TouchEventObservableのtupleのObservableです。
TouchEventObservableは、一回のタッチ／ドラッグの始まりから終わりまでを提供するObservableです。

次のように使うと、ドラッグ（フリック）操作に従って左右方向にキャラクタを動かすことができます。

``` cpp

#include "CCRxTouchEvent.h"

...

    auto player = Sprite::create("player.png");
    addChild(player);

    auto touchObservableObservable = CCRx::touchEventObservable(this, nullptr, false);

    // move player sprite according to touch and move
    auto shared_player = RefPtr<Sprite>(player);

    touchObservableObservable.subscribe(rxu::apply_to([=](Touch *t, CCRx::TouchEventObservable o) {
        
        auto touchPointOrigin = this->getParent()->convertToNodeSpace(t->getLocation());
        auto playerOrigin = shared_player->getPosition();

        o.subscribe(
                    [=](Touch* t){

                        auto touchPoint = this->getParent()->convertToNodeSpace(t->getLocation());
                        auto newPosition = playerOrigin + (touchPoint - touchPointOrigin);
                        shared_player->setPosition(
                            std::max(
                                std::min(newPosition.x, visibleSize.width), 0.0f), playerOrigin.y);

                    });
    }));
```


フレーム更新のイベント
-----------------

CCRx::intervalを使うと、タイマー処理のようなことができます。intervalを0として扱うと毎フレーム呼ばれるので、アニメーション処理に使えます。
内部でcocos::Scheduler::scheduleを使っています。

```cpp
namespace CCRx {
    rxcpp::observable<float> interval(cocos2d::Node* targetNode, float interval);
}
```

次の例では3秒に一度敵キャラを配置して、ランダムに飛ばしています。

```cpp
#include "CCRxScheduler.h"

...

    auto rnd = std::make_shared<std::mt19937>();
    
    CCRx::interval(this, 3)
    .start_with(0)
    .as_dynamic()
    .subscribe([=](float) {
        std::uniform_real_distribution<float> xDist(0.0 ,visibleSize.width);
        const auto fromX = xDist(*rnd);
        const auto toX = xDist(*rnd);
        const auto startPosition = Vec2{fromX, visibleSize.height};
        const float speed = 100.0;
        const auto vector = (Vec2{toX, 0} - startPosition).getNormalized() * speed;

        auto enemy = RefPtr<Sprite>(Sprite::create("enemy.png"));
        addChild(enemy.get());
        
        enemy->setPosition(startPosition);

        CCRx::interval(enemy.get(), 0)
        .scan(0.0f, [](float sum, float b) { return sum + b; })
        .as_dynamic()
        .subscribe([=](float delta) {
            enemy->setPosition(startPosition + vector * delta);
            
            if (enemy->getPosition().y < 0) {
                enemy->removeFromParent();
            }
        });
        
    });

```

さらにサンプル
-------------

githubのリポジトリには、シューティングゲームっぽいサンプルのプロジェクトも入っていますので、もうちょっと実例が欲しい方はご覧ください。

<https://github.com/gamako/CocosRx>

![CCRxSample.png](https://qiita-image-store.s3.amazonaws.com/0/55599/ae3c1a5a-042c-8321-1db4-3684cb02040f.png "CCRxSample.png")