LoginSignup
56
56

More than 5 years have passed since last update.

cocos2d-xでsocket.io(第五回cocos2d-x勉強会)

Last updated at Posted at 2014-06-23

先日、開催されました、
【#TechBuzz】第5回cocos2d-x勉強会
で登壇されました、
トランスリミット高場さん、工藤さん
全世界と戦える“脳トレ”アプリ「BrainWars
で話された内容と同じになります。

cocos2d-xでsocket.io

cocos2d-xではsocket.ioを利用することが出来ます。
その技術を利用して作られたゲームがBrainWarsとのこと。
このお話を聞いて、自分でもやってみました。
その際に直面した問題や、当日お話された問題点を共有出来ればと思います。

実装内容としては、クライアントから送信した文字列をオウム返しで、
socketサーバが返却するだけのシンプルな構成となっております。

cocos2d-x v3.1.1
nodejs v0.10.29
socket.io v0.9.9

で実装しています。

クライアント側実装(cocos2d-x)

HelloWorldScene.h

#ifndef __HELLOWORLD_SCENE_H__
#define __HELLOWORLD_SCENE_H__

#include "cocos2d.h"
#include <SocketIO.h>
#include "ui/cocosGUI.h"

using namespace cocos2d::network;
using namespace cocos2d::ui;

class HelloWorld : public cocos2d::Layer , public SocketIO::SIODelegate
{
private:
    int index;
    SIOClient* _client;
    TextField* editBox;

public:
    // there's no 'id' in cpp, so we recommend returning the class instance pointer
    static cocos2d::Scene* createScene();

    // socket.io eventのevent listener
    void onReceiveEvent(SIOClient* client , const std::string& data);

    // SIODelegate
    virtual void onConnect(SIOClient* client);
    virtual void onMessage(SIOClient* client, const std::string& data);
    virtual void onClose(SIOClient* client);
    virtual void onError(SIOClient* client, const std::string& data);

    // UI周り
    void textFieldEvent(Ref *pSender, TextField::EventType type);
    void addTalkPlayer(const std::string& str);
    void addTalkOther(const std::string& str);


    virtual bool init(); 

    // a selector callback
    void menuCloseCallback(cocos2d::Ref* pSender);

    // implement the "static create()" method manually
    CREATE_FUNC(HelloWorld);
};

#endif // __HELLOWORLD_SCENE_H__

HelloWorldScene.cpp

#include "HelloWorldScene.h"
#include "json/rapidjson.h"
#include "json/document.h"

USING_NS_CC;

// UI関係
const static int PLAYER_TEXT_X = 900;
const static int OTHER_TEXT_X = 50;
const static int TEXT_H = 60;

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()
{
    index = 0;

    // 略

    editBox = TextField::create("please input text" , "Meiryo" , 40);
    editBox->setContentSize(Size(visibleSize.width , 50));
    editBox->setPosition(Point(visibleSize.width / 2 , 30));
    editBox->addEventListener(CC_CALLBACK_2(HelloWorld::textFieldEvent, this));
    this->addChild(editBox);

    // ここでsocket.io connection開始。clientを持っておく
    _client = SocketIO::connect("http://xxx.compute.amazonaws.com:3000", *this);
    _client->on("hello", CC_CALLBACK_2(HelloWorld::onReceiveEvent, this));

    return true;
}

void HelloWorld::onConnect(SIOClient* client){
     // SocketIO::connect success
}

void HelloWorld::onMessage(SIOClient* client, const std::string& data){
     // SocketIO::send receive
}
void HelloWorld::onClose(SIOClient* client){
    // SocketIO::disconnect success
}
void HelloWorld::onError(SIOClient* client, const std::string& data){
    // SocketIO::failed
}

/**
 * serverからのemit("hello")をここでlisten
 */
void HelloWorld::onReceiveEvent(SIOClient* client , const std::string& data){

    rapidjson::Document doc;
    doc.Parse<rapidjson::kParseDefaultFlags>(data.c_str());
    rapidjson::Value &val = doc["args"];
    std::string value = val[rapidjson::SizeType(0)]["value"].GetString();

    addTalkOther(value);
};

//------------------------------------------------------
// ここからUI周り

/**
 * textFieldの処理
 */
void HelloWorld::textFieldEvent(Ref *pSender, TextField::EventType type)
{
    TextField* text;
    std::string sendText;
    switch (type)
    {
        // IMEが閉じた時 
        case TextField::EventType::DETACH_WITH_IME:
            text = (TextField*)pSender;

            sendText = "[{\"value\":\"" + text->getStringValue() + "\"}]";
            _client->emit("hello", sendText);
            addTalkPlayer(text->getStringValue());
            break;
        default:
            break;
    }
}

/**
 * プレイヤーUI
 */
