cocos2dx 3系でバーチャルパッドを実装する

# 概要
cocos2d-x3系でvirtualPad(プニコンとも言うのかな？)を作成するだけの記事です。正直virtualPadの実装は幾つか記事があって、それを参考にすればできると思うのですが、3系で実装してる記事は見当たらなかったのでメモ書きとして書きます。cocos2d-x初心者の方の参考になれば幸いです。


# 完成予想図
完成予想図は以下のようになります。
今回のバーチャルパッドは、レイヤーとして作成し、親のシーンでタッチ情報（touchid)を渡して利用する形になります。

![スクリーンショット 2016-01-12 22.36.27.png](https://qiita-image-store.s3.amazonaws.com/0/92552/8e7e4494-f0d5-6c55-f819-60002ccd9659.png)

パッドの背景はこれ
![virtualpad_bg.png](https://qiita-image-store.s3.amazonaws.com/0/92552/2b9ff627-9b16-127d-9a89-e4df70eae0eb.png)

パッドのピック部分はこれ
![virtualpad_pad.png](https://qiita-image-store.s3.amazonaws.com/0/92552/2c549ff3-f104-cde8-83c5-22302435c16a.png)

# 手順
以下のコードをコピペするだけでできます。

```cpp:HelloWorld.h

#ifndef __HELLOWORLD_SCENE_H__
#define __HELLOWORLD_SCENE_H__

#include "cocos2d.h"
#include "VirtualPad.hpp"
class HelloWorld : public cocos2d::Layer
{
private:
    HelloWorld();
    virtual ~HelloWorld();
public:
    // there's no 'id' in cpp, so we recommend returning the class instance pointer
    static cocos2d::Scene* createScene();
    
    // Here's a difference. Method 'init' in cocos2d-x returns bool, instead of returning 'id' in cocos2d-iphone
    virtual bool init();
    
    // a selector callback
    void menuCloseCallback(cocos2d::Ref* pSender);
    
    /* タッチイベントリスナー */
    void onTouchesBegan(const std::vector<Touch*>& touches, Event *unused_event);
    void onTouchesMoved(const std::vector<Touch*>& touches, Event *unused_event);
    void onTouchesEnded(const std::vector<Touch*>& touches, Event *unused_event);
    
    
    /* VirutalPadの生成 */
    CC_SYNTHESIZE_RETAIN(VirtualPad *, _virPad, VirtualPad);
    
    // implement the "static create()" method manually
    CREATE_FUNC(HelloWorld);
};

#endif // __HELLOWORLD_SCENE_H__


```

```cpp:HelloWorld.cpp

#include "HelloWorldScene.h"

USING_NS_CC;

HelloWorld::HelloWorld()
:_virPad(nullptr)
{
}

HelloWorld::~HelloWorld(){
    CC_SAFE_RELEASE_NULL(_virPad);
}


Scene* HelloWorld::createScene()
{
    // 'scene' is an autorelease object
    auto scene = Scene::create();
    
    // 'layer' is an autorelease object
    auto layer = HelloWorld::create();
    
    // add layer as a child to scene
    scene->addChild(layer);
    
    // return the scene
    return scene;
}

// on "init" you need to initialize your instance
bool HelloWorld::init()
{
    //////////////////////////////
    // 1. super init first
    if ( !Layer::init() )
    {
        return false;
    }
    
    FileUtils::getInstance()->addSearchPath("res");
    
    /* マルチタップリスナーの設置 */
    auto listener = EventListenerTouchAllAtOnce::create();
    listener->setEnabled(true);
    listener->onTouchesBegan = CC_CALLBACK_2(HelloWorld::onTouchesBegan, this);
    listener->onTouchesMoved = CC_CALLBACK_2(HelloWorld::onTouchesMoved, this);
    listener->onTouchesEnded = CC_CALLBACK_2(HelloWorld::onTouchesEnded, this);
    this->getEventDispatcher()->addEventListenerWithSceneGraphPriority(listener, this);
    
    Vec2 origin = Director::getInstance()->getVisibleOrigin();
    
    
    /* バーチャルパッドの実装 */
    auto virPad = VirtualPad::create();
    this->addChild(virPad);
    this->setVirtualPad(virPad);
    
    return true;
}

/**
 *タッチ開始
 *@param touches
 *@param event
 */
void HelloWorld::onTouchesBegan(const std::vector<Touch *> &touches, cocos2d::Event *unused_event){
    
    std::vector<cocos2d::Touch*>::const_iterator iterator = touches.begin();
    while (iterator != touches.end()) {
        Touch* touch = (Touch*)(*iterator);
        auto location = touch->getLocation();
        
        /* VirtualPadは、最初は画像の中をタッチしないと反応しない*/
                if(_virPad->isTouchPad(location)){
        
                    //パッドの開始
                    _virPad->startPad((int)touch->getLocation().x,(int)touch->getLocation().y,touch->getID());
        
                    //パッドの更新
                    _virPad->update((int)touch->getLocation().x,(int)touch->getLocation().y,touch->getID());
                }
        iterator++;
    }
    return;
}

/**
 *タッチ移動
 *@param touches
 *@param event
 */
void HelloWorld::onTouchesMoved(const std::vector<Touch *> &touches, cocos2d::Event *unused_event){
    std::vector<cocos2d::Touch*>::const_iterator iterator = touches.begin();
    while (iterator != touches.end()) {
        Touch* touch = (Touch*)(*iterator);
        auto location = touch->getLocation();
        
        /*バーチャルパッド移動中*/
        _virPad->update((int)touch->getLocation().x,(int)touch->getLocation().y,touch->getID());
        
        iterator++;
    }
    return;
}

/**
 *タッチ終了
 *@param touches
 *@param event
 */
void HelloWorld::onTouchesEnded(const std::vector<Touch *> &touches, cocos2d::Event *unused_event){
    std::vector<cocos2d::Touch*>::const_iterator iterator = touches.begin();
    while (iterator != touches.end()) {
        Touch* touch = (Touch*)(*iterator);
        auto location = touch->getLocation();
        /* バーチャルパッドを離す */
        _virPad->endPad(touch->getID());
        iterator++;
    }
    return;
}





```

```cpp:VirtualPad.h

#ifndef CocosProjectTest1_VirtualPad_hpp
#define CocosProjectTest1_VirtualPad_hpp

#include <stdio.h>
#include "cocos2d.h"
using namespace cocos2d;

/**
 *VirtualPadクラス.
 */
class VirtualPad :public::Layer {
private:
    /*コンストラクタ*/
    VirtualPad();
    /*デストラクタ*/
    ~VirtualPad();
    //Pad下地
    Sprite* padBack;
    //Pad操作部分
    Sprite* padFront;
    
    //表示フラグ
    bool touchFlag;
    //Padの初期位置
    int init_x;
    int init_y;
    //現在位置
    int now_x;
    int now_y;
    //移動量
    int d_x;
    int d_y;
    //移動sin,cos
    float way_x;
    float way_y;
    //最大半径
    int max_r;
    //現在の半径
    int now_r;
    //角度
    float angle;
    //角度テーブル
    float fsin[360];
    float fcos[360];
    //タッチID
    int touchID;
    
public:
    
    void update(float dt)override;
    CREATE_FUNC(VirtualPad);
    bool init() override;
    
    //パッドの開始位置
    void startPad(int x,int y,int touch_id);
    
    //パッドの終了位置
    void endPad(int touch_id);
    
    //パッドを触っている状態か？
    bool isTouching();
 
    //パッドを触ったか？
    bool isTouchPad(Vec2 location);
    
    //角度の取得
    float getCosX();
    float getSinY();
    //方向の取得
    int get4Way();
    int get8Way();
    
    //パッドからスピードの取得
    int getSpeed();
    
    void update(int x,int y,int touch_id);
};

#endif /* VirtualPad_hpp */



```

```cpp:VirtualPad.h


#include "VirtualPad.hpp"

#define VIRTUAL_PAD_MAX_RATE 150
#define VIRTUAL_PAD_MIN_RATE 10

#define VIRTUAL_PAD_POSITION_RATE_X 1/2
#define VIRTUAL_PAD_POSITION_RATE_Y 22/100

/**
 *コンストラクタ
 */
VirtualPad::VirtualPad(){
}

/**
 *デストラクタ
 */
VirtualPad::~VirtualPad(){
}


bool VirtualPad::init(){
    
    if ( !Layer::init() )
    {
        return false;
    }
    //画面サイズの取得
    Size size = Director::getInstance()->getVisibleSize();
    
    //画像の生成（Pad下地）
    padBack = Sprite::create("virtualpad/virtualpad_bg.png");
    padBack->setAnchorPoint(Vec2::ANCHOR_MIDDLE);
    padBack->setPosition(Point(size.width*VIRTUAL_PAD_POSITION_RATE_X, size.height*VIRTUAL_PAD_POSITION_RATE_Y));
    
    /* 画像の生成（Pad操作部分） */
    padFront = Sprite::create("virtualpad/virtualpad_pad.png");
    padFront->setAnchorPoint(Point(0.5f,0.5f));
    padFront->setPosition(Point(size.width*VIRTUAL_PAD_POSITION_RATE_X, size.height*VIRTUAL_PAD_POSITION_RATE_Y));
    
    /* パッドの初期位置 */
    init_x = padFront->getPositionX();
    init_y = padFront->getPositionY();
    
    //レイヤーに追加
    this->addChild(padBack,10000);
    this->addChild(padFront,10001);
    
    //最大半径
    max_r = VIRTUAL_PAD_MAX_RATE ;
    
    //角度毎のx,y位置テーブル作成
    for(int i=0;i<360;i++){
        fsin[i]=(float)sin(i*3.1415926535/180);
        fcos[i]=(float)cos(i*3.1415926535/180);
    }
    
    return true;
}
/**
 * 1fごとに行う処理
 */
void VirtualPad::update(float dt){
    
}

/**
 *Padの表示開始
 *@param x
 *@param y
 */
void VirtualPad::startPad(int x,int y,int touch_id){    
    touchFlag = true;
    now_x = x;
    now_y = y;
    
    touchID = touch_id;
}

/**
 *Padの操作終了
 */
void VirtualPad::endPad(int touch_id){
    if(touch_id != touchID)return;
    
    //touchidの取得
    touchID = touch_id;

    /* タッチを離したら、パッドのつまみ位置に戻す */
    Size size = Director::getInstance()->getVisibleSize();
    padFront->setPosition(Point(size.width*VIRTUAL_PAD_POSITION_RATE_X, size.height*VIRTUAL_PAD_POSITION_RATE_Y));
    
    /* データの初期化 */
    d_x = 0;
    d_y = 0;
    angle = 0;
    way_x = 0;
    way_y = 0;
    now_r = 0;
}

/**
 *Padの表示更新
 *@param x
 *@param y
 */
void VirtualPad::update(int x,int y,int touch_id){
    //    if(drawFlag == false)return;
    if(touch_id != touchID)return;
    //移動量
    d_x = x-init_x;
    d_y = y-init_y;
    
    //角度
    angle = atan2f(d_y, d_x);
    //cos,sin
    way_x = cosf(angle);
    way_y = sinf(angle);
    now_r =sqrt(d_x*d_x + d_y*d_y);
    //円移動範囲外か
    if ( now_r > max_r) {
        //制限後のボタン位置
        x = (int)(init_x + max_r * cos(angle));
        y = (int)(init_y + max_r * sin(angle));
        now_r = max_r;
    }
    //位置をセット
    now_x = x;
    now_y = y;
    padFront->setPosition(Point(now_x, now_y));
    
}

/**
 *cos取得
 *@return パッドの角度から算出したcos
 */
float VirtualPad::getCosX(){
    return way_x;
}

/**
 *sin取得
 *@return パッドの角度から算出したsin
 */
float VirtualPad::getSinY(){
    return way_y;
}

/**
 *タッチした箇所から、バーチャルパッドを触ったか？を判別
 *@param location タッチした箇所
 *@return drawFlag trueなら触った
 */
bool VirtualPad::isTouchPad(Vec2 location){
    auto padBackRect =  this->padBack->getBoundingBox();
    
    //ここでは、パッドの下地の範囲内をタッチしたならパッドをタッチしたと判別
    if(padBackRect.containsPoint(location)){
        return true;
    }
    return false;
}


/**
 *バーチャルパッド使用中かどうかの取得
 *@return drawFlag trueなら表示中
 */
bool VirtualPad::isTouching(){
    return touchFlag;
}

/**
 *方向取得
 *@return int 4方向のうちどの方向かを返す.
 *方向は⬇内容で表す 5が中心
 *789
 *456
 *123
 */
int VirtualPad::get4Way(){
    
    //移動していないか
    if(d_x*d_x + d_y*d_y< VIRTUAL_PAD_MIN_RATE*VIRTUAL_PAD_MIN_RATE){
        return 5;
    }
    //上
    if(way_x<=fcos[45]&&way_x>=fcos[135]&&way_y>0){
        return 8;
    }
    //下
    if(way_x<=fcos[315]&&way_x>=fcos[225]&&way_y<0){
        return 2;
    }
    //左
    if(way_y<=fsin[135]&&way_y>=fsin[225]&&way_x<0){
        return 4;
    }
    //右
    if(way_y<=fsin[45]&&way_y>=fsin[315]&&way_x>0){
        return 6;
    }
    return -1;
}

/**
 *方向取得
 *@return int 8方向のうちどの方向かを返す.
 *方向は⬇内容で表す 5が中心
 *789
 *456
 *123
 */
int VirtualPad::get8Way(){
    //移動していないか
    if(d_x*d_x + d_y*d_y< VIRTUAL_PAD_MIN_RATE*VIRTUAL_PAD_MIN_RATE){
        return 5;
    }
    //右上
    if(way_x<=fcos[22]&&way_x>=fcos[68]&&way_y>0){
        return 9;
    }
    //上
    if(way_x<=fcos[68]&&way_x>=fcos[112]&&way_y>0){
        return 8;
    }
    //左上
    if(way_x<=fcos[112]&&way_x>=fcos[158]&&way_y>0){
        return 7;
    }
    //右下
    if(way_x<=fcos[338]&&way_x>=fcos[292]&&way_y<0){
        return 3;
    }
    //下
    if(way_x<=fcos[292]&&way_x>=fcos[248]&&way_y<0){
        return 2;
    }
    //左下
    if(way_x<=fcos[248]&&way_x>=fcos[202]&&way_y<0){
        return 1;
    }
    //左
    if(way_y<=fsin[158]&&way_y>=fsin[202]&&way_x<0){
        return 4;
    }
    //右
    if(way_y<=fsin[22]&&way_y>=fsin[338]&&way_x>0){
        return 6;
    }
    return -1;
}
/**
 *パッドの移動量からスピードを返す
 *@return スピードを返す
 */
int VirtualPad::getSpeed(){
    /* 座標用 */
    if(now_r >= 150){
        return 8;
    }
    
    if(now_r >= 110){
        return 6;
    }
    
    if(now_r >= 70){
        return 4;
    }
    
    if(now_r >= 30){
        return 2;
    }
    
    if(now_r >= 0){
        return 0;
    }
    ////     ここに来ることはないはず
    //    CCLOG("Errorlog in VirtualPad");
    return now_r;
}

```
# まとめ
一応、マルチタップに対応しています（検証はしてません）。バーチャルパッドを使うゲームはたくさんあると思いますので誰かの参考になれば、、、

# 参考

http://gwi.blog.shinobi.jp/cocos2d-x/cocos2d-x%20%E3%83%90%E3%83%BC%E3%83%81%E3%83%A3%E3%83%AB%E3%83%91%E3%83%83%E3%83%89%E3%81%AE%E5%AE%9F%E8%A3%85