GS2-Chat

テキストチャット機能

GS2-Chat を利用することで、ゲーム内にテキストチャットを組み込むことが可能です。 テキストチャットは新着メッセージの検知のために常時接続セッションを必要とし、サーバーに求める要件も複雑になります。

Game Server Services を利用すれば、そのようなめんどくさい常時接続セッションの管理からも解放されます。

ルーム

GS2-Chat の利用を開始するには、まずはルームを作成する必要があります。 ルームはプレイヤーが自由に作成できるようにもできますし、開発サイドで用意したルームをプレイヤーに使用させることもできます。 また、投稿できるユーザーIDのホワイトリストを設定して特定メンバー専用のルームを構築することも可能です。

一点気をつけなければならないのが、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時間保持され、それ以前のメッセージは削除されます。保持期間はネームスペースの設定で1〜30日まで変更できます。

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

カテゴリーモデルと通知設定

メッセージのカテゴリはマスターデータで定義でき、カテゴリごとにアクセストークンを用いた投稿を拒否するかや、オフライン時にモバイルプッシュ通知へ転送するかを設定できます。

主なマスターデータモデルは以下の通りです。

  • CategoryModel: カテゴリ設定と通知先制限

スクリプトトリガー

ルームの作成・削除、メッセージ投稿、購読や購読解除の各処理の前後で GS2-Script を呼び出せます。同期実行で検証や改ざんを行ったり、非同期実行で Amazon EventBridge 経由の外部連携を組み込むこともできます。

設定できる主なイベントトリガーとスクリプト設定名は以下の通りです。

  • createRoomScript(完了通知: createRoomDone): ルーム作成の前後
  • deleteRoomScript(完了通知: deleteRoomDone): ルーム削除の前後
  • postMessageScript(完了通知: postMessageDone): メッセージ投稿の前後
  • subscribeRoomScript(完了通知: subscribeRoomDone): ルーム購読の前後
  • unsubscribeRoomScript(完了通知: unsubscribeRoomDone): 購読解除の前後

プッシュ通知

設定できる主なプッシュ通知と設定名は以下の通りです。

  • postNotification: 購読しているルームに新しい投稿があったときに通知

通知カテゴリは notificationTypes で制御でき、オフライン端末へのモバイルプッシュ転送設定も可能です。

実装例

ルームの作成

    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)
    {

    });

その他の機能

クライアントから投稿できないカテゴリ

マスターデータの CategoryModel で rejectAccessTokenPost に true を設定することで、該当カテゴリへアクセストークンを使用して投稿できなくなります。 この機能を活用することで、スクリプトなどを経由してしか投稿できないカテゴリを用意し、システムメッセージを格納するような用途に利用できます。

プレイヤーによるルーム作成の可否

ネームスペース設定の allowCreateRoom を利用すると、プレイヤーが自由にルームを作成できるかどうかを管理側で制御できます。

allowCreateRoom=true : プレイヤーが自分でルームを作成可能 allowCreateRoom=false : 運営が用意したルームのみ利用可能

大規模なルーム乱立を防ぎたい場合や、特定のルームのみを許可したい場合に、この設定を適宜調整してください。

詳細なリファレンス