WonderPlanet DEVELOPER BLOG

ワンダープラネットの開発者ブログです。モバイルゲーム開発情報を発信。

Cocos2d-xのゲームを遠隔操作してみよう(コマンド追加編)

エンジニアブログ担当の岩原です。
今回は前回紹介した遠隔操作機能にコマンドを追加したいと思います。

用意するもの

・新規Cocos2d-x v3.x C++プロジェクト(サンプルコードはv3.3を使用)
・TCPクライアント
・開発マシンと通信できる端末とそのIPアドレス。

今回追加するコマンド

指定したノードを指定箇所に移動するコマンドを作ってみましょう。
ノードの指定はタグ値(int)で行うこととし、
コマンドの形式は

nodemove tag x y  

とします。

コマンドの追加

CCConsoleクラスのaddCommand関数を使用するとコマンドを追加できます。
追加はだいたいこんな感じで行います。

void HelloWorld::addNodeMoveCommand()  
{  
    cocos2d::Console::Command cmd;  
    cmd.name = "nodemove";  
    cmd.help = "the specified node move. Args: [tag x y] ";  
      
    cmd.callback = [=](int fd, const std::string& args) {  
        //ここに処理を書く  
    };  
    Director::getInstance()->getConsole()->addCommand(cmd);  
}  

注意

コマンドの実行はネットワークスレッド上で行われます。
Cocos2d-xのUIスレッドではないため、多少注意が必要です。

それでは、ここにノードの移動処理を書いてみます。

ラベルの移動

HelloWorldSceneに初めから存在しているラベルにタグをつけます。

// on "init" you need to initialize your instance  
bool HelloWorld::init()  
{  
//省略  
  
    // position the label on the center of the screen  
    label->setPosition(Vec2(origin.x + visibleSize.width/2,  
                            origin.y + visibleSize.height - label->getContentSize().height));  
    //ラベルにタグをつける  
    label->setTag(9999);  
  
    // add the label as a child to this layer  
    this->addChild(label, 1);  

コマンド引数パース用split関数の追加(補足)

コマンド引数パース用のsplit関数を追加します。
なお、この関数はCCConsoleに定義されているものと同じものです。

static std::vector<std::string> &split(const std::string &s, char delim, std::vector<std::string> &&elems) {  
    std::stringstream ss(s);  
    std::string item;  
    while (std::getline(ss, item, delim)) {  
        elems.push_back(item);  
    }  
    return elems;  
}  

実装

まずはコンソールを有効にし、コマンドを追加します。

// on "init" you need to initialize your instance  
bool HelloWorld::init()  
{  
省略  
    // add "HelloWorld" splash screen"  
    auto sprite = Sprite::create("HelloWorld.png");  
  
    // position the sprite on the center of the screen  
    sprite->setPosition(Vec2(visibleSize.width/2 + origin.x, visibleSize.height/2 + origin.y));  
  
    // add the sprite as a child to this layer  
    this->addChild(sprite, 0);  
      
    //コンソールを有効にし、コマンドを追加する。  
    this->addNodeMoveCommand();  
    cocos2d::Director::getInstance()->getConsole()->listenOnTCP(6010);  
    return true;  
}  

コマンドの実装はこんな感じです。

void HelloWorld::addNodeMoveCommand()  
{  
    cocos2d::Console::Command cmd;  
    cmd.name = "nodemove";  
    cmd.help = "the specified node move. Args: [tag x y] ";  
      
    cmd.callback = [=](int fd, const std::string& args) {  
        std::vector<std::string> argsv;  
        split(args, ' ', argsv);  
        if(argsv.size() < 3){  
            //引数の数が足りない場合は何もしない  
            return;  
        }  
        int tag = atoi(argsv[0].c_str());  
        float x = atof(argsv[1].c_str());  
        float y = atof(argsv[2].c_str());  
        Node* target = this->getChildByTag(tag);  
        target->setPosition(Vec2(x,y));  
    };  
    Director::getInstance()->getConsole()->addCommand(cmd);  
}  

これでコンソール側の実装は完了です。
あとは、TCPクライアントにて、以下のコマンドを打つと指定座標へ「Hello World」ラベルが移動します。

nodemove 9999 500 500  

IMG_0225

IMG_0226

全体のコード

#ifndef __HELLOWORLD_SCENE_H__  
#define __HELLOWORLD_SCENE_H__  
  
#include "cocos2d.h"  
  
class HelloWorld : public cocos2d::Layer  
{  
private:  
      
    void addNodeMoveCommand();  
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);  
      
      
    // implement the "static create()" method manually  
    CREATE_FUNC(HelloWorld);  
};  
  
