cocos2d-xでsocket.ioを使う iOS版

Cocos2d-xでsocket.ioを使う方法について調べました。
Cocos2d-xには標準でSocketIOクラスが用意されていますが、諸々の理由により使わずに、
iOS/Androidそれぞれ個別に設定を行います。

まずはiOSから。

iOSはAZSocketIOを使用します。AZSocketIOはcocoapodsを使用してインストールするのが一般的のようですが、
Cocos2d-xでcocoapodsを使えるようにするのも面倒そうなので、手動で設定します。

今回使用したバージョン

  • Cocos2d-x 3.9
  • AZSocketIO 0.0.6
  • SocketRocket 0.4.2
  • AFNetworking 2.6.3 (3.1.0だとビルドできませんでした)


ライブラリをセットアップ
まずはAZSocketIOと、それに必要なAFNetworking、SocketRocketをCocos2d-xプロジェクトに組み込みます。

下記サイトからそれぞれダウンロードしてzipを解凍します。

解凍したら、それぞれの中にある下記フォルダをCocos2d-xプロジェクトのproj.ios_macなどにコピーして、Xcode上でプロジェクトに追加します。

  • AFNetworking
  • SocketRocket
  • AZSocketIO

次に、Xcode上の「Compile Sources」で、AZ*、AF*、SR*の全てに-fobjc-arcを設定します。

「Link Binary With Libraries」に下記を追加します。

  • SystemConfiguration
  • CFNetwork
  • MobileCoreServices
  • libicucore.tbd

ここまでで、ビルドができるようになるかと思います。


実装
socket.ioサーバーへの接続や切断などはObjective-Cのコードで記述するので、NativeCodeLauncherというクラスに実装します。
また、接続完了した、切断した、エラーが発生したなどのイベントを受け取ることができますが、これらを受け取った時に
Cocos2d-x側でなにかしらのアクションを行えるよう、SocketManagerというシングルトンクラスを作っておきます。
Cocos2d-xのプログラムからSocket.io関連の何かをする場合は全てSocketManagerを通すという設計にします。

NativeCodeLauncher.h

#include <stddef.h>
#include <string>

namespace Cocos2dExt {
    class NativeCodeLauncher
    {
    public:
		// Socket.IO関連
		static void connectToSocketIO(const char* host,int port);
		static void emitToSocketIO(const char* event,const char* message);
		static void disconnectFromSocketIO();
    };
} // end of namespace Cocos2dExt

NativeCodeLauncher.mm

#include "NativeCodeLauncher.h"
#include "NativeCodeLauncher_objc.h"

// Socket.IO関連処理
static void static_connectToSocketIO(const char* host,int port)
{
	[[NativeCodeLauncher sharedManager] connectToSocketIO: [NSString stringWithUTF8String:host] port: port];
}
static void static_emitToSocketIO(const char* event,const char* message)
{
	[[NativeCodeLauncher sharedManager] emitToSocketIO:[NSString stringWithUTF8String:event]
											   message:[NSString stringWithUTF8String:message]];
}
static void static_disconnectFromSocketIO()
{
	[[NativeCodeLauncher sharedManager] disconnectFromSocketIO];
}

namespace Cocos2dExt
{
	// ソケット接続
	void NativeCodeLauncher::connectToSocketIO(const char* host,int port)
	{
		static_connectToSocketIO(host,port);
	}
	// イベントとメッセージをソケットに送信
	void NativeCodeLauncher::emitToSocketIO(const char* event,const char* message)
	{
		static_emitToSocketIO(event,message);
	}
	// ソケット切断
	void NativeCodeLauncher::disconnectFromSocketIO()
	{
		static_disconnectFromSocketIO();
	}
}

NativeCodeLauncher_objc.h

@interface NativeCodeLauncher : NSObject{
	
}

// シングルトンオブジェクトを返す
+ (NativeCodeLauncher *)sharedManager;

// Socket.IO
-(void)connectToSocketIO:(NSString *)host port:(int) port;
-(void)emitToSocketIO:(NSString *)event message:(NSString *)message;
-(void)disconnectFromSocketIO;

@end

NativeCodeLauncher_objc.mm

#import "NativeCodeLauncher_objc.h"
//#import "EAGLView.h"
#import "AppController.h"
#import "RootViewController.h"
#import "AZSocketIO.h"
#import "SocketManager.hpp"

@implementation NativeCodeLauncher
{
	AZSocketIO *socketIO;
}

// シングルトンオブジェクト
static NativeCodeLauncher *sharedData_ = nil;

// シングルトンオブジェクトを返す
+ (NativeCodeLauncher *)sharedManager
{
	@synchronized(self)
	{
		if(!sharedData_)
		{
			sharedData_ = [NativeCodeLauncher new];
		}
	}
	
	return sharedData_;
}
- (id)init
{
	self = [super init];
	if(self)
	{
		
	}
	
	return self;
}

