GS2-Chat
GS2-Chat を利用することで、ゲーム内にテキストチャットを組み込むことが可能です。 テキストチャットは新着メッセージの検知のために常時接続セッションを必要とし、サーバーに求める要件も複雑になります。
Game Server Services を利用すれば、そのようなめんどくさい常時接続セッションの管理からも解放されます。
ルーム
GS2-Chat の利用を開始するには、まずはルームを作成する必要があります。 ルームはプレイヤーが自由に作成できるようにもできますし、開発サイドで用意したルームをプレイヤーに使用させることもできます。
一点気をつけなければならないのが、GS2-Chat は投稿可能なメッセージの数をルームごとに1秒間あたり3件までしか保証しません。 これを超える頻度でメッセージの投稿を行うとエラーが発生する可能性があります。 そのため、ソーシャルネットワークのタイムラインのような大量のプレイヤーを1つのルームに格納するような要件には向きません。
パスワード
ルームにはパスワードを設定することが可能です。 パスワードが設定されたルームのメッセージを取得する際や、メッセージを投稿する際にはパスワードの指定が必要となります。
購読
プレイヤーはルームを購読することで、ルームへの新着メッセージの通知を受けることができるようになります。 メッセージにはカテゴリを設定可能で、ルーム内のカテゴリごとに購読が可能です。
ギルドチャットの実装においてギルドマスターからのメッセージだけカテゴリを特別なものに設定し、ギルドメンバーはギルドマスターからのメッセージだけは絶対に購読させるようなことができます。
購読による通知処理は GS2-Gateway の通知機能が利用されますが、GS2-Gateway には通知先プレイヤーがオフラインの場合 モバイルプッシュ通知などの、ゲーム外の方法で通知する機能が用意されています。
1つのルームを購読できる人数に明確な制限はありませんが、通知を送信するために GS2-Gateway のAPIリクエストが発生するため、GS2のAPI利用料金が通知先1件ごとに発生します。 また、通知先が多くなると通知が届くまでにかかる時間が長くなります。
actor Player1
actor Player2
participant "GS2-Chat#Room"
participant "GS2-Gateway#Namespace"
participant "Firebase Cloud Messaging"
Player1 -> "GS2-Chat#Room" : Subscribe
Player2 -> "GS2-Chat#Room" : Post Message
"GS2-Chat#Room" -> "GS2-Gateway#Namespace" : Send Notification to Player1
"GS2-Gateway#Namespace" -> "Firebase Cloud Messaging" : Notification(if player is offline)
"GS2-Gateway#Namespace" -> Player1 : Notification
Player1 -> "GS2-Chat#Room" : Load New Message
メッセージ
ルームに対してメッセージを送信することが可能です。 メッセージは過去24時間分のみが保持され、それ以前のメッセージは削除されます。
NGワード
メッセージのペイロードに不適切なワードが含まれるかもしれません。 GS2-Script と連携することで、不適切なワードが含まれるメッセージの投稿を拒否したり、メッセージのペイロードを書き換えることができます。
GS2-Script は HTTP 通信を発行することができるため、自社でもつNGワードチェックサーバーにフォワードして処理することも可能です。
actor Player
participant "GS2-Chat#Room"
participant "GS2-Script#Script"
Player -> "GS2-Chat#Room" : Post Message
"GS2-Chat#Room" -> "GS2-Script#Script" : Trigger post message
"GS2-Script#Script" -> "GS2-Chat#Room" : Continue? / Replaced Message
実装例
ルームの作成
var result = await gs2.Chat.Namespace(
namespaceName: "namespace-0001"
).Me(
gameSession: GameSession
).CreateRoomAsync();
const auto Domain = Gs2->Chat->Namespace(
"namespace-0001" // namespaceName
)->Me(
AccessToken
);
const auto Future = Domain->CreateRoom(
"room-0001", // name
nullptr, // metadata
nullptr, // password
nullptr // whiteListUserIds
);
Future->StartSynchronousTask();
if (Future->GetTask().IsError()) return false;
// obtain changed values / result values
const auto Future2 = Future->GetTask().Result()->Model();
Future2->StartSynchronousTask();
if (Future2->GetTask().IsError()) return false;
const auto Result = Future2->GetTask().Result();
ルームの購読
var result = await gs2.Chat.Namespace(
namespaceName: "namespace-0001"
).Me(
gameSession: GameSession
).Subscribe(
roomName: "room-0001"
).SubscribeAsync(
notificationTypes: new [] {
new Gs2.Unity.Gs2Chat.Model.EzNotificationType {
category = 0,
enableTransferMobilePushNotification = false,
},
}
);
const auto Domain = Gs2->Chat->Namespace(
"namespace-0001" // namespaceName
)->Me(
AccessToken
)->Subscribe(
"room-0001" // roomName
);
const auto Future = Domain->Subscribe(
[]
{
const auto v = MakeShared<TArray<TSharedPtr<Gs2::Chat::Model::FNotificationType>>>();
v->Add(MakeShared<Gs2::Chat::Model::FNotificationType>());
return v;
}() // notificationTypes
);
Future->StartSynchronousTask();
if (Future->GetTask().IsError()) return false;
// obtain changed values / result values
const auto Future2 = Future->GetTask().Result()->Model();
Future2->StartSynchronousTask();
if (Future2->GetTask().IsError()) return false;
const auto Result = Future2->GetTask().Result();
ルームの購読解除
var result = await gs2.Chat.Namespace(
namespaceName: "namespace-0001"
).Me(
gameSession: GameSession
).Subscribe(
roomName: "room-0001"
).UnsubscribeAsync(
);
const auto Domain = Gs2->Chat->Namespace(
"namespace-0001" // namespaceName
)->Me(
AccessToken
)->Subscribe(
"room-0001" // roomName
);
const auto Future = Domain->Unsubscribe(
);
Future->StartSynchronousTask();
if (Future->GetTask().IsError()) return false;
メッセージの投稿
var result = await gs2.Chat.Namespace(
namespaceName: "namespace-0001"
).Me(
gameSession: GameSession
).Room(
roomName: "room-0001",
password: null
).PostAsync(
metadata: "MESSAGE_0001",
category: 0
);
const auto Domain = Gs2->Chat->Namespace(
"namespace-0001" // namespaceName
)->Me(
AccessToken
)->Subscribe(
"room-0001" // roomName
);
const auto Future = Domain->Subscribe(
[]
{
const auto v = MakeShared<TArray<TSharedPtr<Gs2::Chat::Model::FNotificationType>>>();
v->Add(MakeShared<Gs2::Chat::Model::FNotificationType>());
return v;
}() // notificationTypes
);
Future->StartSynchronousTask();
if (Future->GetTask().IsError()) return false;
// obtain changed values / result values
const auto Future2 = Future->GetTask().Result()->Model();
Future2->StartSynchronousTask();
if (Future2->GetTask().IsError()) return false;
const auto Result = Future2->GetTask().Result();
メッセージの一覧取得
var items = await gs2.Chat.Namespace(
namespaceName: "namespace-0001"
).Me(
gameSession: GameSession
).Room(
roomName: "room-0001",
password: null
).MessagesAsync(
).ToListAsync();
const auto Domain = Gs2->Chat->Namespace(
"namespace-0001" // namespaceName
)->Me(
AccessToken
)->Room(
"room-0001", // roomName
nullptr // password
);
const auto It = Domain->Messages(
);
TArray<Gs2::UE5::Chat::Model::FEzMessagePtr> Result;
for (auto Item : *It)
{
if (Item.IsError())
{
return false;
}
Result.Add(Item.Current());
}
メッセージの受信通知ハンドリング
MessagesAsync はキャッシュがある場合はキャッシュを応答し、メッセージの一覧で取得できるデータのキャッシュはメッセージ受信通知を受け取った際に、SDKによって自動的に更新されます。 そのため、通常はこのイベントをハンドリングする必要はなく、必要なタイミングで MessagesAsync を呼び出せば、通常はキャッシュを利用して最新のメッセージリストにアクセスできます。
gs2.Chat.OnPostNotification += PostNotification =>
{
var namespaceName = PostNotification.NamespaceName;
var roomName = PostNotification.RoomName;
};
Gs2->Chat->PostNotificationEvent.AddLambda([](const auto Notification)
{
});