#endif // __HELLOWORLD_SCENE_H__  
#include "HelloWorldScene.h"  
#include "CCConsole.h"  
  
USING_NS_CC;  
  
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;  
    }  
      
    Size visibleSize = Director::getInstance()->getVisibleSize();  
    Vec2 origin = Director::getInstance()->getVisibleOrigin();  
  
    /////////////////////////////  
    // 2. add a menu item with "X" image, which is clicked to quit the program  
    //    you may modify it.  
  
    // add a "close" icon to exit the progress. it's an autorelease object  
    auto closeItem = MenuItemImage::create(  
                                           "CloseNormal.png",  
                                           "CloseSelected.png",  
                                           CC_CALLBACK_1(HelloWorld::menuCloseCallback, this));  
      
    closeItem->setPosition(Vec2(origin.x + visibleSize.width - closeItem->getContentSize().width/2 ,  
                                origin.y + closeItem->getContentSize().height/2));  
  
    // create menu, it's an autorelease object  
    auto menu = Menu::create(closeItem, NULL);  
    menu->setPosition(Vec2::ZERO);  
    this->addChild(menu, 1);  
  
    /////////////////////////////  
    // 3. add your codes below...  
  
    // add a label shows "Hello World"  
    // create and initialize a label  
      
    auto label = Label::createWithTTF("Hello World", "fonts/Marker Felt.ttf", 24);  
      
    // position the label on the center of the screen  
    label->setPosition(Vec2(origin.x + visibleSize.width/2,  
                            origin.y + visibleSize.height - label->getContentSize().height));  
    //ラベルにタグをつける  
    label->setTag(9999);  
  
    // add the label as a child to this layer  
    this->addChild(label, 1);  
  
    // add "HelloWorld" splash screen"  
    auto sprite = Sprite::create("HelloWorld.png");  
  
    // position the sprite on the center of the screen  
    sprite->setPosition(Vec2(visibleSize.width/2 + origin.x, visibleSize.height/2 + origin.y));  
  
    // add the sprite as a child to this layer  
    this->addChild(sprite, 0);  
      
    //コンソールを有効にし、コマンドを追加する。  
    this->addNodeMoveCommand();  
    cocos2d::Director::getInstance()->getConsole()->listenOnTCP(6010);  
    return true;  
}  
  
  
void HelloWorld::menuCloseCallback(Ref* pSender)  
{  
#if (CC_TARGET_PLATFORM == CC_PLATFORM_WP8) || (CC_TARGET_PLATFORM == CC_PLATFORM_WINRT)  
    MessageBox("You pressed the close button. Windows Store Apps do not implement a close button.","Alert");  
    return;  
#endif  
  
    Director::getInstance()->end();  
  
#if (CC_TARGET_PLATFORM == CC_PLATFORM_IOS)  
    exit(0);  
#endif  
}  
  
  
static std::vector<std::string> &split(const std::string &s, char delim, std::vector<std::string> &elems) {  
    std::stringstream ss(s);  
    std::string item;  
    while (std::getline(ss, item, delim)) {  
        elems.push_back(item);  
    }  
    return elems;  
}  
  
void HelloWorld::addNodeMoveCommand()  
{  
    cocos2d::Console::Command cmd;  
    cmd.name = "nodemove";  
    cmd.help = "the specified node move. Args: [tag x y] ";  
      
    cmd.callback = [=](int fd, const std::string& args) {  
        std::vector<std::string> argsv;  
        split(args, ' ', argsv);  
        if(argsv.size() < 3){  
            //引数の数が足りない場合は何もしない  
            return;  
        }  
        int tag = atoi(argsv[0].c_str());  
        float x = atof(argsv[1].c_str());  
        float y = atof(argsv[2].c_str());  
        Node* target = this->getChildByTag(tag);  
        target->setPosition(Vec2(x,y));  
    };  
    Director::getInstance()->getConsole()->addCommand(cmd);  
}  
[/cpp]