Personal Chat
1. 概述
本文档介绍了游戏内私聊功能的使用方法,用于与其他玩家进行一对一聊天。通过提供对方玩家的ID即可发起私聊。
完成本教程后,开发者将了解:
- PGOS私聊的基本概念
- 私聊的典型使用场景
- 如何发送和接收私聊消息
- 如何获取最近聊天列表以及如何获取与特定玩家的历史消息
- 私聊事件介绍及其监听方法
2. 流程
以下是使用私聊功能的流程:
私聊互动流程如下:
- 成功登录后,私聊入口图标会显示未读私聊消息数量。
- 点击私聊图标打开聊天窗口。
- 窗口首先显示最近的私聊会话列表。
- 选择一个会话以显示与相应玩家的聊天内容。
- 与该玩家进行聊天。
3. 数据结构
私聊使用 FClientPersonalMsgInfo
和 FClientPersonalChatItem
数据结构。FClientPersonalMsgInfo
用于显示消息内容,FClientPersonalChatItem
用于显示玩家对话。
struct FClientPersonalMsgInfo
{
/** The player who sent the message */
FPlayerInfo sender;
/** The time the message was sent */
int64 sent_time;
/** The type of the message */
EClientChatMsgType msg_type;
/** The message to be sent */
FClientPersonalMsgContent content;
/** The sequence number of the message */
int64 seq;
/** Additional information added by the game */
FString custom_data;
};
struct FClientPersonalChatItem
{
/** The peer player info */
FPlayerInfo peer_player_info;
/** Unread message count */
int32 unread_count;
/** Recent chat message info */
FClientPersonalMsgInfo msg_latest;
/** Is the player my friend */
bool is_friend;
};
enum class EClientChatMsgType : uint8
{
MsgTypeText = 0 UMETA(DisplayName = "MsgTypeText"),
MsgTypeCustom = 1 UMETA(DisplayName = "MsgTypeCustom")
};
struct FClientPersonalMsgContent
{
/** Take the value from here when msg type is MsgTypeText */
FString text_content;
/** Take the value from here when msg type is MsgTypeCustom */
FString custom_content;
};
EClientChatMsgType
类型对应 FClientPersonalMsgContent
中的字段,目前 EClientChatMsgType
包括 MsgTypeText 和 MsgTypeCustom,当 EClientChatMsgType
为 MsgTypeText 时,消息内容在 FClientPersonalMsgContent
的 text_content 中,当 EClientChatMsgType
为 MsgTypeCustom 时,消息内容在 FClientPersonalMsgContent
的 custom_content 中。
除了以上数据结构,你可能还想了解以下几个概念:
- 私聊存储:
- 后端存储:后端会为每个玩家最多商店 50 条未读消息,若未读消息超过 50 条,则会清除最旧的消息,未读消息暂存时长最长为 7 天;但当有新的未读消息到达时,清理倒计时会重置,也就是说,如果有新的未读消息,临时存储会再保留 7 天。
- 本地存储: 私聊中发送和接收的消息都存储在客户端的本地数据库中,每个账号使用单独的数据库文件保存私聊消息,你可以在
pgos_config.ini
中配置chat_db_max_bytes
值来限制单个数据库文件的最大大小。我们还提供了 GetLocalCacheSize获取数据库文件大小和 ClearLocalCache 的 API#312-clearlocalcache-client-api)清除聊天记录的 API。您可以使用这些 API 在数据库文件过大时提示用户清除聊天记录。
- 私聊会话:跟踪与其他玩家的私聊互动。
- seq:随着玩家从给定玩家收到越来越多的消息,消息的序列号会增加。
- 未读私聊消息:未标记为已读的私聊消息。
注意:
chat_db_max_bytes
的最小值为 1MB(1048576),不建议设置超过 100MB 的值。如果您未指定值,则采用默认值 100MB。 当单个数据库文件超出chat_db_max_bytes
的值时,PGOS 会自动删除较旧的消息以释放空间。
4. 使用私聊
4.1 获取最近私聊会话列表
当玩家准备发起私聊时,可能需要使用 GetMyPersonalChatList
API 来查看最近的私聊会话列表。
/**
* Get my personal chat list
*/
void GetMyPersonalChatList(TFunction<void(const FPgosResult& Ret, const FClientGetMyPersonalChatListResult* Data)> Callback) const;
返回的结果保存在FClientGetMyPersonalChatListResult
结构中。返回值列表使用以下优先级排序:未读消息数量 > 最后一条消息的时间。
struct PGOSSDKCPP_API FClientGetMyPersonalChatListResult
{
/** Personal chat list */
TArray<FClientPersonalChatItem> personal_chat_list;
};
struct PGOSSDKCPP_API FClientPersonalChatItem
{
/** The peer player info */
FPlayerInfo peer_player_info;
/** Unread message count */
int32 unread_count;
/** Recent chat message info */
FClientPersonalMsgInfo msg_latest;
/** Is the player my friend */
bool is_friend;
};
按照以下方式从 PersonalChat 模块调用 GetMyPersonalChatList
:
#include "PgosSDKCpp.h"
#include "Core/PgosErrorCode.h"
void SomeClass::GetMyPersonalChatList()
{
auto PersonalChat = IPgosSDKCpp::Get().GetClientPersonalChatAPI();
if (PersonalChat)
{
PersonalChat->GetMyPersonalChatList([](const FPgosResult& Ret, const FClientGetMyPersonalChatListResult* Result)
{
if (Ret.err_code == 0)
{
UE_LOG(LogTemp, Log, TEXT("GetMyPersonalChatList Success"));
}
else
{
UE_LOG(LogTemp, Log, TEXT("GetMyPersonalChatList Failed: err_code=(%d), err_msg=(%s)"), Ret.err_code,
*Ret.msg);
}
});
}
}
4.2 获取私聊消息列表
PGOS在本地存储私聊消息。您可以使用GetPersonalMsgList
分页查询功能来获取指定玩家的聊天记录。
/**
* Get chat message list with a player
*
* @param PeerPlayerId The peer player ID
* @param StartSeq The start sequence number of the message to query, 0 means start from the latest message.
* @param Count The count of message to get
*/
void GetPersonalMsgList(
const FString& PeerPlayerId,
int64 StartSeq,
int32 Count,
TFunction<void(const FPgosResult& Ret, const FClientGetPersonalChatMsgListResult* Data)> Callback) const;
返回的结果保存在 FClientGetPersonalChatMsgListResult
结构体中:
struct PGOSSDKCPP_API FClientGetPersonalChatMsgListResult
{
/** Chat message info list */
TArray<FClientPersonalMsgInfo> msg_list;
/** Whether has more message */
bool has_more;
/** The starting sequence for the next query */
int64 next_seq;
};
本地存储和获取聊天记录的机制如下图所示:
按照以下方式从 PersonalChat 模块调用 GetPersonalMsgList
:
#include "PgosSDKCpp.h"
#include "Core/PgosErrorCode.h"
void SomeClass::GetPersonalMsgList(const FString& PlayerId, int64 StartSeq)
{
auto PersonalChat = IPgosSDKCpp::Get().GetClientPersonalChatAPI();
if (PersonalChat)
{
PersonalChat->GetPersonalMsgList(PlayerId, StartSeq, 20, [](const FPgosResult& Ret, const FClientGetPersonalChatMsgListResult* Result)
{
if (Ret.err_code == 0)
{
UE_LOG(LogTemp, Log, TEXT("GetPersonalMsgList Success"));
}
else
{
UE_LOG(LogTemp, Log, TEXT("GetPersonalMsgList Failed: err_code=(%d), err_msg=(%s)"), Ret.err_code,
*Ret.msg);
}
});
}
}
4.3 发送私聊消息
所有玩家都可以使用玩家ID与对应的玩家发起私聊。发送私聊消息后,如果对方在线,将立即收到该消息。
发送消息有两种方式:
- SendPersonalTextMsg: 发送简单的文本消息。消息的
msg_type
为MsgTypeText
,消息的文本内容存储在content.text_content
中。 - SendPersonalCustomMsg: 用于发送游戏自定义消息。消息的
msg_type
为MsgTypeCustom
,消息内容存储在content.custom_content
中。
/**
* Send personal chat text message
*
* @param TextMsgReq Request struct for sending text message
*/
void SendPersonalTextMsg(
const FClientSendPersonalMsgParams& Params,
TFunction<void(const FPgosResult& Ret, const FClientPersonalMsgInfo* Data)> Callback) const;
/**
* Send personal chat custom message
*
* @param CustomMsgReq Request struct for sending custom message
*/
void SendPersonalCustomMsg(
const FClientSendPersonalMsgParams& Params,
TFunction<void(const FPgosResult& Ret, const FClientPersonalMsgInfo* Data)> Callback) const;
在此您需要注意输入参数。以SendPersonalTextMsg
接口的参数FClientPersonalTextMsgReq
为例:
struct FClientSendPersonalMsgParams
{
/** Peer player's ID */
FString peer_player_id;
/** content */
FString content;
/** Additional information added by the game */
FString custom_data;
};
content 通常是用户的输入内容,会经过内容审核过滤。custom_data 用于游戏开发者添加的额外信息,不会经过内容审核过滤。这里需要注意的是不要在 custom_data 中存储用户输入。 返回结果保存在 FClientPersonalMsgInfo
结构体中,该结构体在上文已详细描述。 从 PersonalChat 模块调用 SendPersonalTextMsg
/SendPersonalCustomMsg
的方式如下:
#include "PgosSDKCpp.h"
#include "Core/PgosErrorCode.h"
void SomeClass::SendPersonalTextMsg(const FString& PlayerId, const FString& Message)
{
FClientSendPersonalMsgParams Params;
Params.player_id = PlayerId;
Params.content = Message;
auto PersonalChat = IPgosSDKCpp::Get().GetClientPersonalChatAPI();
if (PersonalChat)
{
PersonalChat->SendPersonalTextMsg(Params, [](const FPgosResult& Ret, const FClientPersonalMsgInfo* Info)
{
if (Ret.err_code == 0)
{
UE_LOG(LogTemp, Log, TEXT("SendPersonalTextMsg Success"));
}
else
{
UE_LOG(LogTemp, Log, TEXT("SendPersonalTextMsg Failed: err_code=(%d), err_msg=(%s)"), Ret.err_code,
*Ret.msg);
}
});
}
}
void SomeClass::SendPersonalCustomMsg(const FString& PlayerId, const FString& CustomMessage)
{
FClientSendPersonalMsgParams Params;
Params.player_id = PlayerId;
Params.content = Message;
auto PersonalChat = IPgosSDKCpp::Get().GetClientPersonalChatAPI();
if (PersonalChat)
{
PersonalChat->SendPersonalCustomMsg(Params, [](const FPgosResult& Ret, const FClientPersonalMsgInfo* Info)
{
if (Ret.err_code == 0)
{
UE_LOG(LogTemp, Log, TEXT("SendPersonalCustomMsg Success"));
}
else
{
UE_LOG(LogTemp, Log, TEXT("SendPersonalCustomMsg Failed: err_code=(%d), err_msg=(%s)"), Ret.err_code,
*Ret.msg);
}
});
}
}
❗注意:玩家可以选择是否允许非好友向其发送私聊消息。详见隐私设置文档
Key Error Codes
###4.4 激活私聊
可以通过ActivePersonalChat
API开启与指定玩家的私聊 。 正常情况下,玩家在与其他玩家聊天时会不断收到消息,每收到一条消息,未读消息数都会发生变化,触发OnUnreadPersonalMsgCountNotify
事件和OnReceivePersonalMsg
事件 ,通知该玩家未读消息总数和未读消息数的变化。 该玩家正与其他玩家处于聊天界面,所以有新消息应该自动标记为已读。 为了避免未读消息总数反复发生变化而再次触发OnUnreadPersonalMsgCountNotify
事件 ,我们提供了ActivePersonalChat
API,用于开启与指定玩家的私聊。
- 开启前,每收到一条新消息,未读消息数都会发生变化,触发
OnUnreadPersonalMsgCountNotify
事件 。 - 激活后,指定玩家的未读消息数将保持为0,因此新消息不会触发OnUnreadPersonalMsgCountNotify
事件。
如下图示例,您当前正在与Jerry聊天,当您收到Jerry的消息时,默认情况下会将其标记为已读,并且不会改变未读消息总数。当您与Jerry处于聊天界面时,ActivePersonalChat
API会激活聊天,并且Jerry的未读消息数保持为0,因此当您收到Jerry的私聊消息时,不会触发OnUnreadPersonalMsgCountNotify
事件 。当您离开聊天界面时,会向API传递一个空的PlayerId
参数,这样就不会激活私聊 。
/**
* Set the current personal chat. When setting, OnUnreadPersonalMsgCountNotify will be pushed, and subsequent personal chat messages currently in progress are automatically marked as read
*
* @param PlayerId The player to set as active
*/
void ActivePersonalChat(const FString& PlayerId) const;
按照以下方式调用 PersonalChat 模块中的 ActivePersonalChat
:
#include "PgosSDKCpp.h"
#include "Core/PgosErrorCode.h"
void SomeClass::ActivePersonalChat(const FString& PlayerId)
{
auto PersonalChat = IPgosSDKCpp::Get().GetClientPersonalChatAPI();
if (PersonalChat)
{
PersonalChat->ActivePersonalChat(PlayerId);
}
}
注意:
- 同一时间只能激活一个聊天。
- 当
PlayerId
为空时,表示没有激活的会话。
4.5 清除未读私聊消息数
如果来自指定玩家的所有私聊消息都已读,您可以使用ClearPersonalUnreadCount
API将该玩家的未读消息数设置为零。
/**
* Mark messages with someone as all read
*
* @param PlayerId is the ID of the specified player. If it is null, the unread message counts for all current players are set to zero.
*/
void ClearPersonalUnreadCount(
const FString& PlayerId,
TFunction<void(const FPgosResult& Ret) const;
按照以下方式调用 PersonalChat 模块中的 ClearPersonalUnreadCount
:
清零未读私聊消息数量
如果来自指定玩家的所有私聊消息都已读,您可以使用 ClearPersonalUnreadCount
API 将该玩家的未读消息数量设置为零。
/**
* Mark messages with someone as all read
*
* @param PlayerId is the ID of the specified player. If it is null, the unread message counts for all current players are set to zero.
*/
void ClearPersonalUnreadCount(
const FString& PlayerId,
TFunction<void(const FPgosResult& Ret, const FClientClearPersonalUnreadCountResult* Data)> Callback) const;
按照以下方式调用 PersonalChat 模块中的 ClearPersonalUnreadCount
:
#include "PgosSDKCpp.h"
#include "Core/PgosErrorCode.h"
void SomeClass::ClearPersonalUnreadCount(const FString& PlayerId)
{
auto PersonalChat = IPgosSDKCpp::Get().GetClientPersonalChatAPI();
if (PersonalChat)
{
PersonalChat->ClearPersonalUnreadCount(PlayerId, [](const FPgosResult& Ret, const FClientClearPersonalUnreadCountResult* Result)
{
if (Ret.err_code == 0)
{
UE_LOG(LogTemp, Log, TEXT("ClearPersonalUnreadCount Success"));
}
else
{
UE_LOG(LogTemp, Log, TEXT("ClearPersonalUnreadCount Failed: err_code=(%d), err_msg=%s]"), Ret.err_code,
*Ret.msg);
}
});
}
}
4.6 监听私聊事件
使用私聊时会触发以下事件:
- OnUnreadPersonalMsgCountNotify: 登录后立即触发,以及未读消息总数发生变化时触发。事件结果保存在
FClientUnreadMsgCountNotifyEvt
中。 - OnReceivePersonalMsg: 接收到私聊消息时触发。事件结果保存在
FClientReceivePersonalMsgEvt
中。
struct PGOSSDKCPP_API FClientUnreadMsgCountNotifyEvt
{
/** Total unread count of personal chat or group chat */
int32 total_unread_count;
};
struct PGOSSDKCPP_API FClientReceivePersonalMsgEvt
{
/** Personal chat message received */
FClientPersonalMsgInfo msg;
/** Count of unread message from sender */
int32 unread_count;
};
按照以下方式监听来自 PersonalChat 模块的私聊事件:
#include "PgosSDKCpp.h"
#include "Core/PgosErrorCode.h"
void SomeClass::MonitorPersonalChatEvents()
{
auto PersonalChat = IPgosSDKCpp::Get().GetClientPersonalChatAPI();
if (PersonalChat)
{
PersonalChat->OnUnreadPersonalMsgCountNotify().AddUObject(this, &SomeClass::OnUnreadPersonalMsgCountNotify);
PersonalChat->OnReceivePersonalMsg().AddUObject(this, &SomeClass::OnReceivePersonalMsg);
}
}
void SomeClass::OnUnreadPersonalMsgCountNotify(const FClientUnreadMsgCountNotifyEvt& Event)
{
UE_LOG(LogTemp, Log, TEXT("OnUnreadPersonalMsgCountNotify"));
}
void SomeClass::OnReceivePersonalMsg(const FClientReceivePersonalMsgEvt& Event)
{
UE_LOG(LogTemp, Log, TEXT("OnReceivePersonalMsg"));
const auto MsgType = Event.msg.msg_type;
if (MsgType == EClientChatMsgType::MsgTypeText)
{
UE_LOG(LogTemp, Log, TEXT("receive text msg, content=(%s)"), *Event.msg.text_content);
} else if (MsgType == EClientChatMsgType::MsgTypeCustom) {
UE_LOG(LogTemp, Log, TEXT("receive custom msg, content=(%s)"), *Event.msg.custom_content);
}
}
5. 关键错误处理
Error Code | 相关API | 处理建议 |
---|---|---|
kBackendIMChatFrequencyLimit | SendPersonalTextMsg SendPersonalCustomMsg | 这表示操作频率超出限制。建议采用指数级递增的时间间隔重试:5秒、10秒、20秒等。 |
kBackendIMChatMsgIsTooLong | SendPersonalTextMsg SendPersonalCustomMsg | 这表示消息内容过长。消息长度限制为1000个字符。游戏应该向玩家显示提示信息,比如"消息过长,请缩短内容或拆分为多条消息发送。" |
kBackendIMChatCustomDataIsTooLong | SendPersonalTextMsg SendPersonalCustomMsg | 这表示自定义数据太长,限制是8000字节,游戏会捕获这个错误,避免使用超长的自定义数据。 |
kBackendProfaneWords | SendPersonalTextMsg SendPersonalCustomMsg | 这表示消息内容包含不当用语且无法被屏蔽。游戏应该向玩家提示类似"请删除不当用语后再发送消息"的提示。 |
kBackendErrPlayerIsPunished | SendPersonalTextMsg SendPersonalCustomMsg | 玩家受到惩罚。msg是一个JSON序列化字符串,包含截止时间(惩罚结束时的Unix时间戳(以秒为单位),小于等于0表示永久惩罚)和原因。 |