cocos2d-xのComponentパタン

ChainZ(クリエイター)
いろいろやってます。

Unityユーザーならコンポーネントはよく知ってると思います。Unityはコンポーネントパタンをうまく利用して非常に拡張しやすいゲームエンジンを作りました。

コンポーネントの良さは同じコードをいろんなプロジェクトに使えるというところです。

cocos2d-xのComponent

実はcocos2d-xにcocos2d::Componentというクラスが存在しています。このクラスを継承することで、Unityみたいに各機能をコンポーネント化できます。PlayerInputComponentをサンプルとして作りながら説明しよう。

サンプル:PlayerInputComponent

PlayerInputComponentはタップする先に移動させるコンポーネントです。

PlayerInputComponent.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include "cocos2d.h"

// cocos2d::Componentを継承する
class PlayerInputComponent : public cocos2d::Component
{
public:
    PlayerInputComponent();
    virtual ~PlayerInputComponent();

    virtual bool init();
    virtual void onEnter();
    virtual void update(float delta);

    // touch events
    bool onTouchBegan(cocos2d::Touch* touch, cocos2d::Event* event);
    void onTouchMoved(cocos2d::Touch* touch, cocos2d::Event* event);
    void onTouchEnded(cocos2d::Touch* touch, cocos2d::Event* event);
    void onTouchCanceled(cocos2d::Touch* touch, cocos2d::Event* event);

    // PlayerInputComponent::create()メソードを作成してくれるマクロ
    CREATE_FUNC(PlayerInputComponent);

protected:
    // listener
    cocos2d::EventListenerTouchOneByOne *touch_listener_;

    // 移動先
    cocos2d::Vec2 move_to_;

    // 今の場所
    cocos2d::Vec2 cur_pos_;

    // スピード
    float speed_;
};

PlayerInputComponent.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
#include "PlayerInputComponent.h"

USING_NS_CC;

PlayerInputComponent::PlayerInputComponent():
touch_listener_(nullptr),
move_to_(Vec2::ZERO),
cur_pos_(Vec2::ZERO),
speed_(0)
{

}

PlayerInputComponent::~PlayerInputComponent()
{
    touch_listener_->release();
}

bool PlayerInputComponent::init()
{
    if (! Component::init()) return false;

    // コンポーネントの名前をセット
    // Owner->getComponent(<コンポーネント名>)でinstanceを取得できます
    this->setName("PlayerInput");

    // Listenerを作成
    touch_listener_ = EventListenerTouchOneByOne::create();
    touch_listener_->setSwallowTouches(true);

    // autoreleaseされないように
    touch_listener_->retain();

    touch_listener_->onTouchBegan = CC_CALLBACK_2(PlayerInputComponent::onTouchBegan, this);
    touch_listener_->onTouchMoved = CC_CALLBACK_2(PlayerInputComponent::onTouchMoved, this);
    touch_listener_->onTouchEnded = CC_CALLBACK_2(PlayerInputComponent::onTouchEnded, this);
    touch_listener_->onTouchCancelled = CC_CALLBACK_2(PlayerInputComponent::onTouchCanceled, this);

    return true;
}

void PlayerInputComponent::onEnter()
{
    Component::onEnter();

    // スピードをセット
    speed_ = 400;

    Director::getInstance()->getEventDispatcher()->addEventListenerWithSceneGraphPriority(touch_listener_, this->getOwner());

    // Ownerのupdateを走らせる
    this->getOwner()->scheduleUpdate();
}

void PlayerInputComponent::update(float delta)
{
    // 今の位置と目的地座標が違うであれば:
    if (! this->getOwner()->getPosition().equals(move_to_))
    {
        // 移動距離のx, y座標
        Vec2 v = move_to_ - cur_pos_;

        // 移動距離
        float d = move_to_.distance(cur_pos_);

        // 現在の位置
        Vec2 to = this->getOwner()->getPosition();

        // 移動速度
        Vec2 s = Vec2(v.x * speed_/d, v.y * speed_/d);

        //
        to += s * delta;

        // 誤差を補正
        if (v.x < 0) {
            if (to.x < move_to_.x) to.x = move_to_.x;
        } else {
            if (to.x > move_to_.x) to.x = move_to_.x;
        }
        if (v.y < 0) {
            if (to.y < move_to_.y) to.y = move_to_.y;
        } else {
            if (to.y > move_to_.y) to.y = move_to_.y;
        }

        // Ownerの位置更新
        this->getOwner()->setPosition(to);
    }
}

bool PlayerInputComponent::onTouchBegan(cocos2d::Touch* touch, cocos2d::Event *event)
{
    CCLOG("On touch began");
    return true;
}

void PlayerInputComponent::onTouchMoved(cocos2d::Touch* touch, cocos2d::Event *event)
{
    CCLOG("On touch moved");
}

void PlayerInputComponent::onTouchEnded(cocos2d::Touch* touch, cocos2d::Event *event)
{
    // 今の位置と目的をアサインする
    cur_pos_ = this->getOwner()->getPosition();
    move_to_ = touch->getLocation();


    CCLOG("On touch ended");
}

void PlayerInputComponent::onTouchCanceled(cocos2d::Touch* touch, cocos2d::Event *event)
{
    CCLOG("On touch canceled");
}

Componentを使う

Componentを使うのが簡単です。addComponent()Componentのインスタンスを渡すだけです:

1
2
3
4
auto sprite = Sprite::create("HelloWorld.png");
sprite->addComponent(PlayerInputComponent::create());

this->addChild(sprite, 0);

デモ

まとめ

Componentパタンのいいところは、少しのパフォーマンスコストでDRYなコーディングができます。極端にパフォーマンスを追求するゲームじゃなければ、Componentパタンをおすすめします。