ガイド環境
まず、新しいCocosCreatorプロジェクトを作成します。Cocos Creator Dashboard > New Project > Empty Projectを選択し、新しいプロジェクトを作成します。
次に、作成されたプロジェクトにGameAnvilコネクタを追加します。GameAnvilコネクタはこちらからダウンロードできます。ダウンロードしたファイルを解凍し、assetsの下にフォルダを作成して格納します。この時、次のような通知が表示されたらNoをクリックします。
GameAnvilコネクタは、protobufjsを使用するので、GameAnvilコネクタをプロジェクトに含めると、上記のようなエラーが発生することがあります。
protobufjsはNode.jsのinstall機能を利用してプロジェクトに含めます。VSCode端末で、次のコードを入力して、package.jsonファイルを作成します。
npm init
上記のコードを入力すると、package.jsonファイルの作成プロセスが始まるので、パッケージの名前を入力します。この時、単にEnterを押すと、デフォルト値のプロジェクト名を使用します。その後に入力する値も、Enterキーを押すと、デフォルト値を使用します。詳細については、こちらを参照してください。
デフォルト値で作成したpackage.jsonファイルを開くと、次のようになっています。
{
"name": "newproject",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC"
}
package.jsonファイルを作成したら、ターミナルで次のコードを入力してprotobufjsをインストールします。
npm i protobufjs --save
インストールが完了したら、プロジェクトフォルダの下に次のようなフォルダが作成されます。
package.jsonファイルのdependenciesにgameanvil_connectorが追加されていることも確認できます。
{
"name": "newproject",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"protobufjs": "^6.10.2"
}
}
Internet Explorerなど、旧バージョンのブラウザをサポートするにはcore-js, regenerator-runtimeをインストールします。
npm i core-js regenerator-runtime
コネクタを使用するには、コネクタとProtocolManagerモジュールをインポートします(import)。
import { Connector, ProtocolManager} from 'GameAnvil/gameanvil';
ゲームで使用するメッセージを登録し、コネクタオブジェクトを作成します。
export default class GameAnvilManager {
private connector: Connector;
constructor() {
// プロトコル登録。
ProtocolManager.RegisterProtocol(0, GameMessages);
// コネクタ作成。
this.connector = Connector.Create();
}
}
サーバーとやりとりするメッセージを処理するためUpdate()関数を定期的に呼び出す必要があります。Update()関数の呼び出し周期は自由に設定しても構いませんが、呼び出していない場合は、サーバーからメッセージを受信しても、この通知を受け取れません。
setInterval(() => { this.connector.Update(); }, 10);
次のようなシングルトンクラスを作成して使用すると便利です。
/*
Internet Explorerなどの古いブラウザでコネクタを使用するには
core-js、regenerator-runtimeなどのプラグインを使用する必要がある。
*/
import 'core-js';
import 'regenerator-runtime';
import { Connector, ProtocolManager } from 'GameAnvil/gameanvil';
export default class GameAnvilManager {
private static manager: GameAnvilManager;
private connector: Connector;
private constructor() {
// プロトコル登録。
ProtocolManager.RegisterProtocol(0, GameMessages);
// コネクタ作成。
this.connector = Connector.Create();
// メッセージループ。10msごとに呼び出し。
let updater = setInterval(() => { this.connector.Update(); }, 10);
}
static GetInstance() {
if (this.manager == null) {
this.manager = new GameAnvilManager();
}
return this.manager;
}
GetConnectionAgent() {
return this.connector.GetConnectionAgent();
}
GetUserAgent(serviceName: string, subId: string) {
return this.connector.GetUserAgent(serviceName, subId);
}
CreateUserAgent(serviceName: string, subId: string) {
return this.connector.CreateUserAgent(serviceName, subId);
}
}
サーバー接続と認証は、ConnectionAgentを利用して行います。ConnectionAgentはGameAnvilサーバーのコネクションノードに関連する作業を担当します。接続(Connect())、認証(Authentication())など、基本セッション管理機能とチャンネルリストなどを提供し、サーバーの実装に基づいてチャンネル情報を提供したり、ユーザー定義メッセージを送受信できます。ConnectionAgentは、コネクタが初期化されるときに自動的に作成され、Connector.GetConnectionAgent()関数を用いて取得できます。ConnectionAgentはコネクタが初期化される時に自動的に作成され、Connector.GetConnectionAgent()関数を利用して取得できます。
let connection = GameAnvilManager.GetInstance().GetConnectionAgent();
// サーバー接続
// Connect(ipAdress: string, callback?: (agent: ConnectionAgent, resultCode: ResultCodeConnect) => void): void;
// ipAddress :接続するサーバーアドレス。
// callback :結果を受け取るコールバック。(オプション)
// agent : Connect()を呼び出したConnectionAgentオブジェクト。
// resultCode : Connect結果。
connection.Connect(
this.edtIPAddress.string,
(agent, resultCode) => {
console.log("Connect : " + ResultCodeConnect[resultCode]);
if (ResultCodeConnect.CONNECT_SUCCESS == resultCode) {
// 接続成功
} else {
// 接続失敗
}
}
);
let connection = GameAnvilManager.GetInstance().GetConnectionAgent();
// 認証。
// Authenticate(deviceId: string, accountId: string, password: string, payload?: Payload, callback?: (agent: ConnectionAgent, resultCode: ResultCodeAuth, loginedUserInfoList: Array<LoginedUserInfo>, message: string, payload: Payload) => void): void;
// deviceId :重複接続をチェックするために使用。
// accountId :ユーザーアカウント。
// password :パスワード。
// payload :追加で必要なデータがある場合に使用。(オプション)
// callback :結果を受け取るコールバック。(オプション)
// agent : Authenticateを呼び出したConnectionAgentオブジェクト。
// resultCode : Authenticate結果
// loginedUserInfoList :このアカウントIdを利用してログイン中のユーザー情報。
// message :サーバーから送る追加メッセージ。
// payload:サーバーコンテンツから送る追加データ。
connection.Authenticate(
"deviceId",
this.edtAccountId.string,
this.edtPassword.string,
null,
(agent, resultCode, loginedUserInfoList: Array<LoginedUserInfo>, message: string, payload: Payload) => {
if (ResultCodeAuth.AUTH_SUCCESS == resultCode) {
// 認証成功
} else {
// 認証失敗
}
}
);
接続を終了する時はDisconnect()を呼び出します。
let connection = GameAnvilManager.GetInstance().GetConnectionAgent();
// 接続終了
// Disconnect(reason: string, callback?: (agent: ConnectionAgent, resultCode: ResultCodeDisconnect, reason: string) => void, force: boolean, payload: Payload): void;
// reason:接続終了理由。Disconnect()を呼び出すケースが複数ある場合、コールバックでそれぞれのケースを区別するために使用。
// callback :結果を受け取る。(オプション)
// agent : Disconnect()を呼び出したConnectionAgentオブジェクト。
// resultCode : Disconnect()の結果。
// reason :接続終了理由。
// force :サーバーで強制終了するかどうか。重複接続、Kick、AdminKickなど。
// payload :サーバーコンテンツから送る追加データ。
connection.Disconnect("click Disconnect");
Disconnect()の呼び出しの他に、ネットワーク環境が原因で接続終了する場合に対処するためにConnectionAgent.AddOnDisconnect()を利用して個別にコールバックを登録して使用します。
IConnectionListenerインタフェースを実装して登録すると、ConnectionAgentのすべての通知を受け取ることができます。
class ConnectionListener implements IConnectionListener{
OnConnect?(connection: ConnectionAgent, resultCode: ResultCodeConnect): void { }
OnAuthentication?(connection: ConnectionAgent, resultCode: ResultCodeAuth, loginedUserInfoList: Array<LoginedUserInfo>, message: string, payload: Payload): void { }
OnChannelList?(connection: ConnectionAgent, resultCode: ResultCodeChannelList, channelIdList: Array<string>): void { }
OnChannelInfo?(connection: ConnectionAgent, resultCode: ResultCodeChannelInfo, channelInfoList: Array<Payload>): void { }
OnDisconnect?(connection: ConnectionAgent, resultCode: ResultCodeDisconnect, reason: string, force: boolean, payload: Payload): void { }
OnErrorCommand?(connection: ConnectionAgent, errorCode: ErrorCode, msgName: string): void { }
}
let connection = GameAnvilManager.GetInstance().GetConnectionAgent();
connection.AddListener(new ConnectionListener());
// address :サーバーアドレス
connection.Connect(address);
// ConnectionListener.OnConnectでレスポンス
// deviceId :重複接続をチェックするために使用。
// accountId :ユーザーアカウント。
// password :パスワード。
// payload :追加で必要なデータがある場合に使用。(オプション)
connection.Authenticate(deviceId, accountId, password, payload);
// ConnectionListener.OnAuthenticateでレスポンス
ログイン/ログアウトと基本的な機能は、UserAgentを介して利用できます。UserAgentは、GameAnvilサーバーのゲームのノードに関連するタスクを担当します。ログイン(Login())、ログアウト(Logout())とルーム管理などの基本機能を提供し、サーバーの実装に応じて、ユーザー定義機能をさらに提供することもできます。UserAgentを使用するには、Connector.CreateUserAgent()関数を用いて、新しいUserAgentを作成する必要があります。 ServiceIdとSubIdに区分されている複数のUserAgentを作成することができ、作成された各UserAgentは独立して使用できます。作成されたUserAgentはコネクタで内部的に管理され、Connector.GetUser()関数を用いて再利用できます。
// serviceId : UserAgentが使用するserviceId。
// subId :サービスごとにUserAgentを識別することができる固有のID。サーバーの実装に応じて使用しないこともある。
let user = GameAnvilManager.GetInstance().GetUserAgent(this.ServiceName);
if (user == null) {
user = GameAnvilManager.GetInstance().CreateUserAgent(this.ServiceName);
}
let user = GameAnvilManager.GetInstance().GetUserAgent(this.ServiceName);
// Serviceにログインします。
// Login(userType: string, payload?: Payload, channelId?: string, callback?: (agent: UserAgent, resultCode: ResultCodeLogin, loginInfo: LoginInfo) => void): void;
// userType :サーバーに登録されたUserType。
// payload :追加的で必要なデータがある場合に使用。(オプション)
// channelId :使用するチャンネル。(オプション。入力していない場合は、空白文字列として処理され、サーバーに空白の文字列のチャンネルが設定されている必要があります。)
// callback :結果を受け取るコールバック。(オプション)
// agent : Login()を呼び出したUserAgentオブジェクト。
// resultCode : Login()結果
// loginInfo:ログインしているユーザーの情報。
user.Login(this.UserType, null, this.editBoxChannel.string, (UserAgent, resultCode, loginInfo) => {
if(resultCode == ResultCodeLogin.LOGIN_SUCCESS){
// ログイン成功
}else{
// ログイン失敗
}
});
IUserListenerインターフェイスを実装して登録すると、UserAgentのすべての通知を受け取ることもできます。
class UserListener : IUserListener{
OnLogin?(user: UserAgent, resultCode: ResultCodeLogin, loginInfo: LoginInfo): void { }
OnLogout?(user: UserAgent, resultCode: ResultCodeLogout, force: boolean, payload: Payload): void { }
OnMatchRoom?(user: UserAgent, resultCode: ResultCodeMatchRoom, roomId: string, payload: Payload): void { }
OnCreateRoom?(user: UserAgent, resultCode: ResultCodeCreateRoom, roomId: string, payload: Payload): void { }
OnJoinRoom?(user: UserAgent, resultCode: ResultCodeJoinRoom, roomId: string, payload: Payload): void { }
OnNamedRoom?(user: UserAgent, resultCode: ResultCodeNamedRoom, roomId: string, payload: Payload): void { }
OnLeaveRoom?(user: UserAgent, resultCode: ResultCodeLeaveRoom, force: boolean, roomId: string, payload: Payload): void { }
OnMatchUserStart?(user: UserAgent, resultCode: ResultCodeMatchUserStart, payload: Payload): void { }
OnMatchUserCancel?(user: UserAgent, resultCode: ResultCodeMatchUserCancel): void { }
OnMatchUserDone?(user: UserAgent, resultCode: ResultCodeMatchUserDone, created: boolean, roomId: string, payload: Payload): void { }
OnMatchUserTimeout?(user: UserAgent): void { }
OnMatchPartyStart?(user: UserAgent, resultCode: ResultCodeMatchPartyStart, payload: Payload): void { }
OnMatchPartyCancel?(user: UserAgent, resultCode: ResultCodeMatchPartyCancel): void { }
OnMoveChannel?(user: UserAgent, resultCode: ResultCodeMoveChannel, force: boolean, channelId: string, payload: Payload): void { }
OnSnapshot?(user: UserAgent, resultCode: ResultCodeSnapshot, paload: Payload): void { }
OnErrorCommand?(user: UserAgent, errorCode: ErrorCode, msgName: string): void { }
OnNotice?(user: UserAgent, message: string): void { }
OnAdminKickoutNoti?(user: UserAgent, message: string): void { }
}
let user = connector.GetUserAgent(serviceId, subId);
user.AddUserListener(new UserListener())
// payload :サーバーに伝達する追加の値。サーバーの実装に応じて使用しないこともある。
// channelId : UserAgentがログインするチャンネルID。サーバーの実装に応じて使用しないこともある。
user.Login(payload, channelId);
// UserListener.OnLoginでレスポンス
user.Logout();
// UserListener.OnLogoutでレスポンス
user.CreateRoom();
// UserListener.OnCreateRoomでレスポンス
user.MatchRoom();
// UserListener.OnMatchRoomでレスポンス
user.LeaveRoom();
// UserListener.OnLeaveRoomでレスポンス
...
ConnectionAgent、UserAgentの基本機能に加え、Request()とSend()を利用してメッセージをサーバーに送信できます。メッセージを送信するには、メッセージを作成し、登録するプロセスが必要です。
GameAnvilはデフォルトのメッセージプロトコルにProtocolBufferを使用しています。.protoファイルにメッセージを定義し、pbjsで実際のクラスソースコードを作成します。そして、GameAnvilで使用する追加コードを、作成されたコードに挿入します。こうして作成されたソースコードをプロジェクトに追加して使用できます。
追加コードを挿入するにはCodeInserter
をインストールする必要があります。CodeInserter
はこちらからダウンロードできます。ダウンロードしたファイルを解凍し、プロジェクトルートの下(assetsフォルダの外に)にフォルダを作成して格納します。
CodeInserter
は、acornを使用します。ターミナルで次のコードを入力してacornをインストールします。
npm i acorn@5.5.3 --save-dev
package.jsonのdevDependenciesにacornが追加されたことを確認できます。
{
"name": "newproject",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"protobufjs": "^6.10.2"
},
"devDependencies": {
"acorn": "^5.5.3"
}
}
メッセージを作成してみます。まず、assetsフォルダの下にprotocolsフォルダを作成し、次のようにmessages.protoファイルを作成します。
// messages.proto
syntax = "proto3";
package Messages;
message SampleRequest
{
string msg = 1;
}
message SampleResponse
{
repeated string msgs = 1;
}
message SampleSend
{
string msg = 1;
}
message SampleReceive
{
repeated string msgs = 1;
}
次にmessages.protoファイルをコンパイルするためのスクリプトをpackage.jsonに追加します。
{
"name": "newproject",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"messages.js": "pbjs --force-message --no-verify --no-convert --no-delimited -t static-module -w default -r base -o assets/protocols/messages.js assets/protocols/messages.proto",
"messages.codeInsert": "node codeInserter/CodeInserter assets/protocols/messages.js",
"messages.ts": "pbts --no-comments -o assets/protocols/messages.d.ts assets/protocols/messages.js",
"messages": "npm run messages.js && npm run messages.codeInsert && npm run messages.ts",
},
"author": "",
"license": "ISC",
"dependencies": {
"protobufjs": "^6.10.2"
},
"devDependencies": {
"acorn": "^5.5.3"
}
}
そして、ターミナルで次のコードを入力してメッセージを作成します。
npm run messages
次のようにmessage.js
、message.d.ts
ファイルが作成されたことを確認できます。
新たに作成したメッセージを使用するには、使用するメッセージをProtocolManagerにサーバーと同じ値であらかじめ登録する必要があります。登録していなかったり、サーバーと異なる場合、動作しなかったり、誤動作や例外が発生することがあります。
// サーバーと同じ値で登録する必要がある。
ProtocolManager.RegisterProtocol(0, message);
RequestPb()でメッセージを送信すると、サーバーの応答を待ちます。サーバーの応答を待っている間、追加RequestPb()は、キューに格納され、サーバーの応答を処理した後、順次処理されます。サーバーのレスポンスを受け取って処理するには、リスナーを登録する必要があります。リスナーを登録していない場合は、サーバーのレスポンスを受けとっても通知を行わず、次のメッセージを処理することになります。指定された時間内に応答が来なければタイムアウトを発生させ、次のメッセージを処理します。タイムアウトは、OnError()リスナーにErrorCode.TIMEOUTが渡されます。
SendPb()でメッセージを送信すると、SendPb()を呼び出すとすぐにサーバーに送信され、別のレスポンスを待ちません。RequestPb()のレスポンスを待っている中、SendPb()を使用したメッセージは、すぐにサーバーに送信されます。
let connection = GameAnvilManager.GetInstance().GetConnectionAgent();
connection.AddCallback(Messages.SampleReceive, (connectionAgent, receive)=>{
// Messages.SampleReceive
});
let sampleSend= new Messages.SampleSend ();
connection.SendPb(sampleSend);
connection.AddCallback(Messages.SampleResponse, (connectionAgent, response)=>{
// Messages.SampleResponse
});
let sampleRequest = new Messages.SampleRequest();
connection.RequestPb(sampleRequest);
// レスポンスでMessages.SampleResponse.
connection.RequestPb<Messages.SampleResponse>(sampleRequest, (connectionAgent, response)=>{
// Messages.SampleResponse
});
let user = GameAnvilManager.GetInstance().GetUserAgent(this.ServiceName);
user.AddCallback(Messages.SampleReceive, (connectionAgent, receive)=>{
// Messages.SampleReceive
});
let sampleSend= new Messages.SampleSend ();
user.SendPb(sampleSend);
user.AddCallback(Messages.SampleResponse, (connectionAgent, response)=>{
// Messages.SampleResponse
});
let sampleRequest = new Messages.SampleRequest();
user.RequestPb(sampleRequest);
// レスポンスでMessages.SampleResponse.
user.RequestPb<Messages.SampleResponse>(sampleRequest, (connectionAgent, response)=>{
// Messages.SampleResponse
});
パケットクラスを利用して、ProtocolBuffer以外の任意のデータをバイトストリームとしてシリアライズして使用できます。パケットの詳細については、こちらを参照してください。
let connection = GameAnvilManager.GetInstance().GetConnectionAgent();
let reqMsgId = 1;
let resMsgId = 2;
let enc = new TextEncoder();
connection.AddUndefinedProtocolCallback(resMsgId, this.onUndefinedProtocolResponse);
let packet = Packet.CreateFromBuffer(reqMsgId, enc.encode("Test Data"));
connection.Request(packet);
// onUndefinedProtocolResponseレスポンス
connection.Request(packet, (connectionAgent, packet) => {
// ここにレスポンス
});
let user = GameAnvilManager.GetInstance().GetUserAgent(this.ServiceName);
let reqMsgId = 1;
let resMsgId = 2;
let enc = new TextEncoder();
user.AddUndefinedProtocolCallback(resMsgId, this.onUndefinedProtocolResponse);
let packet = Packet.CreateFromBuffer(reqMsgId, enc.encode("Test Data"));
user.Request(packet);
// onUndefinedProtocolResponseレスポンス
user.Request(packet, (connectionAgent, packet) => {
// ここにレスポンス
});
サーバーと送受信するすべてのメッセージはパケットモジュールに載せて処理され、パケットモジュールが提供するインターフェイスを利用します。
GameAnvilコネクタはGoogle Protocol Bufferをデフォルトでのプロトコルとして使用しています。 Google Protocol Bufferを利用するパケットの作成は次のとおりです。
let sampleMessage = new Messages.SampleMessage();
let packet= Packet.CreateFromPbMsg(sampleMessage);
let sampleMessage2 = packet.GetPbMessage<Messages.SampleMessage>();
Google Protocol Bufferを使用しなくてもパケットを作成できます。
let enc = new TextEncoder();
let packet = Packet.CreateFromBuffer(reqMsgId, enc.encode("Test Data"));
let dec = new TextDecoder("utf-8");
let bytes = packet.GetBytes();
let string = dec.decode(bytes);
let uint8arr = JsonUtil.Serialize(obj); // object to UInt8Array;
let packet = Packet.CreateFromBuffer(reqMsgId, uint8arr);
let bytes = packet.GetBytes();
let obj = JsonUtil.Deserialize(bytes);
パケットサイズが大きい場合、圧縮してデータ使用量を減らすことができます。
let sampleMessage = new Messages.SampleMessage();
let packet= Packet.CreateFromPbMsg(sampleMessage);
packet.compress();
if (packet.IsCompress())
packet.Decompress();
let responseMsg = packet.GetPbMessage<Messages.SampleMessage>();
GameAnvilで提供される基本的なAPIを利用する際、追加のデータが必要な場合があります。そのため、基本的なAPIには、追加のデータを渡すことができるpayloadというパラメータが含まれています。このpayloadに必要なデータをパケットに入れてlist形式で保存できます。ここに追加データを入れてサーバーに送信したり、サーバーから送信されたメッセージを取り出すことができます。
let userInfo = Packet.CreateFromPbMsg(new Messages.UserInfo());
let roomInfo = Packet.CreateFromPbMsg(new Messages.RoomInfo());
let payload = Payload.CreateFromPackets([userInfo, roomInfo]);
let userInfoPacket: Packet = payload.GetPacket(Messages.UserInfo);
let roomInfo2: Messages.RoomInfo = payload.GetPBMessage(Messages.RoomInfo);
let userInfo = Packet.CreateFromPbMsg(new Messages.UserInfo());
let roomInfo = Packet.CreateFromPbMsg(new GameMessages.RoomInfo());
let payload = Payload.CreateDefault();
payload.add(userInfo);
payload.add(roomInfo);
let userInfoPacket: Packet = payload.GetPacket(Messages.UserInfo);
let roomInfo2: Messages.RoomInfo = payload.GetPBMessage(Messages.RoomInfo);
ゲームプレイ終了前にConnector.CloseSoket()関数を呼び出して、接続を終了することを推奨します。接続を終了していない場合、サーバ側でクライアントの終了を認識できず、不要な動作を続けることがあります。