Unreal Engine 4でのRoomモジュールの利用方法

リアルタイムゲームではしばしばサーバ上に仮想的な部屋を作成する必要があります。 DiarkisのRoomモジュールを使うことで、これらの機能を簡単に実装することができます。 このエントリではDiarkisのUnreal Engine 4(UE4)用のプラグインを使って、部屋を新規に作成する方法や、既存の部屋に参加する方法を解説します。 また部屋の中にいる人の情報を取得したり、それらの人にメッセージを送る方法なども合わせて解説します。

サンプルプログラムの実行方法

こちらよりサンプルプログラムをダウンロードし実行してください。

最初にDiarkis Cloudとの接続画面が表示されるので、必要な情報を入力し接続してください(詳しくは前回のエントリを参照してください)。

接続が成功するとRoom Demosと書かれた画面(上記画像参考)が表示されます。この画面から部屋の作成に関する複数のデモを試すことができます。

各デモの内容は以下のようになっています。

1. Create Room Demo:部屋を新規に作成する。
2. Join Room Demo:既存の部屋に参加する。
3. Join Random Room Demo:ランダムに部屋に参加する。

1. Create Room Demo(部屋の新規作成)

このデモでは部屋を新規に作成して参加します。

部屋を作成する際には以下のパラメータを設定することができます。

MaxMembers:参加人数の上限。
AllowEmpty:部屋から誰もいなくなったときにすぐに部屋を削除するかどうか。true時のみ下記TTL時間だけ部屋が存続する。
TTL:部屋が空になったときの部屋の存続時間(秒)。AllowEmpty=true時のみ参照される。
Interval:メッセージの送信間隔(ミリ秒)。

パラメータ設定後、Create Room & Joinボタンを押すことで部屋が新規作成され、プレイヤーは自動的にその部屋に参加します。

部屋に参加した後の操作方法は、部屋に参加後の操作方法をご覧ください。

2. Join Room Demo(既存部屋への参加)

このデモでは既に存在する部屋を検索して参加することができます。

部屋を検索するにはRoomTypeというパラメータが必要になります(既存部屋へのRoomTypeの設定方法は部屋に参加後の操作方法を参考にしてください)。

RoomType:検索する部屋のタイプ(数字で指定)。
Limit:検索で取得する部屋の最大件数。

パラメータ設定後、Joinボタンを押すことで部屋が検索され、1件以上部屋が見つかった場合その部屋に参加します。

3. Join Random Room Demo(ランダムに参加)

このデモではランダムに部屋に参加します。

MaxMembers:参加人数の上限。
TTL:部屋が空になったときの部屋の存続時間(秒)。
Interval:メッセージの送信間隔(ミリ秒)。

パラメータ設定後、Joinボタンを押すことで条件に合う部屋が検索され、見つかればその部屋に参加し、見つからなければ部屋を新規作成します。

部屋に参加後の操作方法

各デモから部屋に参加すると、上記のような画面が表示されます。

画面上部に表示される情報は以下を表します。

UID:自分のユーザーID。
RoomID:部屋のID。
OwnerUID:部屋のオーナーのユーザーID。
MemberUIDs:部屋の中にいるメンバーのユーザーID(自分を含む)。
Messages:送られてきたメッセージ。

Get OwnerUIDボタンを押すことでOwnerUID(部屋オーナーのユーザーID)の取得を試みます。

Get MemberUIDsボタンを押すことでMemberUIDs(部屋メンバーのユーザーID)の取得を試みます。

Register Roomを使って、現在参加している部屋にRoomTypeを設定することができます。その際に以下のパラメータを設定できます。

RoomType:部屋のタイプ(数字で指定)。
RoomName:部屋の名前。
RoomMetadata:部屋に持たせたい任意のデータ。

Broadcastを使うことで部屋に参加している全メンバーにメッセージを送信できます。

Message ToはUIDを指定することで特定のメンバーにだけメッセージを送信できます。

Roomモジュールの利用方法