// ソケット接続
-(void)connectToSocketIO:(NSString *)host port:(int) port
{
	// ホストとポート番号を指定してAZSocketIOインスタンス生成
	socketIO = [[AZSocketIO alloc] initWithHost:host andPort:[NSString stringWithFormat:@"%d",port] secure:NO];
	socketIO.reconnectionDelay = 1.0;
	socketIO.reconnectionLimit = 4;
	socketIO.maxReconnectionAttempts = 1;
	
	// 接続開始
	[socketIO connectWithSuccess:^{
		NSLog(@"Socket接続成功.");
		// 接続成功
		SocketManager::getInstance()->onConnected();
	} andFailure:^(NSError *error) {
		NSLog(@"Socket接続失敗. error: %@", error);
		// 接続失敗
		NSString *errorMessage = [NSString stringWithFormat:@"%@", error];
		SocketManager::getInstance()->onError([errorMessage UTF8String]);
	}];
	
	// イベントを受けとった時
	[socketIO setEventReceivedBlock:^(NSString *eventName, id data) {
		// ただのキャストではうまくいかない
		NSString *message = [NSString stringWithFormat:@"%@",data[0]];
		NSLog(@"Event : %@, message : %@",eventName,message);
		// イベントとメッセージを渡して仕分け
		SocketManager::getInstance()->receiveMessage([eventName UTF8String], [message UTF8String]);
	}];
	
	// エラーを受信したときに実行されるBlocks
	[socketIO setErrorBlock:^(NSError *error) {
		NSLog(@"error: %@", error);
		// エラー
		NSString *errorMessage = [NSString stringWithFormat:@"%@", error];
		SocketManager::getInstance()->onError([errorMessage UTF8String]);
	}];
	
	// 切断されたときに実行されるBlocks
	[socketIO setDisconnectedBlock:^{
		NSLog(@"Socket切断完了.");
		// 切断
		SocketManager::getInstance()->onDisconnected();
	}];
}
// イベントとメッセージをソケットに送信
-(void)emitToSocketIO:(NSString *)event message:(NSString *)message
{
	[socketIO emit:event args:message error:NULL];
}
// ソケット切断
-(void)disconnectFromSocketIO
{
	[socketIO disconnect];
}

@end

SocketManager.hpp

#include "cocos2d.h"

USING_NS_CC;

class SocketManager
{
private:
	static SocketManager* mManager;
public:
	SocketManager();
	static SocketManager* getInstance();
	
	// ソケット接続/切断
	void connect();
	void disconnect();
	// 接続された
	void onConnected();
	// 切断された
	void onDisconnected();
	// エラーが発生した
	void onError(std::string error);
	// メッセージを送信
	void emitMessage(std::string event,std::string message);
	// メッセージを受け取った時の処理
	void receiveMessage(std::string event,std::string message);
};

SocketManager.cpp

#include "SocketManager.hpp"
#include "NativeCodeLauncher.h"

SocketManager* SocketManager::mManager = NULL;

#pragma mark - 初期化

SocketManager::SocketManager()
{
	
}

SocketManager* SocketManager::getInstance()
{
	if(mManager == NULL)
	{
		mManager = new SocketManager();
	}
	
	return mManager;
}

// Socket接続
void SocketManager::connect()
{
	// 接続先のホスト、ポートは変更
	Cocos2dExt::NativeCodeLauncher::connectToSocketIO("192.168.0.1", 3150);
}
// Socket切断
void SocketManager::disconnect()
{
	Cocos2dExt::NativeCodeLauncher::disconnectFromSocketIO();
}

// Socket接続された
void SocketManager::onConnected()
{
	// Cocos2d-xのスレッドに実行させる。これがないと、Androidの描画関連がうまくいかないことがある。
	Director::getInstance()->getScheduler()->performFunctionInCocosThread([=](){
		// イベントを通知
		Director::getInstance()->getEventDispatcher()->dispatchCustomEvent("SocketConnected");
	});
}
// Socket切断された
void SocketManager::onDisconnected()
{
	// Cocos2d-xのスレッドに実行させる。これがないと、Androidの描画関連がうまくいかないことがある。
	Director::getInstance()->getScheduler()->performFunctionInCocosThread([=](){
		// イベントを通知
		Director::getInstance()->getEventDispatcher()->dispatchCustomEvent("SocketDisconnected");
	});
}
// Socketでエラーが発生した
void SocketManager::onError(std::string error)
{
	// Cocos2d-xのスレッドに実行させる。これがないと、Androidの描画関連がうまくいかないことがある。
	Director::getInstance()->getScheduler()->performFunctionInCocosThread([=](){
		// イベントを通知
		Director::getInstance()->getEventDispatcher()->dispatchCustomEvent("SocketError");
	});
}
// イベントを送信
void SocketManager::emitMessage(std::string event, std::string message)
{
	Cocos2dExt::NativeCodeLauncher::emitToSocketIO(event.c_str(),message.c_str());
}
// イベントを受信した
void SocketManager::receiveMessage(std::string event, std::string message)
{
	// Cocos2d-xのスレッドに実行させる。これがないと、Androidの描画関連がうまくいかないことがある。
	Director::getInstance()->getScheduler()->performFunctionInCocosThread([=](){
		// イベントを通知
		EventCustom customEvent("SocketEventReceived");
		auto messageValue = Value(message);
		customEvent.setUserData(&messageValue);
		// 受け取った内容をEventCustomに入れて通知
		Director::getInstance()->getEventDispatcher()->dispatchEvent(&customEvent);
	});
}