ゲーム チャット 2 C++ API の使用
このトピックでは、ゲーム チャット 2 の C++ API を使用して、音声とテキストによる通信をゲームに追加する方法について簡単に説明します。
前提条件
ゲーム チャット 2 を使用するには、プロジェクトが GDK 用に設定されている必要があります。 設定方法の詳細については、「Xbox 本体開発の概要 (NDA トピック)認可が必須です」をご覧ください。
ゲーム チャット 2 をコンパイルするには、プライマリ ヘッダー GameChat2.h を含める必要があります。 適切にリンクするには、プロジェクトで少なくとも 1 つのコンパイル ユニットに GameChat2Impl.h を含める必要があります (これらのスタブ関数の実装はサイズが小さく、コンパイラで "インライン" として簡単に生成できるため、共通のプリコンパイル済みヘッダーをお勧めします)。
ゲームチャット 2 のインターフェイスについては、C++/CX と従来の C++ のどちらを使用してコンパイルするかをプロジェクトで選択する必要はありません。 どちらにしても使用できます。 また、この実装では、致命的ではないエラーを報告する手段として例外はスローされません。 必要に応じて、例外のないプロジェクトから簡単に使用できます。 ただし、この実装では、致命的なエラーを報告する手段として例外がスローされます (詳細については、このトピックで後述する「エラー モデル」をご覧ください)。
初期化
ライブラリの操作を開始するには、ゲーム チャット 2 シングルトン インスタンスを、シングルトンの初期化の有効期間に適用するパラメーターで初期化します。 次のように、シングルトン インスタンスは、chat_manager::initialize を呼び出すことによって初期化されます。
chat_manager::singleton_instance().initialize(...);
注意
RegisterAppStateChangeNotification
を使用して、一時停止および再開のイベントに登録する必要があります。 一時停止時に、 chat_manager::cleanup() を使用してゲーム チャット 2 をクリーンアップする必要があります。 再開時には、ゲーム チャット 2 を再初期化する必要があります。 一時停止と再開のサイクルを通してゲーム チャット 2 を使用しようとすると、クラッシュすることがあります。
ユーザーの構成
Microsoft Game Development Kit (GDK) のタイトルにユーザーを追加する
ユーザーをゲーム チャット 2 インスタンスに追加する前に、ユーザーが GDK タイトルに追加されていることを確認します。 これを行うには、XUserAddAsync API を使用します。 この API の使用方法の詳細については、「ユーザー ID と XUser」をご覧ください。
ゲーム チャット 2 に追加するユーザーの XUserHandle
を用意したら、XUserGetId API を使用してユーザーの Xbox ユーザー ID (XUID) を取得する必要があります。
ユーザーはオンラインである必要があり、この手順にはユーザーの同意が必要です。
XUserGetId は、 として XUID を提供します uint64_t
。 ゲーム チャット 2 で使用するには、この XUID を std::wstring
に変換する必要があります。
XUserHandle
を用意した後に、ユーザーをゲーム チャット 2 に追加する方法を示すコード例を次に示します。
注意
XUserResolveIssueWithUiAsync を呼び出すと、システム ダイアログ ボックスが表示されることに注意してください。
HRESULT
AddChatUserFromXUserHandle(
_In_ XUserHandle user,
_In_ XTaskQueueHandle queueHandle,
_Outptr_result_maybenull_ Xs::game_chat_2::chat_user** chatUser
)
{
*chatUser = nullptr;
uint64_t xuid;
HRESULT hr = XUserGetId(user, &xuid);
if (hr == E_GAMEUSER_RESOLVE_USER_ISSUE_REQUIRED)
{
XAsyncBlock* asyncBlock = new (std::nothrow) XAsyncBlock;
if (asyncBlock != nullptr)
{
ZeroMemory(asyncBlock, sizeof(*asyncBlock));
asyncBlock->queue = queueHandle;
hr = XUserResolveIssueWithUiAsync(user, nullptr, asyncBlock);
if (SUCCEEDED(hr))
{
hr = XAsyncGetStatus(asyncBlock, true);
if (SUCCEEDED(hr))
{
hr = XUserGetId(user, &xuid);
}
}
delete asyncBlock;
}
else
{
hr = E_OUTOFMEMORY;
}
}
if (SUCCEEDED(hr))
{
try
{
std::wstring xuidString = std::to_wstring(xuid);
// If the user has already been added, this will return the existing user.
*chatUser = Xs::game_chat_2::chat_manager::singleton_instance().add_local_user(xuidString.c_str());
}
catch (const std::bad_alloc&)
{
hr = E_OUTOFMEMORY;
}
}
return hr;
}
ゲーム チャット 2 にユーザーを追加する
インスタンスが初期化されたら、chat_manager::add_local_user を使用して、ゲーム チャット 2 インスタンスにローカル ユーザーを追加する必要があります。 この例では、ユーザー A はローカル ユーザーを表しています。
chat_user* chatUserA = chat_manager::singleton_instance().add_local_user(<user_a_xuid>);
次に、リモート ユーザーと、ユーザーがいるリモート "エンドポイント" を表すために使用される識別子を追加します。 エンドポイントとは、リモート デバイスで実行されているアプリのインスタンスです。
この例では、ユーザー B はエンドポイント X にいます。ユーザー C と D はエンドポイント Y にいます。エンドポイント X には、識別子 "1" が任意に割り当てられています。 エンドポイント Y には、識別子 "2" が任意に割り当てられています。
次の呼び出しを使用して、ゲーム チャット 2 にリモート ユーザーを通知します。
chat_user* chatUserB = chat_manager::singleton_instance().add_remote_user(<user_b_xuid>, 1);
chat_user* chatUserC = chat_manager::singleton_instance().add_remote_user(<user_c_xuid>, 2);
chat_user* chatUserD = chat_manager::singleton_instance().add_remote_user(<user_d_xuid>, 2);
次に、各リモート ユーザーとローカル ユーザー間の通信関係を構成します。
この例では、ユーザー A とユーザー B が同じチームに属していると仮定します。 双方向通信が許可されています。
c_communicationRelationshipSendAndReceiveAll
は、双方向通信を表すために GameChat2.h で定義されている定数です。
chat_user_local::set_communication_relationship を使用して、ユーザー A のユーザー B との関係を設定します。
chatUserA->local()->set_communication_relationship(chatUserB, c_communicationRelationshipSendAndReceiveAll);
ユーザー C と D は "観客" であり、ユーザー A の話を聞くことは許可しても、ユーザー A と話すことは許可しないとします。
c_communicationRelationshipSendAll
は、この一方向通信を表すために GameChat2.h で定義されている定数です。
関係を次のように設定します。
chatUserA->local()->set_communication_relationship(chatUserC, c_communicationRelationshipSendAll);
chatUserA->local()->set_communication_relationship(chatUserD, c_communicationRelationshipSendAll);
4 人のローカル ユーザー全員の関係設定を含む例については、このトピックで後述するシナリオに関するセクションをご覧ください。
シングルトン インスタンスに追加されていても、ローカル ユーザーと通信するように構成されていないリモート ユーザーがある時点で存在しても問題ありません。 これは、ユーザーがチームを決めようとしているシナリオや会話チャネルを自由に変更できるシナリオで予想されることです。
ゲーム チャット 2 では、インスタンスに追加されたユーザーの情報 (プライバシー リレーションシップや評判など) のみがキャッシュされるので、特定の時点でどのローカル ユーザーにも話すことができない場合であっても、潜在的なすべてのユーザーのことをゲーム チャット 2 に通知することが有益です。
最後に、ユーザー D がゲームを去っていて、ローカルのゲーム チャット 2 インスタンスから削除する必要があるとします。 これは、次のように chat_manager::remove_user を使用して実行できます。
chat_manager::singleton_instance().remove_user(chatUserD);
chat_manager::remove_user()
を呼び出すと、ユーザー オブジェクトが無効になる可能性があります。
リアルタイム オーディオ操作を使用している場合、詳細については、「チャット ユーザーの有効期間」をご覧ください。 それ以外の場合は、chat_manager::remove_user()
が呼び出されるとすぐにユーザー オブジェクトが無効になります。 ユーザーを削除できるタイミングの微妙な制限の詳細については、このトピックで後述する「状態変更の処理」をご覧ください。
データ フレームの処理
ゲーム チャット 2 には独自のトランスポート層がありません。 これはアプリによって提供される必要があります。 このプラグインは、アプリでの、chat_manager::start_processing_data_frames() メソッドと chat_manager::finish_processing_data_frames() メソッドのペアの通常の頻繁な呼び出しによって管理されます。 これらのメソッドを使用して、ゲーム チャット 2 は送信データをアプリに提供します。
これらのメソッドは、すばやく動作するように設計されています。 専用のネットワーク スレッドでこれらを頻繁にポーリングできます。 これにより、予測不可能なネットワークのタイミングやマルチスレッド コールバックの複雑さを気にすることなく、キューに入れられたすべてのデータを取得する便利な場所が提供されます。
chat_manager::start_processing_data_frames()
が呼び出されると、キューに入れられたすべてのデータが、game_chat_data_frame 構造体ポインターの配列内で報告されます。
アプリでは、この配列を反復処理し、ターゲット エンドポイントを調べ、アプリのネットワーク層を使用して適切なリモート アプリ インスタンスにデータを配信する必要があります。
配列のすべての game_chat_data_frame 構造体の処理が終了したら、chat_manager:finish_processing_data_frames()
を呼び出すことで、ゲーム チャット 2 に配列を渡してリソースを解放する必要があります。
これを次の例に示します。
uint32_t dataFrameCount;
game_chat_data_frame_array dataFrames;
chat_manager::singleton_instance().start_processing_data_frames(&dataFrameCount, &dataFrames);
for (uint32_t dataFrameIndex = 0; dataFrameIndex < dataFrameCount; ++dataFrameIndex)
{
game_chat_data_frame const* dataFrame = dataFrames[dataFrameIndex];
// Title-written function responsible for sending packet to remote instances of GameChat 2.
HandleOutgoingDataFrame(
dataFrame->packet_byte_count,
dataFrame->packet_buffer,
dataFrame->target_endpoint_identifier_count,
dataFrame->target_endpoint_identifiers,
dataFrame->transport_requirement
);
}
chat_manager::singleton_instance().finish_processing_data_frames(dataFrames);
データ フレームが処理される頻度が高いほど、ユーザーが体感するオーディオの遅延が低くなります。 オーディオは 40 ミリ秒のデータ フレームに結合されます。 これがポーリング期間の候補となっています。
状態変更の処理
ゲーム チャット 2 では、アプリで chat_manager::start_processing_state_changes() メソッドと chat_manager::finish_processing_state_changes() メソッドのペアを短い間隔で定期的に呼び出すことによって、受信したテキスト メッセージなどの更新がアプリに提供されます。 これらのメソッドは、すばやく動作するので、UI レンダリング ループ内のすべてのグラフィックス フレームで呼び出すことができます。 これにより、予測できないネットワークのタイミングやマルチスレッド化されたコールバックの複雑さについて心配することなく、キューに入れられたすべての変更を取得する便利な場所が提供されます。
chat_manager::start_processing_state_changes()
が呼び出されると、キューに入れられたすべての更新内容は、game_chat_state_change 構造体ポインターの配列内で報告されます。
アプリでは、配列を反復処理し、より具体的な型の基本構造を検査して、基本的な構造体を対応する詳細な型にキャストしてから、その更新を適切に処理する必要があります。
配列の、現在使用できるすべての game_chat_state_change オブジェクトの処理が終了したら、chat_manager::finish_processing_state_changes()
を呼び出すことで、ゲーム チャット 2 に配列を渡してリソースを解放する必要があります。
これを次の例に示します。
uint32_t stateChangeCount;
game_chat_state_change_array gameChatStateChanges;
chat_manager::singleton_instance().start_processing_state_changes(&stateChangeCount, &gameChatStateChanges);
std::list<Xs::game_chat_2::chat_user*> usersWithPrivilegeIssues;
std::list<Xs::game_chat_2::chat_user*> usersWithPrivilegeCheckIssues;
for (uint32_t stateChangeIndex = 0; stateChangeIndex < stateChangeCount; ++stateChangeIndex)
{
switch (gameChatStateChanges[stateChangeIndex]->state_change_type)
{
case game_chat_state_change_type::text_chat_received:
{
HandleTextChatReceived(static_cast<const game_chat_text_chat_received_state_change*>(gameChatStateChanges[stateChangeIndex]));
break;
}
case Xs::game_chat_2::game_chat_state_change_type::transcribed_chat_received:
{
HandleTranscribedChatReceived(static_cast<const Xs::game_chat_2::game_chat_transcribed_chat_received_state_change*>(gameChatStateChanges[stateChangeIndex]));
break;
}
case Xs::game_chat_2::game_chat_state_change_type::communication_relationship_adjuster_changed:
{
HandleAdjusterChangedStateReceived(static_cast<const Xs::game_chat_2::game_chat_communication_relationship_adjuster_changed_state_change*>(gameChatStateChanges[stateChangeIndex]), usersWithPrivilegeIssues, usersWithPrivilegeCheckIssues);
break;
}
...
}
}
chat_manager::singleton_instance().finish_processing_state_changes(gameChatStateChanges);
chat_manager::remove_user()
は、ユーザー オブジェクトに関連付けられたメモリをすぐに無効にし、状態変更にユーザー オブジェクトへのポインターが含まれていることがあるため、状態変更の処理中に chat_manager::remove_user()
を呼び出さないでください。
テキスト チャット
テキスト チャットを送信するには、chat_user::chat_user_local::send_chat_text() を使用します。 これを次の例に示します。
chatUserA->local()->send_chat_text(L"Hello");
ゲーム チャット 2 によって、このメッセージを含むデータ フレームが生成されます。 データ フレームのターゲット エンドポイントは、ローカル ユーザーからテキストを受信するように構成されたユーザーに関連付けられています。 リモート エンドポイントによってデータが処理されると、game_chat_text_chat_received_state_change を介してメッセージが公開されます。
ボイス チャットと同様に、テキスト チャットでも特権とプライバシーの制限が優先されます。 ペアになっているユーザーがテキスト チャットを許可するように構成されていても、特権やプライバシーの制限でその通信が禁止されている場合、テキスト メッセージは破棄されます。
アクセシビリティ
アクセシビリティのために、テキスト チャットの入力と表示をサポートする必要があります。
テキスト入力が必要なのは、従来、物理キーボードを広く使用していなかったプラットフォームやゲーム ジャンルでも、ユーザーは音声合成支援技術を使用するようにシステムを構成する可能性があるためです。
同様に、ユーザーは音声変換を使用するようにシステムを構成する可能性があるため、テキスト表示が必要となります。
これらの設定は、chat_user ::chat_user_local::text_to_speech_conversion_preference_enabled() メソッドと chat_user::chat_user_local::speech_to_text_conversion_preference_enabled() メソッドをそれぞれ呼び出すことによって、ローカル ユーザーで検出できます。 ユーザー設定に基づいて、条件付きでテキストを有効にすることをお勧めします。
音声合成
ユーザーがテキスト読み上げを有効にしている場合、 chat_user::chat_user_local::text_to_speech_conversion_preference_enabled() は を返します true
。 この状態が検出されたときは、アプリでテキスト入力の方法を提供する必要があります。
実際のキーボードまたは仮想キーボードによってテキスト入力が提供されたら、 文字列を chat_user::chat_user_local::synthesize_text_to_speech() メソッドに 渡します。 ゲーム チャット 2 では、文字列とユーザーのアクセシビリティの音声設定に基づいてオーディオ データの検出と合成を行います。 これを次の例に示します。
chat_userA->local()->synthesize_text_to_speech(L"Hello");
この処理の一部として合成されたオーディオは、このローカル ユーザーからオーディオを受信するように構成されているすべてのユーザーに転送されます。
音声合成を有効にしていないユーザーに対して chat_user::chat_user_local::synthesize_text_to_speech()
が呼び出された場合、ゲーム チャット 2 はアクションを起こしません。
音声変換
ユーザーが音声テキスト変換を有効にしている場合、 chat_user::chat_user_local::speech_to_text_conversion_preference_enabled() は を返します true
。 この状態が検出されたときは、アプリで、変換されたチャット メッセージに関連付けられている UI を提供する準備をする必要があります。 ゲーム チャット 2 では、各リモート ユーザーの音声を自動的に文字に変換し、game_chat_transcribed_chat_received_state_change 構造体を介してそれを公開します。
音声変換のパフォーマンスに関する考慮事項
音声変換が有効になっている場合、各リモート デバイス上のゲーム チャット 2 インスタンスは、音声サービス エンドポイントとの WebSocket 接続を開始します。 各ゲーム チャット 2 リモート クライアントは、この WebSocket を使用してオーディオを音声サービス エンドポイントにアップロードします。 音声サービス エンドポイントからリモート デバイスに、トランスクリプション メッセージが返されることがあります。 その後、リモート デバイスは、トランスクリプション メッセージ (つまり、テキスト メッセージ) をローカル デバイスに送信します。 文字に変換されたメッセージは、ゲーム チャット 2 によってアプリに提供され、レンダリングされます。
したがって、音声変換の主なパフォーマンス コストはネットワーク使用量です。 ネットワーク トラフィックのほとんどは、エンコードされたオーディオのアップロードです。 WebSocket では、"通常の" ボイス チャット パスで、ゲーム チャット 2 によって既にエンコードされているオーディオがアップロードされます。 アプリでは、chat_manager::set_audio_encoding_bitrate によってビット レートを制御します。
UI
ユーザーに UI が表示される場所、特に、スコアボードなどのゲーマータグのリストには、ユーザーへのフィードバックとして、ミュート中/会話中のアイコンも表示することをお勧めします。
これを行うには、chat_user::chat_indicator() を呼び出して、そのユーザーのチャットの現在の瞬間的な状態を表す game_chat_user_chat_indicator 列挙型を取得します。 次の例は、iconToShow
変数に割り当てる特定のアイコンの定数値を決定するために、chatUserA
変数が参照する chat_user オブジェクトのインジケーター値を取得する方法を示しています。
switch (chatUserA->chat_indicator())
{
case game_chat_user_chat_indicator::silent:
{
iconToShow = Icon_InactiveSpeaker;
break;
}
case game_chat_user_chat_indicator::talking:
{
iconToShow = Icon_ActiveSpeaker;
break;
}
case game_chat_user_chat_indicator::local_microphone_muted:
{
iconToShow = Icon_MutedSpeaker;
break;
}
...
}
chat_user::chat_indicator() によって報告される値は、たとえば、プレーヤーが会話を開始したり停止したりするときに頻繁に変化すると予想されます。 そのため、すべての UI フレームでポーリングするアプリをサポートするように設計されています。
ミュート
chat_user::chat_user_local::set_microphone_muted() メソッドを使用して、ローカル ユーザーのマイクのミュート状態を切り替えることができます。 マイクがミュートされると、そのマイクからキャプチャされるオーディオはなくなります。 ユーザーが Kinect などの共有デバイスを使用中の場合、ミュート状態はすべてのユーザーに適用されます。
ローカル ユーザーのマイクのミュート状態は、chat_user::chat_user_local::microphone_muted() メソッドを使用して取得できます。 このメソッドに反映されるのは、chat_user::chat_user_local::set_microphone_muted()
の呼び出しによってソフトウェアでローカル ユーザーのマイクがミュートされているかどうかだけです。 このメソッドには、ユーザーのヘッドセットのボタンなど、ハードウェアによって制御されるミュートは反映されません。
ゲーム チャット 2 を使用して、ユーザーのオーディオ デバイスのハードウェアのミュート状態を取得するためのメソッドはありません。
chat_user::chat_user_local::set_remote_user_muted() メソッドを使用して、特定のローカル ユーザーに関連するリモート ユーザーのミュート状態を切り替えることができます。 リモート ユーザーがミュートされているときに、ローカル ユーザーがリモート ユーザーからのオーディオを聴いたり、テキスト メッセージを受信したりすることはありません。
良くない評判の自動ミュート
通常は、リモート ユーザーがミュート解除を開始します。 ゲーム チャット 2 では、次のような場合にユーザーのミュート状態を開始します。
- リモート ユーザーはローカル ユーザーの友だちではない。
- リモート ユーザーに、良くない評判のフラグが立っている。
この処理のためにユーザーがミュートされていると、chat_user::chat_indicator()
から game_chat_user_chat_indicator::reputation_restricted
が返されます。
この状態は、ターゲット ユーザーとしてリモート ユーザーを含む、chat_user::chat_user_local::set_remote_user_muted()
への最初の呼び出しによって上書きされます。
特権とプライバシー
ゲームによって構成された通信リレーションシップに加えて、ゲーム チャット 2 では、特権とプライバシー制限が適用されます。
ゲーム チャット 2 では、ユーザーが初めて追加されたときに、特権とプライバシーの制限の検索が実行されます。 この操作が完了するまで、そのユーザーの chat_user::chat_indicator()
は、常に game_chat_user_chat_indicator::silent
を返します。
ユーザーとの通信が特権またはプライバシーの制限の影響を受ける場合、ユーザーの chat_user::chat_indicator()
は game_chat_user_chat_indicator::platform_restricted
を返します。
プラットフォームの通信制限は、ボイス チャットとテキスト チャットの両方に適用されます。 プラットフォームの制限によってテキスト チャットはブロックされても、ボイス チャットはブロックされないということはありません。その逆も同様です。
chat_user::chat_user_local::get_effective_communication_relationship() を使用すると、不完全な権限とプライバシーの操作によってユーザーが通信できない場合の区別に役立ちます。 このメソッドでは、ゲーム チャット 2 によって適用された通信関係が game_chat_communication_relationship_flags の形式で返され、関係が構成済みの関係と同じではない可能性がある理由が game_chat_communication_relationship_adjuster 列挙型の形式で返されます。
たとえば、検索操作がまだ進行中の場合は、game_chat_communication_relationship_adjuster が game_chat_communication_relationship_adjuster::initializing
になります。
UI に影響を与えるために、このメソッドを使用しないでください。 (詳細については、前述の「UI」をご覧ください)。
ゲーム チャット 2 で特権の問題が発生した場合は、communication_relationship_adjuster_changed の状態変化で報告されます。
回復不可能な理由のためにゲーム チャット 2 でユーザーの特権の取得に失敗する場合は、game_chat_communication_relationship_adjuster::privilege_check_failure
の調整機能として報告されます。
ユーザーが解決できる可能性がある理由について、ゲーム チャット 2 でユーザーの特権の取得に失敗する場合は、game_chat_communication_relationship_adjuster::resolve_user_issue
の調整機能として報告されます。
ユーザーに、UI で解決できる可能性のある特権が不足している場合、game_chat_communication_relationship_adjuster::privilege
の調整機能として報告されます。
このような場合、通信が制限されます。
ここでは、ユーザーが次の一般的な問題のいずれかを抱えているかどうかを確認する方法の例を示します。
- ゲーム チャット 2 で特権を確認するには、ユーザーが Xbox サービスに同意する必要があります。
- ユーザーのアカウントが特権を拒否するように構成されています (お子様のアカウントであるため、チャットを使用できないなど)。
void
HandleAdjusterChangedStateReceived (
_In_ const Xs::game_chat_2::game_chat_communication_relationship_adjuster_changed_state_change* adjusterChange,
_Inout_ std::list<Xs::game_chat_2::chat_user*>& usersWithPrivilegeIssues,
_Inout_ std::list<Xs::game_chat_2::chat_user*>& usersWithPrivilegeCheckIssues
)
{
Xs::game_chat_2::game_chat_communication_relationship_flags communicationRelationship;
Xs::game_chat_2::game_chat_communication_relationship_adjuster communicationRelationshipAdjuster;
adjusterChange->local_user->local()->get_effective_communication_relationship(
adjusterChange->target_user,
&communicationRelationship,
&communicationRelationshipAdjuster);
if (communicationRelationshipAdjuster == Xs::game_chat_2::game_chat_communication_relationship_adjuster::privilege)
{
// The local user has privilege issues.
usersWithPrivilegeIssues.push_back(adjusterChange->local_user);
}
else if (communicationRelationshipAdjuster == Xs::game_chat_2::game_chat_communication_relationship_adjuster::resolve_user_issue)
{
// The local user has an issue checking privileges.
usersWithPrivilegeCheckIssues.push_back(adjusterChange->local_user);
}
}
game_chat_communication_relationship_adjuster::privilege
の調整機能で報告される問題については、XUserPrivilegeOptions::None
と XUserPrivilege::Communications
を指定して XUserResolvePrivilegeWithUiAsync を呼び出すことで、問題の解決を試みることができます。
game_chat_communication_relationship_adjuster::resolve_user_issue
の調整機能で報告される問題については、その URL の nullptr
を指定して XUserResolveIssueWithUiAsync を呼び出すことで、問題の解決を試みることができます。
特権の問題があることを示す UI を表示することをお勧めします。 ボタン押下またはメニュー オプションを使用して、問題の解決を試みるかどうかをユーザーが決定できるようにします。
ユーザーが問題を解決できない場合や、解決を望んでいない場合があります。 ユーザーが問題を解決した場合は、そのユーザーがゲーム チャット 2 に次回追加されたときに有効になります。
注意
chat_manager::remove_user() は、状態変更の処理中 (つまり、 chat_manager::start_processing_state_changes() が呼び出された後、および chat_manager ::finish_processing_state_changes()) への対応する呼び出しの前に呼び出す必要があります。 状態変更の処理中に chat_manager::remove_user()
を呼び出すと、削除されたユーザーに関連付けられているメモリが無効になる可能性があります。
game_chat_communication_relationship_adjuster::privilege
の調整機能が表示され、ユーザーの特権の解決を試みる場合は、状態変更の処理が完了するまで待ってから、これを試みる必要があります。
XUserResolvePrivilegeWithUiAsync
を呼び出すために必要な XUserHandle
を XUID から取得するには、XUserFindUserById API を使用して新しい XUserHandle
を取得します。 または、XUserAddAsync で取得したものを保持し、それにマップされている XUID を追跡することもできます。
これらの問題の解決方法の例を次に示します。
// If we got an Xs::game_chat_2::game_chat_communication_relationship_adjuster::resolve_user_issue,
// we need to try and fix our issue, if we haven't already, and then remove and re-add that user.
for (Xs::game_chat_2::chat_user* localUser : usersWithPrivilegeCheckIssues)
{
auto asyncBlock = std::make_unique<XAsyncBlock>();
ZeroMemory(asyncBlock.get(), sizeof(*asyncBlock));
asyncBlock->queue = g_asyncQueue;
XUserHandle userHandle;
hr = XUserFindUserById(localUser->local()->xbox_user_id(), &userHandle);
if (SUCCEEDED(hr))
{
hr = XUserResolveIssueWithUiAsync(
userHandle,
nullptr,
asyncBlock.get());
if (SUCCEEDED(hr))
{
hr = XAsyncGetStatus(asyncBlock.get(), true);
if (SUCCEEDED(hr))
{
// Remove and re-add the user after fixing the privileges.
// Users must not be removed while processing state changes.
}
asyncBlock.release();
}
}
}
// If we got an Xs::game_chat_2::game_chat_communication_relationship_adjuster::resolve_user_privilege,
// we need to try and resolve the privileges, if we haven't already, and then remove and re-add that user.
for (Xs::game_chat_2::chat_user* localUser : usersWithPrivilegeIssues)
{
auto asyncBlock = std::make_unique<XAsyncBlock>();
ZeroMemory(asyncBlock.get(), sizeof(*asyncBlock));
asyncBlock->queue = g_asyncQueue;
XUserHandle userHandle;
hr = XUserFindUserById(localUser->local()->xbox_user_id(), &userHandle);
if (SUCCEEDED(hr))
{
hr = XUserResolvePrivilegeWithUiAsync(
userHandle,
XUserPrivilegeOptions::None,
XUserPrivilege::Communications,
asyncBlock.get());
if (SUCCEEDED(hr))
{
hr = XAsyncGetStatus(asyncBlock.get(), true);
if (SUCCEEDED(hr))
{
// Remove and re-add the user after fixing the privileges.
// Users must not be removed while processing state changes.
}
asyncBlock.release();
}
}
}
クリーンアップ
アプリでゲーム チャット 2 を介した通信が不要になったら、chat_manager::cleanup() を呼び出す必要があります。 これにより、ゲーム チャット 2 は通信の管理に割り当てられたリソースを解放できます。
エラー モデル
ゲーム チャット 2 の実装では、致命的ではないエラーを報告する手段として例外はスローされません。 必要に応じて、例外のないプロジェクトから簡単に使用できます。 ただし、ゲーム チャット 2 では、致命的なエラーについて通知するために例外がスローされます。
これらのエラーは、インスタンスを初期化する前にゲーム チャット インスタンスにユーザーを追加したり、ゲーム チャット 2 インスタンスから削除された後にユーザー オブジェクトにアクセスしたりなど、API の誤用によるものです。
これらのエラーは、開発の初期に見つかると期待されており、ゲーム チャット 2 を操作するために使用されるパターンを変更することで修正できます。 そのようなエラーが発生すると、何がエラーの原因になったかに関するヒントがデバッガーに出力された後、例外が発生します。
一般的なシナリオの構成方法
プッシュして話しかける
プッシュして話しかける機能は、chat_user::chat_user_local::set_microphone_muted() を使用して実装する必要があります。
音声を許可するには set_microphone_muted(false)
を呼び出し、それを制限するには set_microphone_muted(true)
を呼び出します。
このメソッドでは、ゲーム チャット 2 からの応答待機時間が最小になります。
チーム
ユーザー A とユーザー B が青チーム、ユーザー C とユーザー D が赤チームに属しているとします。 各ユーザーは、アプリの一意のインスタンスに含まれます。
ユーザー A のデバイス:
chatUserA->local()->set_communication_relationship(chatUserB, c_communicationRelationshipSendAndReceiveAll);
chatUserA->local()->set_communication_relationship(chatUserC, game_chat_communication_relationship_flags::none);
chatUserA->local()->set_communication_relationship(chatUserD, game_chat_communication_relationship_flags::none);
ユーザー B のデバイス:
chatUserB->local()->set_communication_relationship(chatUserA, c_communicationRelationshipSendAndReceiveAll);
chatUserB->local()->set_communication_relationship(chatUserC, game_chat_communication_relationship_flags::none);
chatUserB->local()->set_communication_relationship(chatUserD, game_chat_communication_relationship_flags::none);
ユーザー C のデバイス:
chatUserC->local()->set_communication_relationship(chatUserA, game_chat_communication_relationship_flags::none);
chatUserC->local()->set_communication_relationship(chatUserB, game_chat_communication_relationship_flags::none);
chatUserC->local()->set_communication_relationship(chatUserD, c_communicationRelationshipSendAndReceiveAll);
ユーザー D のデバイス:
chatUserD->local()->set_communication_relationship(chatUserA, game_chat_communication_relationship_flags::none);
chatUserD->local()->set_communication_relationship(chatUserB, game_chat_communication_relationship_flags::none);
chatUserD->local()->set_communication_relationship(chatUserC, c_communicationRelationshipSendAndReceiveAll);
ブロードキャスト
ユーザー A がリーダーで、注文をするとします。 ユーザー B、C、D は、聴くことだけが可能です。 各プレーヤーは固有のデバイスを利用しています。
ユーザー A のデバイス:
chatUserA->local()->set_communication_relationship(chatUserB, c_communicationRelationshipSendAll);
chatUserA->local()->set_communication_relationship(chatUserC, c_communicationRelationshipSendAll);
chatUserA->local()->set_communication_relationship(chatUserD, c_communicationRelationshipSendAll);
ユーザー B のデバイス:
chatUserB->local()->set_communication_relationship(chatUserA, c_communicationRelationshipReceiveAll);
chatUserB->local()->set_communication_relationship(chatUserC, game_chat_communication_relationship_flags::none);
chatUserB->local()->set_communication_relationship(chatUserD, game_chat_communication_relationship_flags::none);
ユーザー C のデバイス:
chatUserC->local()->set_communication_relationship(chatUserA, c_communicationRelationshipReceiveAll);
chatUserC->local()->set_communication_relationship(chatUserB, game_chat_communication_relationship_flags::none);
chatUserC->local()->set_communication_relationship(chatUserD, game_chat_communication_relationship_flags::none);
ユーザー D のデバイス:
chatUserD->local()->set_communication_relationship(chatUserA, c_communicationRelationshipReceiveAll);
chatUserD->local()->set_communication_relationship(chatUserB, game_chat_communication_relationship_flags::none);
chatUserD->local()->set_communication_relationship(chatUserC, game_chat_communication_relationship_flags::none);