void HelloWorld::addTalkPlayer(const std::string& str){
    Size size = Director::getInstance()->getVisibleSize();

    DrawNode* draw = DrawNode::create();

    int originalX = PLAYER_TEXT_X;
    int originalY = size.height - (TEXT_H * (index + 1));

    int x = originalX - 290;
    int y = originalY - 60;
    int w = 300;
    int h = 60;

    Vec2 points[] = {
        Vec2(x , y),
        Vec2(x + w , y),
        Vec2(x + w , y + h),
        Vec2(x , y + h),
    };

    this->addChild(draw);
    draw->drawPolygon(points, 4, Color4F(0 , 0.5, 0, 1), 1, Color4F(0,0,1,1));

    auto text = Text::create(str, "Meiryo", 40);
    text->setTextHorizontalAlignment(TextHAlignment::RIGHT);
    text->setAnchorPoint(Point(1.0 , 1.0));
    text->setPosition(Point(originalX , originalY));

    this->addChild(text);
    index++;
}

/**
 * その他UI
 */
void HelloWorld::addTalkOther(const std::string& str){
    Size size = Director::getInstance()->getVisibleSize();

    DrawNode* draw = DrawNode::create();

    int originalX = OTHER_TEXT_X;
    int originalY = size.height - (TEXT_H * (index + 1));

    int x = originalX - 10;
    int y = originalY - 60;
    int w = 300;
    int h = 60;

    Vec2 points[] = {
        Vec2(x , y),
        Vec2(x + w , y),
        Vec2(x + w , y + h),
        Vec2(x , y + h),
    };

    this->addChild(draw);
    draw->drawPolygon(points, 4, Color4F(0.5, 0, 0, 1), 1, Color4F(1,0,0,1));

    auto text = Text::create(str, "Meiryo", 40);
    text->setTextHorizontalAlignment(TextHAlignment::LEFT);
    text->setAnchorPoint(Point(0.0 , 1.0));
    text->setPosition(Point(originalX , originalY));
    text->setColor(Color3B(255, 255, 0));
    this->addChild(text);
    index++;
}


void HelloWorld::menuCloseCallback(Ref* pSender){ // 略 }

クライアント側はこのような実装になります。
SIODelegateを実装する事が最小限必須です。
本当はrapidjsonも使わずに実装したかったのですが、
パース部分がめんどくさかったので、使ってしまいました…。

サーバ側実装(nodejs+socket.io)

socket.js

server = require('http').Server();
var socketIO = require('socket.io');
var io = socketIO.listen(server);
io.sockets.on('connection', function(socket){
  console.log("connect");
  socket.on('hello', function(data){
      console.log("hello : " + data.value);
      io.sockets.emit('hello', { value: data.value });
  });
  socket.on('disconnect', function(){
      console.log("disconnect");
  });
  io.sockets.emit('hello', { value: "welcome" });
});
server.listen(3000);

実サービスにするならばexpressを利用して
プロジェクトとしてしっかりと管理するのが良いと思いますが、
今回はサンプルですので、最小限で。

実行

クライアント
スクリーンショット 2014-06-23 14.52.24.png

サーバ
socketio_log.png

まず起動するとwelcomeメッセージを受信します。
画面下部にある入力フォームに文言を入力すると、socket.ioを介してサーバに送信され、
サーバはそのイベントを受け取り、オウム返しで同じメッセージをクライアントに返却しています。

注意点やハマりどころ

json

ここはsocket.ioの基本かもしれないのですが、
基本的にjsonp_pollingになりますので、
受け渡しするパラメータがjson形式で無いと上手く送信されません。
例えば、

HelloWorldScene.cpp

_client->emit("event" , "aaaaa");

このように記述すると、送信しました!というログは出るのですが、
socketサーバ側では、受け取りました!というログが出ないという現象になります。

通信量

トランスリミットの工藤さんが登壇時に話された内容です。
cocos2d-xのsocket.ioは内部的にはwebSocketになっています。
そして、一度の通信では"2KB"前後しかやりとりすることが出来ず、
それ以上になると2度の通信に分割されてしまうそうです。

通信量を減らすのが最も良いですが、どうしてもそのように出来ない場合は
分割を判定したり、結合したりする仕組みが必要になります。

socket.ioのバージョン

現在は1.0.4までバージョンアップしています。
が、最新バージョンを使用すると、上手く行かなくなります。

URL + /socket.io/1

というような通信の結果が404となってしまいます。
これを回避するには、

transport=polling

というパラメータを付加するという手があるのですが、
これを付加してもcocos2d-x側はどうも上手く行きません。
(connectionは貼れるが、Heart Beatに失敗して、即切断される)

なので、socket.ioはv0.9.x以下を利用するのがオススメです。

node.js

make長いです!
(すっかり忘れてました)

spine/Json.hを使わない

パース出来るがエンコード出来なかったりするようです。
picoJsonrapidJson
オススメです。

32bitと64bitの違いによる動きの違い

cocos2d-xは32bit系と64bit系で実装が違うため、
32bitではソケットが繋がるが、
64bitではソケットが繋がらない
というcocos2d-xのVersionがあるようです。
最新のcocos2d-xを利用するか、修正差分の反映が必要となります。

56
56
4

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
56
56