ここからはプラグインの使い方を実際のソースコードを交えて解説します。

プラグインの機能にアクセスするには、まずプラグインの中で使用されているクラスを継承して新しいクラスを作成する必要があります。

継承する必要のあるクラスは以下のものがあります。

DiarkisInterfaceBase:プラグイン本体(必ず継承が必要)

// トランスポート層
DiarkisTcpBase:TCP層
DiarkisUdpBase:UDP層

// 各種モジュール
DiarkisP2PBase:P2Pモジュール
DiarkisRoomBase:Roomモジュール(※今回利用するモジュール)
DiarkisFieldBase:Fieldモジュール
DiarkisGroupBase:Groupモジュール
DiarkisMatchMakerBase:MatchMakerモジュール

今回のエントリではRoomモジュールを利用します。ですのでプラグインの大元となるDiarkisInterfaceBaseDiarkisRoomBaseを継承したクラスを新規作成します。

継承したクラスの名前は自由につけていただいて構わないのですが、今回は便宜上から以下のような名前で作成します。

// 実際は自由なクラス名をつけてください
DiarkisInterfaceBaseを継承したクラス:CustomDiarkisInterface
DiarkisRoomBaseを継承したクラス:CustomDiarkisRoom

CustomDiarkisInterfaceの実装

DiarkisInterfaceBaseを継承してCustomDiarkisInterfaceを作成します。

#include "DiarkisInterfaceBase.h"

class CustomDiarkisInterface : public DiarkisInterfaceBase
{
public:
CustomDiarkisInterface(std::string host, std::string clientKey, uint64_t uid, LogOutType out);

virtual ~CustomDiarkisInterface() {}
};

DiarkisInterfaceBaseではインスタンス作成時に内部で各種モジュールの作成を行っています。

今回はDiarkisRoomBaseを拡張したクラス(CustomDiarkisRoom)を使用しますので、コンストラクタの処理を一部上書きし、CustomDiarkisRoomモジュールを内部で持たせるように変更します。

#include "CustomDiarkisInterface.h"
#include "CustomDiarkisRoom.h" // CustomDiarkisRoomのインクルード(後に解説)。

CustomDiarkisInterface::CustomDiarkisInterface(std::string host, std::string clientKey, uint64_t uid, LogOutType out)
: DiarkisInterfaceBase(host, clientKey, uid, out)
{
this->roomBase = make_shared<CustomDiarkisRoom>(); // Roomモジュールを上書き
}

それからDiarkisInterfaceBaseを作成するときは代わりにCustomDiarkisInterfaceインスタンスを作成するようにします。

+ #include "CustomDiarkisInterface.h"

bool AMyClass::Connect(const FString& Host, const FString& ClientKey, int64 UID, bool bUseUdp)
{
...
...
- this->diarkis = MakeShared<DiarkisInterfaceBase>(this, host, clientKey, (uint64_t) UID, LogOutType::FILE_OUT); // 削除
+ this->diarkis = MakeShared<CustomDiarkisInterface>(this, host, clientKey, (uint64_t) UID, LogOutType::FILE_OUT);
this->diarkis->WaitUntilReady();
...
...
}

CustomDiarkisRoomの実装

DiarkisRoomBaseを継承してCustomDiarkisRoomを作成します。この時各種コールバック関数(OnRoomCreation ~ OnRoomFineByType)をオーバーライドします。

#include "DiarkisRoomBase.h"

class CustomDiarkisRoom : public DiarkisRoomBase
{
public:
CustomDiarkisRoom();

virtual ~CustomDiarkisRoom() {}

protected:
// 各種コールバック関数をオーバーライド
void OnRoomCreation(const DiarkisRoomCreationEventArgs& e) override;

void OnRoomJoin(const DiarkisRoomJoinEventArgs& e) override;

...
...
...

void OnRoomFineByType(const DiarkisRoomFindByTypeEventArgs& e) override;
};

部屋を新規作成する

