게임 채팅 2 C++ API 사용
이 항목은 게임에 음성 및 텍스트 통신을 추가하기 위한 게임 채팅 2의 C++ API 사용법에 대한 간단한 단계별 정보를 제공합니다.
필수 구성 요소
게임 채팅 2에서는 프로젝트가 GDK용으로 설정되어야 합니다. 자세한 설정 방법을 보려면 Xbox 콘솔 개발 시작(NDA 항목)권한 부여 필요을 참조하세요.
게임 채팅 2를 컴파일하려면 기본 GameChat2.h 헤더를 포함해야 합니다. 적절하게 연결하려면 프로젝트의 한 개 이상의 컴파일 단위에 GameChat2Impl.h도 포함되어야 합니다(이 같은 스텁 함수 구현은 크기가 작으며 컴파일러에서 "인라인"으로 손쉽게 생성할 수 있으므로 미리 컴파일된 공통 헤더를 권장합니다).
게임 채팅 2 인터페이스는 기존의 C++과 달리 프로젝트에서 C++/CX를 사용한 컴파일을 선택할 필요가 없습니다. 두 가지 모두 사용할 수 있습니다. 또한 구현 시 치명적이지 않은 오류 보고 수단으로 예외를 반환하지 않습니다. 원하는 경우 예외 없는 프로젝트에서 쉽게 사용할 수 있습니다. 그러나 구현은 치명적인 오류를 보고하는 수단으로 예외를 반환합니다. (자세한 내용은 이 항목의 후반부에 있는 실패 모델 섹션을 참조하십시오.)
초기화
게임 채팅 2 싱글톤 인스턴스를 싱글톤의 초기화 수명에 적용되는 매개 변수로 초기화하여 라이브러리와 상호 작용을 시작합니다. chat_manager::initialize를 호출하면 다음과 같이 싱글톤 인스턴스는 초기화됩니다.
chat_manager::singleton_instance().initialize(...);
참고 항목
RegisterAppStateChangeNotification
을(를) 통해 일시 중단 및 다시 시작 이벤트를 등록해야 합니다. 중단 시 chat_manager::cleanup()으로 게임 채팅 2를 정리해야 합니다. 다시 시작할 때 게임 채팅 2를 다시 초기화해야 합니다. 이는 일시 중지/다시 시작 주기에서 사용을 시도하는 경우 충돌할 수 있습니다.
사용자 구성
Microsoft GDK(게임 개발 키트) 타이틀에 사용자 추가
게임 채팅 2 인스턴스에 사용자를 추가하기 전 GDK에 사용자를 추가했는지 확인하세요. 이 작업은 XUserAddAsync API를 사용하여 수행합니다. 이 API를 사용하는 방법에 대한 자세한 내용은 사용자 ID 및 XUser를 참조합니다.
게임 채팅 2에 추가하려는 사용자의 XUserHandle
을(를) 받은 후 XUserGetId API를 사용하여 사용자의 Xbox User 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는 임의로 식별자 id "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를 청취할 수 있지만, 말하지는 못하는 것으로 가정합니다.
c_communicationRelationshipSendAll
은(는) 이 단방향 통신을 나타내기 위해 GameChat2.h에서 정의된 상수입니다.
다음과 같이 관계를 설정합니다.
chatUserA->local()->set_communication_relationship(chatUserC, c_communicationRelationshipSendAll);
chatUserA->local()->set_communication_relationship(chatUserD, c_communicationRelationshipSendAll);
전체 네 명의 로컬 사용자 관계 설정에 대한 사례는 이 항목의 후반부에 있는 시나리오 섹션을 참조합니다.
어떤 지점에 싱글톤 인스턴스에는 추가되었으나 로컬 사용자와 통신하도록 구성되지는 않은 원격 사용자가 있는 경우 아무런 문제도 없습니다! 이것은 사용자가 팀을 결정하는 중이거나 말하기 채널을 임의로 바꿀 수 있는 시나리오라고 생각할 수 있습니다.
게임 채팅 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 구조체와 함께 완료되면, 배열을 게임 채팅 2에 다시 전달하여 chat_manager:finish_processing_data_frames()
을(를) 호출해서 리소스를 해제해야 합니다.
이 방법은 다음 예제에서 확인할 수 있습니다.
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);
데이터 프레임이 더 자주 처리되면 사용자가 명확하게 알 수 있는 오디오 대기 시간이 줄어듭니다. 오디오는 40ms 데이터 프레임으로 병합됩니다. 이것이 제안된 풀링 시간입니다.
상태 변경 사항 처리
게임 채팅 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 개체와 함께 종료되면, 이 배열을 게임 채팅 2에 다시 전달하고 chat_manager::finish_processing_state_changes()
을(를) 호출하여 리소스를 해제해야 합니다.
이 방법은 다음 예제에서 확인할 수 있습니다.
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 열거형을 검색해서 수행됩니다. 다음 예제에서는 chatUserA
값이 가리키는 chat_user 개체의 표시기 값을 검색하여, iconToShow
변수에 할당할 특정 아이콘 상수 값을 결정하는 방법을 보여줍니다.
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()은 불완전한 권한 및 개인 정보 보호 작업으로 인해 사용자가 통신할 수 없는 경우를 구별하는 데 사용합니다. game_chat_communication_relationship_flags 형태로 게임 채팅 2에 적용되는 통신 관계와 구성 관계에 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
을(를) 호출하는 데 필요한 XUID로부터 XUserHandle
을(를) 얻으려면 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);