Implement Subsystem - Session chat - (Unreal Engine module)
注釈:本資料はAI技術を用いて翻訳されています。
サブシステムの展開
このチュートリアルでは、AGS Online Subsystem (OSS) を使用してセッションチャットを実装する方法を学びます。Byte Wars プロジェクトでは、SessionChatSubsystem という名前のゲームインスタンスサブシステムがすでに作成されています。このサブシステムには、セッションチャットに関連する機能が含まれています。このチュートリアルでは、そのサブシステムのスターターバージョンを使用して、セッションチャットをゼロから実装します。
スターターパックの内容
このチュートリアルに従うために、SessionChatSubsystem_Starter という名前のスターターサブシステムクラスが用意されています。このクラスは以下のファイルで構成されています。
- ヘッダーファイル:
/Source/AccelByteWars/TutorialModules/Social/SessionChat/SessionChatSubsystem_Starter.h - CPP ファイル:
/Source/AccelByteWars/TutorialModules/Social/SessionChat/SessionChatSubsystem_Starter.cpp
SessionChatSubsystem_Starter クラスは、以下を含むいくつかの機能を提供します。
-
セッションチャット関連の機能を実装するために使用できる AGS OSS チャットインターフェース:
FOnlineChatAccelBytePtr USessionChatSubsystem_Starter::GetChatInterface() const
{
const IOnlineSubsystem* Subsystem = Online::GetSubsystem(GetWorld());
if (!ensure(Subsystem))
{
UE_LOG_SESSIONCHAT(Warning, TEXT("The online subsystem is invalid. Please make sure OnlineSubsystemAccelByte is enabled and the DefaultPlatformService under [OnlineSubsystem] in the Engine.ini file is set to AccelByte."));
return nullptr;
}
return StaticCastSharedPtr<FOnlineChatAccelByte>(Subsystem->GetChatInterface());
}FOnlineSessionV2AccelBytePtr USessionChatSubsystem_Starter::GetSessionInterface() const
{
const IOnlineSubsystem* Subsystem = Online::GetSubsystem(GetWorld());
if (!ensure(Subsystem))
{
UE_LOG_SESSIONCHAT(Warning, TEXT("The online subsystem is invalid. Please make sure OnlineSubsystemAccelByte is enabled and the DefaultPlatformService under [OnlineSubsystem] in the Engine.ini file is set to AccelByte."));
return nullptr;
}
return StaticCastSharedPtr<FOnlineSessionV2AccelByte>(Subsystem->GetSessionInterface());
}FOnlineIdentityAccelBytePtr USessionChatSubsystem_Starter::GetIdentityInterface() const
{
const IOnlineSubsystem* Subsystem = Online::GetSubsystem(GetWorld());
if (!ensure(Subsystem))
{
UE_LOG_SESSIONCHAT(Warning, TEXT("The online subsystem is invalid. Please make sure OnlineSubsystemAccelByte is enabled and DefaultPlatformService under [OnlineSubsystem] in the Engine.ini set to AccelByte."));
return nullptr;
}
return StaticCastSharedPtr<FOnlineIdentityAccelByte>(Subsystem->GetIdentityInterface());
} -
ゲーム UI に通知を表示するためのユーティリティ。プレイヤーが新しいセッションチャットメッセージを受信したときに通知を表示するために必要です。
UPromptSubsystem* USessionChatSubsystem_Starter::GetPromptSubsystem() const
{
const UAccelByteWarsGameInstance* GameInstance = Cast<UAccelByteWarsGameInstance>(GetGameInstance());
if (!GameInstance)
{
return nullptr;
}
return GameInstance->GetSubsystem<UPromptSubsystem>();
} -
Byte Wars 通知システムに通知をプッシュする方法を示す機能。プレイヤーの名前を受け取り、そのプレイヤーからメッセージを受信したことを示す通知を表示します。
void USessionChatSubsystem_Starter::PushChatRoomMessageReceivedNotification(const FUniqueNetId& Sender, const FChatRoomId& RoomId, const TSharedRef<FChatMessage>& Message)
{
if (!GetChatInterface())
{
UE_LOG_SESSIONCHAT(Warning, TEXT("Cannot push chat room message received notification. Chat Interface is not valid."));
return;
}
if (!GetPromptSubsystem())
{
UE_LOG_SESSIONCHAT(Warning, TEXT("Cannot push chat room message received notification. Prompt Subsystem is not valid."));
return;
}
const FUniqueNetIdAccelByteUserRef SenderABId = StaticCastSharedRef<const FUniqueNetIdAccelByteUser>(Message->GetUserId());
if (!SenderABId.Get().IsValid())
{
UE_LOG_SESSIONCHAT(Warning, TEXT("Cannot push chat room message received notification. Sender User Id is not valid."));
return;
}
// Only push a notification only if the player is not in the chat menu of the same type.
const EAccelByteChatRoomType ChatRoomType = GetChatInterface()->GetChatRoomType(RoomId);
const UCommonActivatableWidget* ActiveWidget = UAccelByteWarsBaseUI::GetActiveWidgetOfStack(EBaseUIStackType::Menu, this);
if (const USessionChatWidget_Starter* SessionWidget = Cast<USessionChatWidget_Starter>(ActiveWidget))
{
if (SessionWidget->GetCurrentChatType() == ChatRoomType)
{
return;
}
}
// If the sender doesn't have display name, then use the default display name.
FString SenderStr = Message.Get().GetNickname();
if (SenderStr.IsEmpty() || SenderABId.Get().GetAccelByteId().Equals(SenderStr))
{
SenderStr = UTutorialModuleOnlineUtility::GetUserDefaultDisplayName(Message->GetUserId().Get());
}
switch (ChatRoomType)
{
case EAccelByteChatRoomType::SESSION_V2:
// Push the notification only if the player already travelled to online session.
if (GetWorld() && GetWorld()->GetNetMode() != ENetMode::NM_Standalone)
{
GetPromptSubsystem()->PushNotification(FText::Format(GAMESESSION_CHAT_RECEIVED_MESSAGE, FText::FromString(SenderStr)));
}
break;
case EAccelByteChatRoomType::PARTY_V2:
GetPromptSubsystem()->PushNotification(FText::Format(PARTY_CHAT_RECEIVED_MESSAGE, FText::FromString(SenderStr)));
break;
default:
break;
}
// The push notification is not shown to the gameplay level. Instead, the game will show exclamation mark on the chat button.
if (const FTutorialModuleGeneratedWidget* SessionChatGameplayButtonMetadata = FTutorialModuleGeneratedWidget::GetMetadataById(TEXT("btn_session_chat_gameplay")))
{
if (UAccelByteWarsButtonBase* Button = Cast<UAccelByteWarsButtonBase>(SessionChatGameplayButtonMetadata->GenerateWidgetRef))
{
Button->ToggleExclamationMark(true);
}
}
}
チャット接続の実装
このセクションでは、チャットに接続し、切断された場合に再接続する機能を実装します。
-
まず、
DefaultEngine.iniファイルを開き、このオプションが有効になっていることを確認します。これにより、AccelByte OSS はログイン成功後に自動的にチャットに接続するようになります。[OnlineSubsystemAccelByte]
bAutoChatConnectAfterLoginSuccess=true -
SessionChatSubsystem_Starterクラスのヘッダーファイルを開き、再接続の再試行回数を保存するために以下の変数を宣言します。private:
// ...
int32 ReconnectChatNumTries = 0;
const int32 ReconnectChatMaxTries = 3; -
次に、チャットの再接続を実行する新しい関数を宣言します。
protected:
// ...
void ReconnectChat(FString Message); -
SessionChatSubsystem_Starterクラスの CPP ファイルを開き、上記の関数を定義します。この関数は、指定された最大再試行回数内で、ログインしたプレイヤーをチャットに接続しようと再試行します。void USessionChatSubsystem_Starter::ReconnectChat(FString Message)
{
if (!GetWorld() || GetWorld()->bIsTearingDown)
{
UE_LOG_SESSIONCHAT(Warning, TEXT("Failed to reconnect to chat service. The level world is tearing down."));
return;
}
if (!GetChatInterface())
{
UE_LOG_SESSIONCHAT(Warning, TEXT("Failed to reconnect to chat service. The chat interface is tearing down."));
return;
}
if (!GetIdentityInterface() || GetIdentityInterface()->GetLoginStatus(0) == ELoginStatus::Type::LoggedIn)
{
UE_LOG_SESSIONCHAT(Warning, TEXT("Failed to reconnect to chat service. The player is not logged in yet."));
return;
}
if (ReconnectChatNumTries >= ReconnectChatMaxTries)
{
UE_LOG_SESSIONCHAT(Warning, TEXT("Failed to reconnect to chat service. Number tries to reconnect chat reached %d times limit."), ReconnectChatMaxTries);
return;
}
UE_LOG_SESSIONCHAT(Log, TEXT("Try to reconnect chat due to: %s"), *Message);
GetChatInterface()->Connect(0);
ReconnectChatNumTries++;
} -
次に、セッションチャットメッセージが切断されたときに呼び出されるように、上記の関数をバインドする必要があります。これは、
Initialize()関数に以下の強調表示されたコードを追加することで実行できます。void USessionChatSubsystem_Starter::Initialize(FSubsystemCollectionBase& Collection)
{
// ...
if (GetChatInterface())
{
// ...
// Try reconnect chat on disconnected from chat.
GetChatInterface()->OnConnectChatCompleteDelegates->AddWeakLambda(this, [this](int32 LocalUserNum, bool bWasSuccessful, const FUniqueNetId& UserId, const FString& ErrorMessage)
{
if (bWasSuccessful)
{
UE_LOG_SESSIONCHAT(Log, TEXT("Success to connect to chat service."));
ReconnectChatNumTries = 0;
return;
}
ReconnectChat(ErrorMessage);
});
GetChatInterface()->OnChatDisconnectedDelegates.AddUObject(this, &ThisClass::ReconnectChat);
// ...
}
} -
最後に、サブシステムが非初期化されるときにコールバック関数をアンバインドする必要があります。これは、
Deinitialize()関数に以下の強調表示されたコードを追加することで実行できます。void USessionChatSubsystem_Starter::Deinitialize()
{
// ...
if (GetChatInterface())
{
// ...
GetChatInterface()->OnConnectChatCompleteDelegates->RemoveAll(this);
GetChatInterface()->OnChatDisconnectedDelegates.RemoveAll(this);
// ...
}
}
セッションチャットユーティリティの実装
このセクションでは、セッションチャット用のいくつかのユーティリティを実装します。
-
SessionChatSubsystem_Starterクラスのヘッダーファイルを開き、以下の関数を宣言します。public:
// ...
EAccelByteChatRoomType GetChatRoomType(const FString& RoomId);
FString GetChatRoomIdBasedOnType(const EAccelByteChatRoomType ChatRoomType);
FString GetGameSessionChatRoomId();
FString GetPartyChatRoomId();
bool IsMessageFromLocalUser(const FUniqueNetIdPtr UserId, const FChatMessage& Message); -
次に、
SessionChatSubsystem_Starterクラスの CPP ファイルを開き、GetGameSessionChatRoomId()を定義します。この関数は、現在アクティブなゲームセッションチャットルーム ID を返します。FString USessionChatSubsystem_Starter::GetGameSessionChatRoomId()
{
if (!GetChatInterface())
{
return FString();
}
if (!GetSessionInterface())
{
return FString();
}
const FNamedOnlineSession* GameSession = GetSessionInterface()->GetNamedSession(NAME_GameSession);
if (!GameSession)
{
return FString();
}
return GetChatInterface()->SessionV2IdToChatTopicId(GameSession->GetSessionIdStr());
} -
同じファイルで、
GetPartyChatRoomId()を定義して、現在アクティブなパーティーセッションチャットルーム ID を返します。FString USessionChatSubsystem_Starter::GetPartyChatRoomId()
{
if (!GetChatInterface())
{
return FString();
}
if (!GetSessionInterface())
{
return FString();
}
const FNamedOnlineSession* PartySession = GetSessionInterface()->GetPartySession();
if (!PartySession)
{
return FString();
}
return GetChatInterface()->PartyV2IdToChatTopicId(PartySession->GetSessionIdStr());
} -
次に、
GetChatRoomIdBasedOnType()を定義して、チャットルームタイプに基づいてチャットルーム ID を返します。これは、GetGameSessionChatRoomId()とGetPartyChatRoomId()の機能をカプセル化するのに役立ちます。FString USessionChatSubsystem_Starter::GetChatRoomIdBasedOnType(const EAccelByteChatRoomType ChatRoomType)
{
FString ChatRoomId;
switch (ChatRoomType)
{
case EAccelByteChatRoomType::SESSION_V2:
ChatRoomId = GetGameSessionChatRoomId();
break;
case EAccelByteChatRoomType::PARTY_V2:
ChatRoomId = GetPartyChatRoomId();
break;
}
return ChatRoomId;
} -
次に、
GetChatRoomType()関数を定義して、チャットルーム ID をチャットルームタイプに解析します。EAccelByteChatRoomType USessionChatSubsystem_Starter::GetChatRoomType(const FString& RoomId)
{
if (!GetChatInterface())
{
UE_LOG_SESSIONCHAT(Warning, TEXT("Cannot get chat room type for Room Id: %s"), *RoomId);
return EAccelByteChatRoomType::NORMAL;
}
return GetChatInterface()->GetChatRoomType(RoomId);
} -
最後に、
IsMessageFromLocalUser()関数を定義します。この関数は、チャットがローカルプレイヤーからのものか、他のプレイヤーからのものかをチェックします。これは、受信したメッセージと送信したメッセージの表示方法を区別することで、後でチャットメッセージを表示するために使用できます(例:色や配置)。bool USessionChatSubsystem_Starter::IsMessageFromLocalUser(const FUniqueNetIdPtr UserId, const FChatMessage& Message)
{
if (!GetChatInterface())
{
UE_LOG_SESSIONCHAT(Warning, TEXT("Cannot check whether chat message is from local user or not. Chat Interface is not valid."));
return false;
}
if (!UserId)
{
UE_LOG_SESSIONCHAT(Warning, TEXT("Cannot check whether chat message is from local user or not. User NetId is not valid."));
return false;
}
return GetChatInterface()->IsMessageFromLocalUser(UserId.ToSharedRef().Get(), Message, true);
}
セッションチャットメッセージの送信の実装
このセクションでは、セッションチャットメッセージを送信する機能を実装します。
-
SessionChatSubsystem_Starterクラスのヘッダーファイルを開き、以下の関数を宣言します。public:
// ...
void SendChatMessage(const FUniqueNetIdPtr UserId, const FChatRoomId& RoomId, const FString& Message); -
次に、セッションチャットメッセージが送信されたときに処理するためのデリゲートとコールバック関数を宣言します。このデリゲートは、後でセッションチャット UI と接続するために使用します。
public:
// ...
FOnSendChatComplete* GetOnSendChatCompleteDelegates()
{
return &OnSendChatCompleteDelegates;
}protected:
void OnSendChatComplete(FString UserId, FString MsgBody, FString RoomId, bool bWasSuccessful);private:
FOnSendChatComplete OnSendChatCompleteDelegates; -
それでは、上記の関数を定義しましょう。
SessionChatSubsystem_Starterクラスの CPP ファイルを開き、まずSendChatMessage()関数を定義してセッションチャットメッセージを送信します。void USessionChatSubsystem_Starter::SendChatMessage(const FUniqueNetIdPtr UserId, const FChatRoomId& RoomId, const FString& Message)
{
if (!GetChatInterface())
{
UE_LOG_SESSIONCHAT(Warning, TEXT("Cannot send chat message. Chat Interface is not valid."));
OnSendChatComplete(FString(), Message, RoomId, false);
return;
}
if (!UserId)
{
UE_LOG_SESSIONCHAT(Warning, TEXT("Cannot send chat message. User NetId is not valid."));
OnSendChatComplete(FString(), Message, RoomId, false);
return;
}
if (RoomId.IsEmpty())
{
UE_LOG_SESSIONCHAT(Warning, TEXT("Cannot send chat message. Room Id is empty."));
OnSendChatComplete(FString(), Message, RoomId, false);
return;
}
GetChatInterface()->SendRoomChat(UserId.ToSharedRef().Get(), RoomId, Message);
} -
次に、
OnSendChatComplete()関数を定義します。セッションチャット送信リクエストが完了すると、先ほど作成したデリゲートをブロードキャストして、プロセスが完了したことを通知します。void USessionChatSubsystem_Starter::OnSendChatComplete(FString UserId, FString MsgBody, FString RoomId, bool bWasSuccessful)
{
if (bWasSuccessful)
{
UE_LOG_SESSIONCHAT(Log, TEXT("Success to send chat message on Room %s"), *RoomId);
}
else
{
UE_LOG_SESSIONCHAT(Warning, TEXT("Failed to send chat message on Room %s"), *RoomId);
}
OnSendChatCompleteDelegates.Broadcast(UserId, MsgBody, RoomId, bWasSuccessful);
} -
次に、セッションチャットメッセージが送信されたときに呼び出されるように、上記のコールバック関数をバインドする必要があります。これは、サブシステムが初期化されたときに最初に呼び出される関数である
Initialize()関数に、以下の強調表示されたコードを追加することで実行できます。void USessionChatSubsystem_Starter::Initialize(FSubsystemCollectionBase& Collection)
{
// ...
if (GetChatInterface())
{
GetChatInterface()->OnSendChatCompleteDelegates.AddUObject(this, &ThisClass::OnSendChatComplete);
// ...
}
} -
最後に、サブシステムが非初期化されるときにコールバック関数をアンバインドする必要があります。これは、
Deinitialize()関数に以下の強調表示されたコードを追加することで実行できます。void USessionChatSubsystem_Starter::Deinitialize()
{
// ...
if (GetChatInterface())
{
GetChatInterface()->OnSendChatCompleteDelegates.RemoveAll(this);
// ...
}
}
セッションチャットメッセージの受信の実装
このセクションでは、セッションチャットが受信されたときに処理する機能を実装します。
-
SessionChatSubsystem_Starterクラスのヘッダーファイルを開き、セッションチャットメッセージが受信されたときに処理するためのデリゲートとコールバック関数を宣言します。これは、新しいチャットが受信されたときに通知を表示するために使用します。また、このデリゲートは、後でセッションチャット UI と接続して受信したメッセージを表示するためにも使用します。public:
// ...
FOnChatRoomMessageReceived* GetOnChatRoomMessageReceivedDelegates()
{
return &OnChatRoomMessageReceivedDelegates;
}protected:
// ...
void OnChatRoomMessageReceived(const FUniqueNetId& UserId, const FChatRoomId& RoomId, const TSharedRef<FChatMessage>& Message);private:
// ...
FOnChatRoomMessageReceived OnChatRoomMessageReceivedDelegates; -
次に、
SessionChatSubsystem_Starterクラスの CPP ファイルを開き、OnChatRoomMessageReceived()関数を定義します。セッションチャットが受信されると、先ほど作成したデリゲートをブロードキャストします。void USessionChatSubsystem_Starter::OnChatRoomMessageReceived(const FUniqueNetId& UserId, const FChatRoomId& RoomId, const TSharedRef<FChatMessage>& Message)
{
UE_LOG_SESSIONCHAT(Log,
TEXT("Received chat message from %s on Room %s: %s"),
!Message.Get().GetNickname().IsEmpty() ? *Message.Get().GetNickname() : *UTutorialModuleOnlineUtility::GetUserDefaultDisplayName(Message->GetUserId().Get()),
*RoomId,
*Message.Get().GetBody());
OnChatRoomMessageReceivedDelegates.Broadcast(UserId, RoomId, Message);
} -
次に、セッションチャットメッセージが受信されたときに呼び出されるように、上記のコールバック関数をバインドする必要があります。これは、サブシステムが初期化されたときに最初に呼び出される関数である
Initialize()関数に、以下の強調表示されたコードを追加することで実行できます。ここでは、新しいチャットが受信されたときに通知を表示するために、事前定義されたPushChatRoomMessageReceivedNotification()関数もバインドします。void USessionChatSubsystem_Starter::Initialize(FSubsystemCollectionBase& Collection)
{
// ...
if (GetChatInterface())
{
// ...
GetChatInterface()->OnChatRoomMessageReceivedDelegates.AddUObject(this, &ThisClass::OnChatRoomMessageReceived);
// Push a notification when received chat messages.
GetChatInterface()->OnChatRoomMessageReceivedDelegates.AddUObject(this, &ThisClass::PushChatRoomMessageReceivedNotification);
// ...
}
} -
最後に、サブシステムが非初期化されるときにコールバック関数をアンバインドする必要があります。これは、
Deinitialize()に以下の強調表示されたコードを追加することで実行できます。void USessionChatSubsystem_Starter::Deinitialize()
{
// ...
if (GetChatInterface())
{
// ...
GetChatInterface()->OnChatRoomMessageReceivedDelegates.RemoveAll(this);
// ...
}
}
最後のセッションチャットメッセージの取得の実装
このセクションでは、最後のセッションチャットメッセージを取得する機能を実装します。
-
SessionChatSubsystem_Starterクラスのヘッダーファイルを開き、以下の関数を宣言します。public:
// ...
bool GetLastChatMessages(const FUniqueNetIdPtr UserId, const FChatRoomId& RoomId, const int32 NumMessages, TArray<TSharedRef<FChatMessage>>& OutMessages); -
次に、
SessionChatSubsystem_Starterクラスの CPP ファイルを開き、上記の関数を定義します。この関数は、ゲームキャッシュに保存されている最後のチャットメッセージを返します。注記すべてのチャットメッセージは、実行時にゲームキャッシュに保存されます。ゲームを閉じると、チャットメッセージは削除されます。
bool USessionChatSubsystem_Starter::GetLastChatMessages(const FUniqueNetIdPtr UserId, const FChatRoomId& RoomId, const int32 NumMessages, TArray<TSharedRef<FChatMessage>>& OutMessages)
{
if (!GetChatInterface())
{
UE_LOG_SESSIONCHAT(Warning, TEXT("Cannot get last chat messages. Chat Interface is not valid."));
return false;
}
if (!UserId)
{
UE_LOG_SESSIONCHAT(Warning, TEXT("Cannot get last chat messages. User NetId is not valid."));
return false;
}
if (RoomId.IsEmpty())
{
UE_LOG_SESSIONCHAT(Warning, TEXT("Cannot get last chat messages. Room Id is empty."));
return false;
}
GetChatInterface()->GetLastMessages(UserId.ToSharedRef().Get(), RoomId, NumMessages, OutMessages);
UE_LOG_SESSIONCHAT(Log, TEXT("Success to get last chat messages. Returned messages: %d"), OutMessages.Num());
return true;
}
リソース
-
このチュートリアルセクションで使用されているファイルは、Unreal Byte Wars GitHub リポジトリで入手できます。