まず部屋を作成する前に、DiarkisInterfaceBase::SetupRoomを呼び出しRoomモジュールを初期化します。

次にDiarkisInterfaceBase::CreateRoomを呼び出し部屋を作成します。

void AMyClass::SendCreateRoom(int32 MaxMembers, bool AllowEmpty, bool Join, int32 TTL, int32 Interval)
{
this->diarkis->SetupRoom();
this->diarkis->CreateRoom((uint16_t) MaxMembers, AllowEmpty, Join, (uint16_t) TTL, (uint32_t) Interval);
}

CreateRoomには以下の引数を渡します。

maxMembers:参加人数の上限。
allowEmpty:部屋から誰もいなくなったときにすぐに部屋を削除するかどうか。true時のみ下記ttl時間だけ部屋が存続する。
join:部屋作成後、直ちに参加するかどうか。
ttl:部屋が空になったときの部屋の存続時間(秒)。allowEmpty=true時のみ参照される。
interval:メッセージの送信間隔(ミリ秒)。

CreateRoomの実行結果はCustomDiarkisRoomでオーバーライドしたOnRoomCreation関数が呼ばれることで取得できます。

void CustomDiarkisRoom::OnRoomCreation(const DiarkisRoomCreationEventArgs& e)
{
DiarkisRoomBase::OnRoomCreation(e);

bool isSuccess = e.IsSuccess();
std::string roomID = e.GetRoomID();
}

既存の部屋へ参加する

既に作成されている部屋に参加するには、まずDiarkisInterfaceBase::FindByTypeRoomを呼び出して部屋を検索する必要があります。その時検索する条件としてRoomTypeを渡す必要があります。

void AMyClass::SendFindByTypeRoom(int32 RoomType, int32 Limit)
{
this->diarkis->SetupRoom();
this->diarkis->FindByTypeRoom(RoomType, Limit);
}

FindByTypeRoomに渡す引数は以下のようになっています。

roomType:検索する部屋のタイプ(数字で指定)。
limit:検索で取得する部屋の最大件数。

FindByTypeRoomの実行結果はOnRoomFineByType関数が呼ばれることで取得できます。

void CustomDiarkisRoom::OnRoomFineByType(const DiarkisRoomFindByTypeEventArgs& e)
{
DiarkisRoomBase::OnRoomFineByType(e);

bool isSuccess = e.IsSuccess();
std::vector<RoomListItem> roomList = e.GetRoomListItem();
}

1つ以上の部屋が見つかった状態でDiarkisInterfaceBase::JoinRoomを呼び出すことで、その部屋に参加します。

void AMyClass::SendJoinRoom()
{
this->diarkis->JoinRoom();
}

JoinRoomの実行結果はOnRoomJoin関数が呼ばれることで取得できます。

void CustomDiarkisRoom::OnRoomJoin(const DiarkisRoomJoinEventArgs& e)
{
DiarkisRoomBase::OnRoomJoin(e);

bool isSuccess = e.IsSuccess();
}

ランダムに部屋に参加する

ランダムに部屋に参加するにはDiarkisInterfaceBase::RandamJoinRoomを呼び出します。

void AMyClass::SendJoinRandomRoom(int32 MaxMembers, int32 TTL, int32 Interval)
{
this->diarkis->SetupRoom();
this->diarkis->RandamJoinRoom((uint16_t) MaxMembers, (uint16_t) TTL, (uint32_t) Interval);
}

RandamJoinRoomには以下の引数を渡します。

maxMembers:参加人数の上限。
ttl:部屋が空になったときの部屋の存続時間(秒)。
interval:メッセージの送信間隔(ミリ秒)。

RandamJoinRoomの実行結果はOnRoomCreationもしくはOnRoomJoin関数が呼ばれることで取得できます(コードの実装例は部屋を新規作成するもしくは既存の部屋へ参加するを参照してください)。

部屋を去る(部屋に参加中のみ)

ここからは部屋に参加中のみ呼び出すことができる機能を解説していきます。

