Implement Subsystem - Private Chat - (Unreal Engine module)
注釈:本資料はAI技術を用いて翻訳されています。
プライベートチャット機能を使用するために Admin Portal で何かを設定する必要はありませんが、文字数制限やスパム保護など、必要に応じて変更できるパラメータがあります。詳細については、Introduction to Chat を参照してください。
サブシステムの展開
このチュートリアルでは、AGS Online Subsystem (OSS) を使用してプライベートチャットを実装する方法を学びます。Byte Wars プロジェクトでは、PrivateChatSubsystem という名前のゲームインスタンスサブシステムがすでに作成されています。このサブシステムには、プライベートチャットに関連する関数が含まれています。このチュートリアルでは、そのサブシステムのスターターバージョンを使用して、プライベートチャットをゼロから実装します。
スターターパックの内容
このチュートリアルに従うために、PrivateChatSubsystem_Starter という名前のスターターサブシステムクラスが用意されています。このクラスは以下のファイルで構成されています:
- ヘッダーファイル:
/Source/AccelByteWars/TutorialModules/Social/PrivateChat/PrivateChatSubsystem_Starter.h - CPP ファイル:
/Source/AccelByteWars/TutorialModules/Social/PrivateChat/PrivateChatSubsystem_Starter.cpp
PrivateChatSubsystem_Starter クラスは、以下を含むいくつかの関数を提供します:
-
プライベートチャット関連の関数を実装するために使用できる AGS OSS チャットインターフェース:
FOnlineChatAccelBytePtr UPrivateChatSubsystem_Starter::GetChatInterface() const
{
const IOnlineSubsystem* Subsystem = Online::GetSubsystem(GetWorld());
if (!ensure(Subsystem))
{
UE_LOG_PRIVATECHAT(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());
}FOnlineIdentityAccelBytePtr UPrivateChatSubsystem_Starter::GetIdentityInterface() const
{
const IOnlineSubsystem* Subsystem = Online::GetSubsystem(GetWorld());
if (!ensure(Subsystem))
{
UE_LOG_PRIVATECHAT(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* UPrivateChatSubsystem_Starter::GetPromptSubsystem() const
{
const UAccelByteWarsGameInstance* GameInstance = Cast<UAccelByteWarsGameInstance>(GetGameInstance());
if (!GameInstance)
{
return nullptr;
}
return GameInstance->GetSubsystem<UPromptSubsystem>();
} -
Byte Wars 通知システムに通知をプッシュする方法を示す関数。プレイヤーの名前を受け取り、そのプレイヤーからメッセージを受信したことを示す通知を表示します。
void UPrivateChatSubsystem_Starter::PushPrivateChatMessageReceivedNotification(const FUniqueNetId& UserId, const TSharedRef<FChatMessage>& Message)
{
if (!GetPromptSubsystem())
{
UE_LOG_PRIVATECHAT(Warning, TEXT("Cannot push private chat message received notification. Prompt Subsystem is not valid."));
return;
}
const FUniqueNetIdAccelByteUserRef SenderABId = StaticCastSharedRef<const FUniqueNetIdAccelByteUser>(Message->GetUserId());
if (!SenderABId.Get().IsValid())
{
UE_LOG_PRIVATECHAT(Warning, TEXT("Cannot push private chat 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 recipient.
const UCommonActivatableWidget* ActiveWidget = UAccelByteWarsBaseUI::GetActiveWidgetOfStack(EBaseUIStackType::Menu, this);
if (const UPrivateChatWidget_Starter* PrivateChatWidget = Cast<UPrivateChatWidget_Starter>(ActiveWidget))
{
const FUniqueNetIdAccelByteUserPtr CurrentRecipientABId = StaticCastSharedPtr<const FUniqueNetIdAccelByteUser>(PrivateChatWidget->GetPrivateChatRecipient());
if (!CurrentRecipientABId || CurrentRecipientABId->GetAccelByteId().Equals(SenderABId->GetAccelByteId()))
{
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());
}
GetPromptSubsystem()->PushNotification(FText::Format(PRIVATE_CHAT_RECEIVED_MESSAGE, FText::FromString(SenderStr)));
}
チャット接続の実装
このセクションでは、チャットに接続し、切断された場合に再接続する関数を実装します。
-
まず、
DefaultEngine.iniファイルを開き、このオプションが有効になっていることを確認します。これにより、AccelByte OSS はログイン成功後に自動的にチャットに接続するようになります。[OnlineSubsystemAccelByte]
bAutoChatConnectAfterLoginSuccess=true -
PrivateChatSubsystem_Starterクラスのヘッダーファイルを開き、再接続の再試行回数を保存するために以下の変数を宣言します:private:
// ...
int32 ReconnectChatNumTries = 0;
const int32 ReconnectChatMaxTries = 3; -
次に、チャット再接続を実行する新しい関数を宣言します。
protected:
// ...
void ReconnectChat(FString Message); -
PrivateChatSubsystem_Starterクラスの CPP ファイルを開き、上記の関数を定義します。この関数は、指定された最大再試行回数内で、ログインしているプレイヤーをチャットに接続しようと再試行します。void UPrivateChatSubsystem_Starter::ReconnectChat(FString Message)
{
if (!GetWorld() || GetWorld()->bIsTearingDown)
{
UE_LOG_PRIVATECHAT(Warning, TEXT("Failed to reconnect to chat service. The level world is tearing down."));
return;
}
if (!GetChatInterface())
{
UE_LOG_PRIVATECHAT(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_PRIVATECHAT(Warning, TEXT("Failed to reconnect to chat service. The player is not logged in yet."));
return;
}
if (ReconnectChatNumTries >= ReconnectChatMaxTries)
{
UE_LOG_PRIVATECHAT(Warning, TEXT("Failed to reconnect to chat service. Number tries to reconnect chat reached %d times limit."), ReconnectChatMaxTries);
return;
}
UE_LOG_PRIVATECHAT(Log, TEXT("Try to reconnect chat due to: %s"), *Message);
GetChatInterface()->Connect(0);
ReconnectChatNumTries++;
} -
次に、プライベートチャットメッセージが切断されたときに呼び出されるように、上記の関数をバインドする必要があります。これは、
Initialize()関数に以下のハイライトされたコードを追加することで実行できます。void UPrivateChatSubsystem_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_PRIVATECHAT(Log, TEXT("Success to connect to chat service."));
ReconnectChatNumTries = 0;
return;
}
ReconnectChat(ErrorMessage);
});
GetChatInterface()->OnChatDisconnectedDelegates.AddUObject(this, &ThisClass::ReconnectChat);
// ...
}
} -
最後に、サブシステムが非初期化されるときにコールバック関数をアンバインドする必要があります。これは、
Deinitialize()関数に以下のハイライトされたコードを追加することで実行できます:void UPrivateChatSubsystem_Starter::Deinitialize()
{
// ...
if (GetChatInterface())
{
// ...
GetChatInterface()->OnConnectChatCompleteDelegates->RemoveAll(this);
GetChatInterface()->OnChatDisconnectedDelegates.RemoveAll(this);
// ...
}
}
プライベートチャットユーティリティの実装
このセクションでは、プライベートチャット用のいくつかのユーティリティを実装します。
-
PrivateChatSubsystem_Starterクラスのヘッダーファイルを開き、以下の関数を宣言します:public:
// ...
FString GetPrivateChatRoomId(const FUniqueNetIdPtr SenderUserId, const FUniqueNetIdPtr RecipientUserId);
bool IsMessageFromLocalUser(const FUniqueNetIdPtr UserId, const FChatMessage& Message); -
次に、
PrivateChatSubsystem_Starterクラスの CPP ファイルを開き、GetPrivateChatRoomId()を定義します。この関数はプライベートチャットルーム ID を返します。これは、受信したチャットタイプ(例: セッションチャット vs. プライベートチャット)を区別するために使用できます。FString UPrivateChatSubsystem_Starter::GetPrivateChatRoomId(const FUniqueNetIdPtr SenderUserId, const FUniqueNetIdPtr RecipientUserId)
{
if (!GetChatInterface())
{
return FString();
}
if (!SenderUserId || !RecipientUserId)
{
return FString();
}
const FUniqueNetIdAccelByteUserPtr SenderABId = StaticCastSharedPtr<const FUniqueNetIdAccelByteUser>(SenderUserId);
const FUniqueNetIdAccelByteUserPtr RecipientABId = StaticCastSharedPtr<const FUniqueNetIdAccelByteUser>(RecipientUserId);
if (!SenderABId || !RecipientABId)
{
return FString();
}
TOptional<FString> PersonalRoomId = GetChatInterface()->GetPersonalChatTopicId(SenderABId->GetAccelByteId(), RecipientABId->GetAccelByteId());
return PersonalRoomId.IsSet() ? PersonalRoomId.GetValue() : TEXT("");
} -
同じファイルで、
IsMessageFromLocalUser()関数を定義します。この関数は、チャットがローカルプレイヤーからのものか他のプレイヤーからのものかをチェックします。これは、受信メッセージと送信メッセージの表示方法を区別することで(例: 色や配置)、後でチャットメッセージを表示するために使用できます。bool UPrivateChatSubsystem_Starter::IsMessageFromLocalUser(const FUniqueNetIdPtr UserId, const FChatMessage& Message)
{
if (!GetChatInterface())
{
UE_LOG_PRIVATECHAT(Warning, TEXT("Cannot check whether chat message is from local user or not. Chat Interface is not valid."));
return false;
}
if (!UserId)
{
UE_LOG_PRIVATECHAT(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);
}
プライベートチャットメッセージの送信実装
このセクションでは、プライベートチャットメッセージを送信する関数を実装します。
-
PrivateChatSubsystem_Starterクラスのヘッダーファイルを開き、以下の関数を宣言します:public:
// ...
void SendPrivateChatMessage(const FUniqueNetIdPtr UserId, const FUniqueNetIdPtr RecipientUserId, const FString& Message); -
次に、プライベートチャットメッセージが送信されたときに処理するためのデリゲートとコールバック関数を宣言します。このデリゲートは、後でプライベートチャット UI と接続するために使用します。
public:
// ...
FOnSendChatComplete* GetOnSendPrivateChatCompleteDelegates()
{
return &OnSendPrivateChatCompleteDelegates;
}protected:
void OnSendPrivateChatComplete(FString UserId, FString MsgBody, FString RoomId, bool bWasSuccessful);private:
FOnSendChatComplete OnSendPrivateChatCompleteDelegates; -
それでは、上記の関数を定義します。
PrivateChatSubsystem_Starterクラスの CPP ファイルを開き、まずSendPrivateChatMessage()関数を定義してプライベートチャットメッセージを送信します。void UPrivateChatSubsystem_Starter::SendPrivateChatMessage(const FUniqueNetIdPtr UserId, const FUniqueNetIdPtr RecipientUserId, const FString& Message)
{
if (!GetChatInterface())
{
UE_LOG_PRIVATECHAT(Warning, TEXT("Cannot send private chat message. Chat Interface is not valid."));
OnSendPrivateChatComplete(FString(), Message, FString(), false);
return;
}
if (!UserId)
{
UE_LOG_PRIVATECHAT(Warning, TEXT("Cannot send private chat message. User NetId is not valid."));
OnSendPrivateChatComplete(FString(), Message, FString(), false);
return;
}
if (!RecipientUserId)
{
UE_LOG_PRIVATECHAT(Warning, TEXT("Cannot send private chat message. Recipient NetId is not valid."));
OnSendPrivateChatComplete(FString(), Message, FString(), false);
return;
}
GetChatInterface()->SendPrivateChat(UserId.ToSharedRef().Get(), RecipientUserId.ToSharedRef().Get(), Message);
} -
次に、
OnSendPrivateChatComplete()関数を定義します。プライベートチャット送信リクエストが完了すると、先ほど作成したデリゲートをブロードキャストして、プロセスが完了したことを通知します。void UPrivateChatSubsystem_Starter::OnSendPrivateChatComplete(FString UserId, FString MsgBody, FString RoomId, bool bWasSuccessful)
{
// Abort if the room id is not a private chat room.
if (!GetChatInterface() || GetChatInterface()->GetChatRoomType(RoomId) != EAccelByteChatRoomType::PERSONAL)
{
return;
}
if (bWasSuccessful)
{
UE_LOG_PRIVATECHAT(Log, TEXT("Success to send chat message on Room %s"), *RoomId);
}
else
{
UE_LOG_PRIVATECHAT(Warning, TEXT("Failed to send chat message on Room %s"), *RoomId);
}
OnSendPrivateChatCompleteDelegates.Broadcast(UserId, MsgBody, RoomId, bWasSuccessful);
} -
次に、プライベートチャットメッセージが送信されたときに呼び出されるように、上記のコールバック関数をバインドする必要があります。これは、サブシステムが初期化されるときに最初に呼び出される
Initialize()関数に、以下のハイライトされたコードを追加することで実行できます。void UPrivateChatSubsystem_Starter::Initialize(FSubsystemCollectionBase& Collection)
{
// ...
if (GetChatInterface())
{
GetChatInterface()->OnSendChatCompleteDelegates.AddUObject(this, &ThisClass::OnSendPrivateChatComplete);
// ...
}
} -
最後に、サブシステムが非初期化されるときにコールバック関数をアンバインドする必要があります。これは、
Deinitialize()関数に以下のハイライトされたコードを追加することで実行できます:void UPrivateChatSubsystem_Starter::Deinitialize()
{
// ...
if (GetChatInterface())
{
GetChatInterface()->OnSendChatCompleteDelegates.RemoveAll(this);
// ...
}
}
プライベートチャットメッセージの受信実装
このセクションでは、プライベートチャットが受信されたときに処理する関数を実装します。
-
PrivateChatSubsystem_Starterクラスのヘッダーファイルを開き、プライベートチャットメッセージが受信されたときに処理するためのデリゲートとコールバック関数を宣言します。これは、新しいチャットが受信されたときに通知を表示するために使用します。また、このデリゲートは、後でプライベートチャット UI と接続して受信したメッセージを表示するためにも使用します。public:
// ...
FOnChatPrivateMessageReceived* GetOnPrivateChatMessageReceivedDelegates()
{
return &OnPrivateChatMessageReceivedDelegates;
}protected:
// ...
void OnPrivateChatMessageReceived(const FUniqueNetId& UserId, const TSharedRef<FChatMessage>& Message);private:
// ...
FOnChatPrivateMessageReceived OnPrivateChatMessageReceivedDelegates; -
次に、
PrivateChatSubsystem_Starterクラスの CPP ファイルを開き、OnPrivateChatMessageReceived()関数を定義します。プライベートチャットが受信されると、先ほど作成したデリゲートをブロードキャストします。void UPrivateChatSubsystem_Starter::OnPrivateChatMessageReceived(const FUniqueNetId& UserId, const TSharedRef<FChatMessage>& Message)
{
UE_LOG_PRIVATECHAT(Log,
TEXT("Received private chat message from %s: %s"),
!Message.Get().GetNickname().IsEmpty() ? *Message.Get().GetNickname() : *UTutorialModuleOnlineUtility::GetUserDefaultDisplayName(Message->GetUserId().Get()),
*Message.Get().GetBody());
OnPrivateChatMessageReceivedDelegates.Broadcast(UserId, Message);
} -
次に、プライベートチャットメッセージが受信されたときに呼び出されるように、上記のコールバック関数をバインドする必要があります。これは、サブシステムが初期化されるときに最初に呼び出される
Initialize()関数に、以下のハイライトされたコードを追加することで実行できます。ここでは、新しいチャットが受信されたときに通知を表示するために、PushPrivateChatMessageReceivedNotification()という名前の事前定義された関数もバインドします。void UPrivateChatSubsystem_Starter::Initialize(FSubsystemCollectionBase& Collection)
{
// ...
if (GetChatInterface())
{
// ...
GetChatInterface()->OnChatPrivateMessageReceivedDelegates.AddUObject(this, &ThisClass::OnPrivateChatMessageReceived);
// Push a notification when received chat messages.
GetChatInterface()->OnChatPrivateMessageReceivedDelegates.AddUObject(this, &ThisClass::PushPrivateChatMessageReceivedNotification);
// ...
}
} -
最後に、サブシステムが非初期化されるときにコールバック関数をアンバインドする必要があります。これは、
Deinitialize()に以下のハイライトされたコードを追加することで実行できます:void UPrivateChatSubsystem_Starter::Deinitialize()
{
// ...
if (GetChatInterface())
{
// ...
GetChatInterface()->OnChatPrivateMessageReceivedDelegates.RemoveAll(this);
// ...
}
}
最後のプライベートチャットメッセージの取得実装
このセクションでは、最後のプライベートチャットメッセージを取得する関数を実装します。
-
PrivateChatSubsystem_Starterクラスのヘッダーファイルを開き、以下の関数を宣言します:public:
// ...
bool GetLastPrivateChatMessages(const FUniqueNetIdPtr UserId, const FChatRoomId& RoomId, const int32 NumMessages, TArray<TSharedRef<FChatMessage>>& OutMessages); -
次に、
PrivateChatSubsystem_Starterクラスの CPP ファイルを開き、上記の関数を定義します。この関数は、ゲームキャッシュに保存されている最後のチャットメッセージを返します。注記すべてのチャットメッセージは、実行時にゲームキャッシュに保存されます。ゲームを閉じると、チャットメッセージは削除されます。
bool UPrivateChatSubsystem_Starter::GetLastPrivateChatMessages(const FUniqueNetIdPtr UserId, const FChatRoomId& RoomId, const int32 NumMessages, TArray<TSharedRef<FChatMessage>>& OutMessages)
{
if (!GetChatInterface())
{
UE_LOG_PRIVATECHAT(Warning, TEXT("Cannot get last chat messages. Chat Interface is not valid."));
return false;
}
if (!UserId)
{
UE_LOG_PRIVATECHAT(Warning, TEXT("Cannot get last chat messages. User NetId is not valid."));
return false;
}
// Abort if not a private chat room.
if (RoomId.IsEmpty() || GetChatInterface()->GetChatRoomType(RoomId) != EAccelByteChatRoomType::PERSONAL)
{
UE_LOG_PRIVATECHAT(Warning, TEXT("Cannot get last chat messages. Room Id is empty or not a private chat room."));
return false;
}
GetChatInterface()->GetLastMessages(UserId.ToSharedRef().Get(), RoomId, NumMessages, OutMessages);
UE_LOG_PRIVATECHAT(Log, TEXT("Success to get last chat messages. Number of returned messages: %d"), OutMessages.Num());
return true;
}
リソース
-
このチュートリアルセクションで使用されているファイルは、Unreal Byte Wars GitHub リポジトリで入手できます。