簡単なカスタムボタンの作り方（２）

# カスタムボタンをつくる

Cocos2d-x でボタンを使うためには主に以下の二通りがあります。
* CCMenuItem を使う
* CCSprite にタップイベントを追加する

後者について、二日目に t-kashimaさんが[二日目](http://qiita.com/t-kashima/items/adb7e823cc2269aed3f4)で紹介されているので、
今日は前者のやり方について、TIPSを紹介してみます。

## ボタンが押された時にはアクションを

Cocos2d-x を使用したアプリでは、基本的には独自のUIになるため
ボタンを押された時にユーザーに的確なフィードバックを行うことが重要です。

例えば、押された時にボタンの色や大きさを変えたり、音を鳴らしたりするなど、です。
基本的には以下の様に記述します。

```objc:HelloWorld.cpp
    CCMenuItemImage *pCloseItem = CCMenuItemImage::create(
                    "CloseNormal.png", 		// 通常状態の画像
                    "CloseSelected.png",	// 押下状態の画像
                    this,
                    menu_selector(HelloWorld::menuCloseCallback));  // 押下時のアクション
    CCMenu* pMenu = CCMenu::create(pCloseItem, NULL);
    pMenu->setPosition(CCPointZero);
    this->addChild(pMenu, 1);
}

void HelloWorld::menuCloseCallback(CCObject* pSender)
{
   // 効果音を鳴らす
    CocosDenshion::SimpleAudioEngine::sharedEngine()->playEffect("effect1.wav");
}
```

## ボタン自体にアクションを組み込む

ただ、前述の方法で、各ボタン毎にアクションを用意したり、画像を用意したりするのは面倒・・・
そんなときには、ボタン自体にアクションを追加しましょう。

## CCMenuItem から派生したクラスでアクションを処理する

CCMenuItem を基底としたクラスを生成して、その中でアクションを処理するようにします。
(ここでは CCMenuItemImage から派生した MyMenuItem クラスとします)

```objc:MyMenuItem.h
class MyMenuItem : public cocos2d::CCMenuItemImage
{    
public:
（中略）
    // 生成関数
    bool initWithImage(const char *normal, const char* selected,
                       cocos2d::CCObject* target,
                       cocos2d::SEL_MenuHandler selector);
    
    // このボタンをタップした時の処理
    void onTapThis(cocos2d::CCObject* sender);
};
```

```objc:MyMenuItem.cpp
bool MyMenuItem::initWithImage(const char *normal, const char *selected,
                               CCObject* target, SEL_MenuHandler selector)
{
   // このクラスでタップイベントを取得するように登録する
    return initWithImage(normal, selected, this, 
                                     menu_selector(MyMenuItem::onTapThis));
}

void MyMenuItem::onTapThis(cocos2d::CCObject *sender)
{
    // ボタンが押されたイベント内で効果音を鳴らす
    CocosDenshion::SimpleAudioEngine::sharedEngine()->playEffect("effect1.wav");
}
```

これで、ボタンクラスでアクション（効果音を鳴らす）が実行できるようになりました。

しかし、これでは init 関数で指定されたセレクタを無視していて、一つのアクションしか定義できないため、余り意味がありません。
そこで、次は init 関数で指定されたセレクタを保持して、クラスのアクションを実行した後にその処理を実行するようにしましょう。

```objc:MyMenuItem.h
class MyMenuItem : public cocos2d::CCMenuItemImage
{
private:
    // 元のセレクタ
    cocos2d::CCObject* m_orgTarget;
    cocos2d::SEL_MenuHandler m_orgSelector;
（中略）
};
```

```objc:MyMenuItem.cpp
bool MyMenuItem::initWithImage(const char *normal, const char *selected,
                               CCObject* target, SEL_MenuHandler selector)
{
    // 元のセレクタを保持
    m_orgTarget = target;
    m_orgSelector = selector;
    
   // このクラスでタップイベントを取得する
    return initWithImage(normal, selected, this, 
                                     menu_selector(MyMenuItem::onTapThis));
}
void MyMenuItem::onTapThis(cocos2d::CCObject *sender)
{
   // 効果音を鳴らす
    CocosDenshion::SimpleAudioEngine::sharedEngine()->playEffect("effect1.wav");

    // 元のセレクタを呼び出す
    if (m_orgTarget && m_orgSelector) {
        (m_orgTarget->*m_orgSelector)(this);
    }
}
```

## ボタン押下時（選択時）の画像処理を共通にする

ついでにボタンが押された時の画像処理も共通化してみます。
ボタンが押された時の画像は、通常時の画像に setColor でグレーを重ねた画像にします。

以上のことを実装したクラスが以下になります。

```objc:MyMenuItem.h
class MyMenuItem : public cocos2d::CCMenuItemImage
{
private:
    // 元のセレクタ
    cocos2d::CCObject* m_orgTarget;
    cocos2d::SEL_MenuHandler m_orgSelector;
    
public:
    MyMenuItem();
    virtual ~MyMenuItem();
    
    static MyMenuItem* create(const char *image, cocos2d::CCObject* target,
                              cocos2d::SEL_MenuHandler selector);
    static MyMenuItem* create(const char *normal, const char *selected,
                              cocos2d::CCObject* target,
                              cocos2d::SEL_MenuHandler selector);
    
    bool initWithImage(const char *normal, const char* selected,
                       cocos2d::CCObject* target,
                       cocos2d::SEL_MenuHandler selector);
    
    // このボタンをタップした時の処理
    void onTapThis(cocos2d::CCObject* sender);
    
    // タップした時の効果音番号
    CC_SYNTHESIZE(int, m_seNo, SeNo);
};
```

```objc:MyMenuItem.cpp
MyMenuItem::MyMenuItem()
: m_orgTarget(NULL)
, m_orgSelector(NULL)
, m_seNo(0)
{
}

MyMenuItem::~MyMenuItem()
{
}


MyMenuItem * MyMenuItem::create(const char *image, CCObject* target,
                                SEL_MenuHandler selector)
{
    MyMenuItem *pRet = new MyMenuItem();
    if (pRet && pRet->initWithImage(image, NULL, target, selector))
    {
        pRet->autorelease();
        return pRet;
    }
    CC_SAFE_DELETE(pRet);
    return NULL;
}

MyMenuItem * MyMenuItem::create(const char *normal, const char *selected,
                                CCObject* target, SEL_MenuHandler selector)
{
    MyMenuItem *pRet = new MyMenuItem();
    if (pRet && pRet->initWithImage(normal, selected, target, selector))
    {
        pRet->autorelease();
        return pRet;
    }
    CC_SAFE_DELETE(pRet);
    return NULL;
}

bool MyMenuItem::initWithImage(const char *normal, const char *selected,
                               CCObject* target, SEL_MenuHandler selector)
{
    // 元のセレクタを保持
    m_orgTarget = target;
    m_orgSelector = selector;
    
    // 通常時のボタン画像
    CCSprite *pNormal = pNormal = CCSprite::create(normal);
    CCSprite *pSelected = NULL;
    if (selected) {
        pSelected = CCSprite::create(selected);
    }else{
        // 選択時の画像の指定がなければ、
        // 通常時の画像にグレーをセットして選択時の画像とする
        pSelected = CCSprite::create(normal);
        pSelected->setColor(ccc3(105, 105, 105));
    }
    return initWithNormalSprite(pNormal, pSelected, NULL, this, menu_selector(MyMenuItem::onTapThis));
}

void MyMenuItem::onTapThis(cocos2d::CCObject *sender)
{
    // このメニューアイテムが押下された
    if (m_seNo != 0) {
        // 効果音の指定があれば、効果音を鳴らす
        CCString* name = CCString::createWithFormat("effect%d.wav", m_seNo);
        CocosDenshion::SimpleAudioEngine::sharedEngine()->playEffect(name->getCString());
    }
    if (m_orgTarget && m_orgSelector) {
        // 元のセレクタを呼び出す
        (m_orgTarget->*m_orgSelector)(this);
    }
}
```

これで最初の処理は以下のように書けるようになりました。
（こうみると余り違いが無いようにも見えますが・・・）

###Before

```objc:HelloWorld.cpp
    CCMenuItemImage *pCloseItem = CCMenuItemImage::create(
                    "CloseNormal.png", 		// 通常状態の画像
                    "CloseSelected.png",	// 押下状態の画像
                    this,
                    menu_selector(HelloWorld::menuCloseCallback));  // 押下時のアクション
    CCMenu* pMenu = CCMenu::create(pCloseItem, NULL);
    pMenu->setPosition(CCPointZero);
    this->addChild(pMenu, 1);
}

void HelloWorld::menuCloseCallback(CCObject* pSender)
{
   // 効果音を鳴らす
    CocosDenshion::SimpleAudioEngine::sharedEngine()->playEffect("effect1.wav");
}
```

###After

```objc:HelloWorld.cpp
    MyMenuItem *pCloseItem = MyMenuItem::create(
                    "CloseNormal.png",     // 通常状態の画像
                    this,
                    menu_selector(HelloWorld::menuCloseCallback));  // 押下時のアクション
    pCloseItem->setSeNo(1); // 効果音 No.1
    CCMenu* pMenu = CCMenu::create(pCloseItem, NULL);
    pMenu->setPosition(CCPointZero);
    this->addChild(pMenu, 1);
}

void HelloWorld::menuCloseCallback(CCObject* pSender)
{
    // ここで効果音の処理は要らない
}
```

## まとめ
こういう方法は他に、Google Analytics のイベントを発行する時など、ボタン共通の処理を行いたいときにはこういった方法は有効かなと思います。

最初のうちは CCMenuItem をそのまま使って「使いにくい・・・パラメータが渡せない・・・」等と思われることも多いと思いますが、既存のクラスを活用すると色々楽ができるので、色々試してみてください。