現在参加している部屋から抜けるためにはDiarkisRoomBase::SendLeaveRoomを呼び出します。

void AMyClass::SendLeaveRoom()
{
this->diarkis->GetRoomBase()->SendLeaveRoom(this->diarkis->GetUid());
}

SendLeaveRoomの実行結果はOnRoomLeave関数が呼ばれることで取得できます。

void CustomDiarkisRoom::OnRoomLeave(const DiarkisRoomSuccessEventArgs& e)
{
DiarkisRoomBase::OnRoomLeave(e);

bool isSuccess = e.IsSuccess();
}

ユーザーの入室を検知する(部屋に参加中のみ)

現在参加している部屋に新規のユーザーが入ってくるとOnRoomMemberJoin関数が呼ばれます。

void CustomDiarkisRoom::OnRoomMemberJoin(const DiarkisPayloadEventArgs& e)
{
DiarkisRoomBase::OnRoomMemberJoin(e);

uint64_t uid = *(uint64_t*) e.GetPayload().data();
}

ユーザーの退室を検知する(部屋に参加中のみ)

現在参加している部屋からユーザーが去るとOnRoomMemberLeave関数が呼ばれます。

void CustomDiarkisRoom::OnRoomMemberLeave(const DiarkisPayloadEventArgs& e)
{
DiarkisRoomBase::OnRoomMemberLeave(e);

uint64_t uid = *(uint64_t*) e.GetPayload().data();
}

部屋オーナーのユーザーIDを取得する(部屋に参加中のみ)

現在参加している部屋のオーナーのユーザーIDを取得するにはDiarkisInterfaceBase::SendGetOnwerIDを呼び出します。

void AMyClass::SendGetOwnerUID()
{
this->diarkis->SendGetOnwerID();
}

SendGetOnwerIDの実行結果はOnRoomGetOwnerID関数が呼ばれることで取得できます。

void CustomDiarkisRoom::OnRoomGetOwnerID(const DiarkisRoomGetOwnerIDEventArgs& e)
{
DiarkisRoomBase::OnRoomGetOwnerID(e);

bool isSuccess = e.IsSuccess();
std::string ownerID = e.GetOwnerID();
}

部屋に参加している全ユーザーのIDを取得する(部屋に参加中のみ)

現在参加している部屋のすべてのユーザーIDを取得するにはDiarkisInterfaceBase::SendGetMemberIDsを呼び出します。

void AMyClass::SendGetMemberUIDs()
{
this->diarkis->SendGetMemberIDs();
}

SendGetMemberIDsの実行結果はOnRoomMemberIDs関数が呼ばれることで取得できます。

void CustomDiarkisRoom::OnRoomMemberIDs(const DiarkisRoomMemberIDsEventArgs& e)
{
DiarkisRoomBase::OnRoomMemberIDs(e);

bool isSuccess = e.IsSuccess();
std::vector<std::string> memberIDs = e.GetMemberIDs();
}

部屋を登録する(部屋に参加中のみ)

FindByTypeRoomによって部屋を検索できるよう、部屋にRoomTypeの情報などを登録することができます。

登録したい部屋に参加している最中にDiarkisInterfaceBase::RegisterRoomを呼び出すことでその部屋を登録できます。

void AMyClass::SendRegisterRoom(int32 RoomType, const FString& RoomName, const FString& RoomMetadata)
{
std::string roomName = std::string(TCHAR_TO_UTF8(*RoomName));
std::string roomMetadata = std::string(TCHAR_TO_UTF8(*RoomMetadata));
this->diarkis->RegisterRoom((uint32_t) RoomType, roomName, roomMetadata);
}

RegisterRoomには以下の引数を渡します。

roomType:部屋のタイプ(数字で指定)。
roomName:部屋の名前。
roomMetadata:部屋に持たせたい任意のデータ。

RegisterRoomの実行結果はOnRoomRegister関数が呼ばれることで取得できます。

void CustomDiarkisRoom::OnRoomRegister(const DiarkisRoomPayloadEventArgs& e)
{
DiarkisRoomBase::OnRoomRegister(e);

bool isSuccess = e.IsSuccess();
}

メッセージをブロードキャストする(部屋に参加中のみ)

DiarkisInterfaceBase::SendBroadcastToRoomは任意のバイナリデータを部屋に参加している全ユーザーに送信します。

この機能を使うことでメッセージを一斉送信することができます。

void AMyClass::SendBroadcastMessageToRoom(const FString& Message)
{
std::string msg = std::string(TCHAR_TO_UTF8(*Message));
std::vector<uint8_t> payload = std::vector<uint8_t>((uint8_t*) msg.data(), (uint8_t*) msg.data() + msg.size());

this->diarkis->SendBroadcastToRoom(payload, true, 0, ProfileType::PT_NONE, 0);
}

SendBroadcastToRoomには以下の引数を渡します。

payload:送信するデータ。
reliable:RUDPで送るかどうか(トランスポート層にUDP指定時のみ)。
dataType:送信するデータのタイプ。(0: 通常データ 10: Profile用テストデータ)。
profileType:送信するデータにプロファイル用データを含めるかの設定。
createStartTime:送信するデータの作成時間(ProfileType::PT_SENDCREATE指定時のみ)。

SendBroadcastToRoomの実行結果はOnRoomMemberBroadcast関数が呼ばれることで取得できます。この時受け取ったデータの先頭5バイトを削除する必要があります。

void CustomDiarkisRoom::OnRoomMemberBroadcast(const DiarkisPayloadEventArgs& e)
{
DiarkisRoomBase::OnRoomMemberBroadcast(e);

std::vector<uint8_t> payload = e.GetPayload();

// 先頭の5バイトを削除
int trimHeaderCount = 5;
std::vector<uint8_t> trimmedPayload(payload.begin() + trimHeaderCount, payload.end());

std::string message = std::string((char*) trimmedPayload.data(), trimmedPayload.size());
}

任意のユーザーにメッセージを送信する(部屋に参加中のみ)

DiarkisInterfaceBase::SendMessageToRoomはユーザーを指定して任意のバイナリデータを送信できます。

この機能を使うことで限られた人にだけメッセージを送ることができます。

void AMyClass::SendMessageTo(const FString& UID, const FString& Message)
{
std::string msg = std::string(TCHAR_TO_UTF8(*Message));
std::vector<uint8_t> payload = std::vector<uint8_t>((uint8_t*) msg.data(), (uint8_t*) msg.data() + msg.size());

std::vector<std::string> memberIDs;
memberIDs.push_back(std::string(TCHAR_TO_UTF8(*UID)));

this->diarkis->SendMessageToRoom(memberIDs, payload, true, 0, ProfileType::PT_NONE, 0);
}

SendMessageToRoomには以下の引数を渡します。

memberIDs:送信するメンバーのユーザーID(複数指定可)。
payload:送信するデータ。
reliable:RUDPで送るかどうか(トランスポート層にUDP指定時のみ)。
dataType:送信するデータのタイプ。(0: 通常データ 10: Profile用テストデータ)。
profileType:送信するデータにプロファイル用データを含めるかの設定。
createStartTime:送信するデータの作成時間(ProfileType::PT_SENDCREATE指定時のみ)。

SendMessageToRoomの実行結果はOnRoomMemberMessage関数が呼ばれることで取得できます。この時受け取ったデータの先頭5バイトを削除する必要があります。

void CustomDiarkisRoom::OnRoomMemberMessage(const DiarkisPayloadEventArgs& e)
{
DiarkisRoomBase::OnRoomMemberMessage(e);

std::vector<uint8_t> payload = e.GetPayload();

// 先頭の5バイトを削除
int trimHeaderCount = 5;
std::vector<uint8_t> trimmedPayload(payload.begin() + trimHeaderCount, payload.end());

std::string message = std::string((char*) trimmedPayload.data(), trimmedPayload.size());
}