跳到主要内容

Group

2. 群组管理

###2.1 群组关键概念 两个结构体用于在不同场景下描述一个群组 。

  • FClientGroupInfo: 群组内和群外的玩家都可以访问。

  • FClientGroupDetail:只有群组内的玩家才能访问。

FClientGroupInfoFClientGroupDetail 中关键字段如下:

  • group_id: 群组的唯一标识。

  • name:每个群组都有一个名称,方便记忆或搜索,最大字符数限制为 300。

  • icon: 群组图标的 Uri,玩家可以为自己创建的群组配置一个酷炫的图标,最大字符数限制为 1024。

  • description:向其他玩家介绍你的群组描述性文字,无论他们是否在群组内,最大字符数限制为 1024。

  • tags:可以使用标签定义群组的分类。

  • owner_info: 群组主的玩家信息

  • member_count:当前群组成员数。

  • max_members: 群组成员最大数量,PGOS 将此字段固定为 200。

  • announcement:只有群组成员才能查看的公告文本。

  • join_rule:玩家入群组的规则。

    • Any:表示任何拥有此群组 id 的人都可以加入该群组 。
    • MemberInvite:必须得到群组成员邀请才能入群组 。
    • AdminInvite:必须得到群组管理员或群主邀请才能入群组 。
    • Forbid:入群规则未改变前,任何人均不得加入。
  • is_public:若群组外的玩家可以看到该群组则为 True。

  • is_offical:标识该群组是否由游戏官方人员创建。

FClientGroupMember 用于描述群组内玩家。FClientGroupMember 中关键字段如下:

  • player_info: 群组成员基本信息,类型为 FPlayerInfo

  • nickname:每个群组成员可以有一个昵称。

  • remark:成员的备注可由群组主或管理员设置。备注可用于自定义群组成员分类。

  • is_owner:True 表示该群组由该玩家所有。

  • is_admin:True 表示该玩家是管理员。

2.2 创建/加入/退出群组

玩家可以通过调用 UPgosGroupAPI::CreateGroup API 最多创建5个群组。创建群组的玩家将成为群组所有者。如果API执行成功,将返回一个指向 FClientGroupDetail 结构的指针。

#include "PgosSDKCpp.h"

void SomeUObjectClass::SomeFunction()
{
auto Group = IPgosSDKCpp::Get().GetClientGroupAPI();
if (Group)
{
FClientCreatGroupParams params;
// Fill create group params
Group->CreateGroup(params, [](const FPgosResult& Ret, const FClientGroupDetail* Data) {
if (Ret.err_code == (int32)Pgos::PgosErrCode::kSuccess)
{
UE_LOG(LogTemp, Log, TEXT("CreateGroup Successfully"));
}
else
{
UE_LOG(LogTemp, Log, TEXT("CreateGroup Failed: err_code=%d, err_msg=%s"), Ret.err_code, *Ret.msg);
}
});
}
}

玩家可以通过调用 UPgosGroupAPI::LeaveGroup API离开群组。群主在将所有权转让给其他群组成员之前无法离开群组。

#include "PgosSDKCpp.h"

void SomeUObjectClass::SomeFunction()
{
auto Group = IPgosSDKCpp::Get().GetClientGroupAPI();
if (Group)
{
FString GroupID;
Group->JoinGroup(GroupID, [](const FPgosResult& Ret, const FClientGroupDetail* Data) {
if (Ret.err_code == (int32)Pgos::PgosErrCode::kSuccess)
{
UE_LOG(LogTemp, Log, TEXT("JoinGroup Successfully"));
}
else
{
UE_LOG(LogTemp, Log, TEXT("JoinGroup Failed: err_code=%d, err_msg=%s"), Ret.err_code, *Ret.msg);
}
});
}
}

群组所有者可以通过调用 UPgosGroupAPI::DismissGroup API 永久解散群组。当群组被解散时,除群主外的所有群组成员都会收到 OnGroupDismissed 事件。

#include "PgosSDKCpp.h"

void SomeUObjectClass::SomeFunction()
{
auto Group = IPgosSDKCpp::Get().GetClientGroupAPI();
if (Group)
{
FString GroupID;
Group->LeaveGroup(GroupID, [](const FPgosResult& Ret, const FClientGroupDetail* Data) {
if (Ret.err_code == (int32)Pgos::PgosErrCode::kSuccess)
{
UE_LOG(LogTemp, Log, TEXT("LeaveGroup Successfully"));
}
else
{
UE_LOG(LogTemp, Log, TEXT("LeaveGroup Failed: err_code=%d, err_msg=%s"), Ret.err_code, *Ret.msg);
}
});
}
}

群组所有者可以通过调用 UPgosGroupAPI::DismissGroup API 永久解散群组。当群组被解散时,除群主外的所有群组成员都会收到 OnGroupDismissed 事件。

#include "PgosSDKCpp.h"

void SomeUObjectClass::SomeFunction()
{
auto Group = IPgosSDKCpp::Get().GetClientGroupAPI();
if (Group)
{
FString GroupID;
Group->DismissGroup(GroupID, [](const FPgosResult& Ret, const FClientGroupDetail* Data) {
if (Ret.err_code == (int32)Pgos::PgosErrCode::kSuccess)
{
UE_LOG(LogTemp, Log, TEXT("DismissGroup Successfully"));
}
else
{
UE_LOG(LogTemp, Log, TEXT("DismissGroup Failed: err_code=%d, err_msg=%s"), Ret.err_code, *Ret.msg);
}
});
}
}

2.3 创建官方群组

官方群组旨在帮助游戏运营者管理和引导玩家创建更好的游戏社交环境。官方群组与非官方群组的区别如下:

  • 官方群组只能通过PGOS网页门户创建。
  • 官方群组的成员数量上限更高。
  • 官方群组不占用群主的群组创建数量限制。

您可以按照以下步骤在网页门户上创建官方群组。

image-20220629183113584

2.4 群组搜索

UPgosGroupAPI::SearchGroup 提供了搜索公开群组的功能,无论这些群组是否为官方群组。 搜索群组时可以使用以下筛选条件:

  • official_filter:筛选群组的 is_official 字段。
  • left_room_filter:使用此筛选条件来指定是否检索已满员的群组。
  • tags_filter:筛选具有特定标签的群组。

如果未指定任何筛选条件,将返回一组 PGOS 推荐的群组。

#include "PgosSDKCpp.h"

void SomeUObjectClass::SomeFunction()
{
FClientSearchGroupParams params;
params.official_filter = FClientGroupOfficialFilterType::Dummy;
params.left_room_filter = FClientGroupRoomFilterType::Dummy;
params.tags_filter = [];
params.order_by = FClientGroupOrderBy::Dummy;
params.order_type = FClientGroupOrderType::Dummy;
params.offset = 0;
params.count = 20;
auto Group = IPgosSDKCpp::Get().GetClientGroupAPI();
if (Group)
{
Group->SearchGroup(params, [](const FPgosResult& Ret, const SearchGroupRsp* Data) {
if (Ret.err_code == (int32)Pgos::PgosErrCode::kSuccess)
{
UE_LOG(LogTemp, Log, TEXT("SearchGroup Successfully"));
}
else
{
UE_LOG(LogTemp, Log, TEXT("SearchGroup Failed: err_code=%d, err_msg=%s"), Ret.err_code, *Ret.msg);
}
});
}
}


2.5 群组邀请

加入群组的另一种方式是被群组成员邀请。邀请行为受 join_rule 参数约束。

当被邀请者在线时,会触发 OnGroupInvitation 事件,PGOS 会为离线玩家缓存邀请。玩家登录后,可以调用 UPgosGroupAPI::GetGroupInvitations API 来获取发送给该玩家的所有邀请。

玩家可以通过调用 UPgosGroupAPI::AcceptGroupInvitation API 接受邀请,然后玩家将自动加入群组。玩家可以通过调用 UPgosGroupAPI::RejectGroupInvitation API 拒绝邀请。已处理的邀请将从玩家的邀请列表中移除。

#include "PgosSDKCpp.h"

// Invite some players with msg 'Join us for having fun!'
void SomeUObjectClass::SomeFunction()
{
auto Group = IPgosSDKCpp::Get().GetClientGroupAPI();
if (Group)
{
FClientInvitePlayersToGroupParams Params;
// Fill invite players params
Group->InvitePlayersToGroup(Params, [](const FPgosResult& Ret) {
if (Ret.err_code == (int32)Pgos::PgosErrCode::kSuccess)
{
UE_LOG(LogTemp, Log, TEXT("InvitePlayersToGroup Successfully"));
}
else
{
UE_LOG(LogTemp, Log, TEXT("InvitePlayersToGroup Failed: err_code=%d, err_msg=%s"), Ret.err_code, *Ret.msg);
}
});
}
}

// Get group invitation list
void SomeUObjectClass::SomeFunction()
{
auto Group = IPgosSDKCpp::Get().GetClientGroupAPI();
if (Group)
{
Group->GetGroupInvitations(GroupID, [](const FPgosResult& Ret, const TArray<FClientGroupInvitation>* Data) {
if (Ret.err_code == (int32)Pgos::PgosErrCode::kSuccess)
{
UE_LOG(LogTemp, Log, TEXT("GetGroupInvitations Successfully"));
}
else
{
UE_LOG(LogTemp, Log, TEXT("GetGroupInvitations Failed: err_code=%d, err_msg=%s"), Ret.err_code, *Ret.msg);
}
});
}
}

// Accept a group invitation
void SomeUObjectClass::SomeFunction()
{
auto Group = IPgosSDKCpp::Get().GetClientGroupAPI();
if (Group)
{
FString GroupID;
FString InvitationId;
Group->AcceptGroupInvitation(GroupID, [](const FPgosResult& Ret, const FClientAcceptGroupInvitationRsp* Data) {
if (Ret.err_code == (int32)Pgos::PgosErrCode::kSuccess)
{
UE_LOG(LogTemp, Log, TEXT("AcceptGroupInvitation Successfully"));
}
else
{
UE_LOG(LogTemp, Log, TEXT("AcceptGroupInvitation Failed: err_code=%d, err_msg=%s"), Ret.err_code, *Ret.msg);
}
});
}
}

// Reject a group invitation
void SomeUObjectClass::SomeFunction()
{
auto Group = IPgosSDKCpp::Get().GetClientGroupAPI();
if (Group)
{
FString GroupID;
FString InvitationId;
Group->RejectGroupInvitation(GroupID, [](const FPgosResult& Ret, const FClientRejectGroupInvitationRsp* Data) {
if (Ret.err_code == (int32)Pgos::PgosErrCode::kSuccess)
{
UE_LOG(LogTemp, Log, TEXT("RejectGroupInvitation Successfully"));
}
else
{
UE_LOG(LogTemp, Log, TEXT("RejectGroupInvitation Failed: err_code=%d, err_msg=%s"), Ret.err_code, *Ret.msg);
}
});
}
}

2.6 修改群组资料

只有群主或管理员可以修改群组资料。可修改的字段如下:

FieldAPI
nameUPgosGroupAPI::SetGroupName
iconUPgosGroupAPI::SetGroupIcon
descriptionUPgosGroupAPI::SetGroupDesc
announcementUPgosGroupAPI::SetGroupAnnouncement
join_ruleUPgosGroupAPI::SetGroupJoinRule
is_publicUPgosGroupAPI::SetGroupPublic

上述接口遵循相同的模式。以下以SetGroupName为例给出示例代码。

#include "PgosSDKCpp.h"

// Invite some players with msg 'Join us for having fun!'
void SomeUObjectClass::SomeFunction()
{
FClientSetGroupNameParams Params;
// Fille set group name params;
auto Group = IPgosSDKCpp::Get().GetClientGroupAPI();
if (Group)
{
Group->SetGroupName(Params, [](const FPgosResult& Ret, const FClientGroupDetail* Data) {
if (Ret.err_code == (int32)Pgos::PgosErrCode::kSuccess)
{
UE_LOG(LogTemp, Log, TEXT("SetGroupName Successfully"));
}
else
{
UE_LOG(LogTemp, Log, TEXT("SetGroupName Failed: err_code=%d, err_msg=%s"), Ret.err_code, *Ret.msg);
}
});
}
}

2.7 Manage Group Members

Group members with different roles have different group management permissions:

OperationRolesTarget PeoplePermission
Set remarkOwner群组中的任何玩家
Administrator群组中的任何玩家
MuteOwner群组中除他自己以外的任何玩家
Owner他自己
Administrator群组中的任何玩家除了他自己和Owner以外
AdministratorOwner 以及玩家自己
UnmuteOwner群组中的任何玩家
Administrator群组中的任何玩家
RemoveOwner群组中除他自己以外的任何玩家
Owner他自己
Administrator普通群组成员
AdministratorOwner, 他自己以及其他管理员

以下是这些接口的示例。

使用 UPgosGroupAPI::GetGroupMembers API 获取群组成员集合。返回的数据是一个无序映射,因此游戏需要根据需求自行安排成员列表中玩家的顺序。

#include "PgosSDKCpp.h"

struct FClientGetGroupMembersRsp
{
/** Group members, key: player id, value: group member information. */
TMap<FString, FClientGroupMember> group_members;

/** Muted group members, key: player id, value: muted member information. */
TMap<FString, FClientMutedGroupMember> muted_members;
};

void SomeUObjectClass::SomeFunction()
{
auto Group = IPgosSDKCpp::Get().GetClientGroupAPI();
if (Group)
{
Group->GetGroupMembers(GroupID, [](const FPgosResult& Ret, const FClientGetGroupMembersRsp* Data) {
if (Ret.err_code == (int32)Pgos::PgosErrCode::kSuccess)
{
UE_LOG(LogTemp, Log, TEXT("GetGroupMembers Successfully"));
}
else
{
UE_LOG(LogTemp, Log, TEXT("GetGroupMembers Failed: err_code=%d, err_msg=%s"), Ret.err_code, *Ret.msg);
}
});
}
}

使用 UPgosGroupAPI::GetGroupMember API 获取群组成员信息。

#include "PgosSDKCpp.h"

struct FClientGetGroupMemberRsp
{
/** Whether the player is a member of a group. */
bool is_member;

/** Group member information. */
FClientMutedGroupMember member_info;
};

void SomeUObjectClass::SomeFunction()
{
auto Group = IPgosSDKCpp::Get().GetClientGroupAPI();
if (Group)
{
Group->GetGroupMember(GroupID, PlayerID, [](const FPgosResult& Ret, const FClientGetGroupMemberRsp* Data) {
if (Ret.err_code == (int32)Pgos::PgosErrCode::kSuccess)
{
UE_LOG(LogTemp, Log, TEXT("GetGroupMember Successfully"));
}
else
{
UE_LOG(LogTemp, Log, TEXT("GetGroupMember Failed: err_code=%d, err_msg=%s"), Ret.err_code, *Ret.msg);
}
});
}
}

使用 UPgosGroupAPI::SetMyGroupNickname API 修改当前登录玩家的昵称。

#include "PgosSDKCpp.h"

void SomeUObjectClass::SomeFunction()
{
auto Group = IPgosSDKCpp::Get().GetClientGroupAPI();
if (Group)
{
SetMyGroupNickname Params;
// Fille set my group nickname params
Group->SetMyGroupNickname(Params, [](const FPgosResult& Ret) {
if (Ret.err_code == (int32)Pgos::PgosErrCode::kSuccess)
{
UE_LOG(LogTemp, Log, TEXT("SetMyGroupNickName Successfully"));
}
else
{
UE_LOG(LogTemp, Log, TEXT("SetMyGroupNickName Failed: err_code=%d, err_msg=%s"), Ret.err_code, *Ret.msg);
}
});
}
}

使用 UPgosGroupAPI::RemoveGroupMember API 将玩家从群组中移除。

#include "PgosSDKCpp.h"

void SomeUObjectClass::SomeFunction()
{
auto Group = IPgosSDKCpp::Get().GetClientGroupAPI();
if (Group)
{
FString GroupId;
FString PlayerId;
Group->RemoveGroupMember(GroupID, PlayerId, [](const FPgosResult& Ret) {
if (Ret.err_code == (int32)Pgos::PgosErrCode::kSuccess)
{
UE_LOG(LogTemp, Log, TEXT("RemoveGroupMember Successfully"));
}
else
{
UE_LOG(LogTemp, Log, TEXT("RemoveGroupMember Failed: err_code=%d, err_msg=%s"), Ret.err_code, *Ret.msg);
}
});
}
}

使用 UPgosGroupAPI::SetGroupMemberRemark API 为群组成员设置备注。此 API 有一个批量操作版本 UPgosGroupAPI::BatchSetGroupMemberRemark

#include "PgosSDKCpp.h"

void SomeUObjectClass::SomeFunction()
{
auto Group = IPgosSDKCpp::Get().GetClientGroupAPI();
if (Group)
{
FClientSetGroupMemberRemarkParams Params;
// Fill params
Group->SetGroupMemberRemark(Params, [](const FPgosResult& Ret) {
if (Ret.err_code == (int32)Pgos::PgosErrCode::kSuccess)
{
UE_LOG(LogTemp, Log, TEXT("SetGroupMemberRemark Successfully"));
}
else
{
UE_LOG(LogTemp, Log, TEXT("SetGroupMemberRemark Failed: err_code=%d, err_msg=%s"), Ret.err_code, *Ret.msg);
}
});
}
}

使用 UPgosGroupAPI::MuteGroupMemberUPgosGroupAPI::UnmuteGroupMember 来禁言或解除禁言群组成员。

#include "PgosSDKCpp.h"

// Mute
void SomeUObjectClass::SomeFunction()
{
auto Group = IPgosSDKCpp::Get().GetClientGroupAPI();
if (Group)
{
FString GroupId;
FString PlayerId;
int32 MuteMins = 1;
Group->MuteGroupMember(GroupID, PlayerId, MuteMins, [](const FPgosResult& Ret, const FClientGroupMember* Data) {
if (Ret.err_code == (int32)Pgos::PgosErrCode::kSuccess)
{
UE_LOG(LogTemp, Log, TEXT("MuteGroupMember Successfully"));
}
else
{
UE_LOG(LogTemp, Log, TEXT("MuteGroupMember Failed: err_code=%d, err_msg=%s"), Ret.err_code, *Ret.msg);
}
});
}
}

// Unmute
void SomeUObjectClass::SomeFunction()
{
auto Group = IPgosSDKCpp::Get().GetClientGroupAPI();
if (Group)
{
FString GroupId;
FString PlayerId;
Group->UnmuteGroupMember(GroupID, PlayerId, [](const FPgosResult& Ret) {
if (Ret.err_code == (int32)Pgos::PgosErrCode::kSuccess)
{
UE_LOG(LogTemp, Log, TEXT("UnmuteGroupMember Successfully"));
}
else
{
UE_LOG(LogTemp, Log, TEXT("UnmuteGroupMember Failed: err_code=%d, err_msg=%s"), Ret.err_code, *Ret.msg);
}
});
}
}

使用 UPgosGroupAPI::GrantGroupAdminUPgosGroupAPI::RevokeGroupAdmin 来授予或撤销管理员角色。这些 API 只能由群组所有者使用。

#include "PgosSDKCpp.h"

// Grant
void SomeUObjectClass::SomeFunction()
{
auto Group = IPgosSDKCpp::Get().GetClientGroupAPI();
if (Group)
{
FString GroupId;
FString PlayerId;
Group->GrantGroupAdmin(GroupID, PlayerId, [](const FPgosResult& Ret) {
if (Ret.err_code == (int32)Pgos::PgosErrCode::kSuccess)
{
UE_LOG(LogTemp, Log, TEXT("GrantGroupAdmin Successfully"));
}
else
{
UE_LOG(LogTemp, Log, TEXT("GrantGroupAdmin Failed: err_code=%d, err_msg=%s"), Ret.err_code, *Ret.msg);
}
});
}
}

// Revoke
void SomeUObjectClass::SomeFunction()
{
auto Group = IPgosSDKCpp::Get().GetClientGroupAPI();
if (Group)
{
FString GroupId;
FString PlayerId;
Group->RevokeGroupAdmin(GroupID, PlayerId, [](const FPgosResult& Ret) {
if (Ret.err_code == (int32)Pgos::PgosErrCode::kSuccess)
{
UE_LOG(LogTemp, Log, TEXT("RevokeGroupAdmin Successfully"));
}
else
{
UE_LOG(LogTemp, Log, TEXT("RevokeGroupAdmin Failed: err_code=%d, err_msg=%s"), Ret.err_code, *Ret.msg);
}
});
}
}

使用 UPgosGroupAPI::TransferGroupOwnership 将群主角色转让给群组成员或管理员。这些API仅限群主使用。

#include "PgosSDKCpp.h"

void SomeUObjectClass::SomeFunction()
{
auto Group = IPgosSDKCpp::Get().GetClientGroupAPI();
if (Group)
{
FString GroupId;
FString PlayerId;
Group->TransferGroupOwnership(GroupID, PlayerId, [](const FPgosResult& Ret) {
if (Ret.err_code == (int32)Pgos::PgosErrCode::kSuccess)
{
UE_LOG(LogTemp, Log, TEXT("TransferGroupOwnership Successfully"));
}
else
{
UE_LOG(LogTemp, Log, TEXT("TransferGroupOwnership Failed: err_code=%d, err_msg=%s"), Ret.err_code, *Ret.msg);
}
});
}
}

2.8 获取群组信息/详情

使用 UPgosGroupAPI::GetGroupInfo 来请求玩家尚未加入的群组的基本信息。

#include "PgosSDKCpp.h"

void SomeUObjectClass::SomeFunction()
{
auto Group = IPgosSDKCpp::Get().GetClientGroupAPI();
if (Group)
{
FString GroupId;
Group->GetGroupInfo(GroupID, [](const FPgosResult& Ret, const FClientGroupInfo* Data) {
if (Ret.err_code == (int32)Pgos::PgosErrCode::kSuccess)
{
UE_LOG(LogTemp, Log, TEXT("GetGroupInfo Successfully"));
}
else
{
UE_LOG(LogTemp, Log, TEXT("GetGroupInfo Failed: err_code=%d, err_msg=%s"), Ret.err_code, *Ret.msg);
}
});
}
}

使用 UPgosGroupAPI::GetGroupDetail 来请求玩家已加入的群组的详细信息。此 API 有一个批量版本 UPgosGroupAPI::BatchGetGroupDetail

#include "PgosSDKCpp.h"

void SomeUObjectClass::SomeFunction()
{
auto Group = IPgosSDKCpp::Get().GetClientGroupAPI();
if (Group)
{
FString GroupId;
Group->GetGroupDetail(GroupID, [](const FPgosResult& Ret, const FClientGroupDetail* Data) {
if (Ret.err_code == (int32)Pgos::PgosErrCode::kSuccess)
{
UE_LOG(LogTemp, Log, TEXT("GetGroupDetail Successfully"));
}
else
{
UE_LOG(LogTemp, Log, TEXT("GetGroupDetail Failed: err_code=%d, err_msg=%s"), Ret.err_code, *Ret.msg);
}
});
}
}

2.9 群组发现

使用 UPgosGroupAPI::GetGroupDiscoveryList 可以分页获取群组发现数量。您还可以额外获取在门户网站上配置的指定数量的推荐群组。

  • 群组服务每天会根据活跃度和其他规则为每个群组计算分数。系统会选取分数最高的前20,000个群组并按降序排列。您可以使用 GetGroupDiscoveryList API的 offsetcount 参数进行分页获取群组。
  • 游戏也可以获取额外的推荐群组。游戏运营人员可以在门户网站上配置一定数量的推荐群组,其中部分群组可以置顶。游戏开发者可以使用 GetGroupDiscoveryList API的 recommend_count 参数来获取指定数量上限的推荐群组。在获取过程中,API会从门户网站配置的推荐群组中随机选取 recommend_count 个群组,优先获取置顶群组。
  • GetGroupDiscoveryList API还有一个 owner_os 参数。游戏可以使用此参数获取群主操作系统与指定操作系统匹配的群组。在大多数情况下,此参数可以留空。

img

接口原型:

struct FClientGetGroupDiscoveryListParams 
{
/** Only obtain groups where the group owner's OS is the specified OS. Currently supports passing in at most one OS. Empty means no filtering based on OS. */
TArray<EClientOS> owner_os;
/**
* Get the number of recommended groups additionally, cannot exceed 50.
* Recommended groups can be configured on the portal.
*/
int32 recommend_count = 0;
/** The starting position of the list. */
int32 offset = 0;
/** The query count, cannot exceed 50. */
int32 count = 0;
};

/**
* Paging to get the number of group discoveries. You can additionally obtain the specified number of recommendation groups configured on the portal.
*
* @param Params
*/
void GetGroupDiscoveryList(
const FClientGetGroupDiscoveryListParams& Params,
TFunction<void(const FPgosResult& Ret, const FClientGetGroupDiscoveryListResult* Data)> Callback) const;

示例代码:

#include "PgosSDKCpp.h"

void SomeUObjectClass::SomeFunction()
{
auto Group = IPgosSDKCpp::Get().GetClientGroupAPI();
if (Group)
{
FClientGetGroupDiscoveryListParams Params;
Group->GetGroupDiscoveryList(Params, [](const FPgosResult& Ret, const FClientGetGroupDiscoveryListResult* Data) {
if (Ret.err_code == (int32)Pgos::PgosErrCode::kSuccess)
{
UE_LOG(LogTemp, Log, TEXT("GetGroupDiscoveryList Success"));
}
else
{
UE_LOG(LogTemp, Log, TEXT("GetGroupDiscoveryList Failed: err_code=%d, err_msg=%s"), Ret.err_code, *Ret.msg);
}
});
}
}

2.10 自定义数据处理

群组自定义数据用于帮助游戏扩展群组功能。每个群组有两层自定义数据:"全局自定义数据"和"玩家自定义数据"。游戏开发者可以在其中存储任何数据来实现特定功能。

"全局自定义数据"是一个键值映射,用于存储全局范围的数据。所有成员都可以访问,但在游戏客户端中只有群主和管理员有权限存储和修改。

"玩家自定义数据"是一个 string->string(玩家ID -> 数据)映射,用于存储每个群组成员的数据,其中键是玩家ID,值是由所属成员设置的数据。所有成员都可以访问,但玩家只能存储和修改自己的玩家自定义数据。群主和管理员可以修改所有群组成员的玩家自定义数据。

当任何自定义数据发生变更时(包括全局自定义数据和玩家自定义数据),所有成员都会在游戏客户端收到GroupGlobalCustomDataChangedEvtGroupPlayerCustomDataChangedEvt事件。

sequenceDiagram PlayerA(Owner)->>GroupService: modify global custom data PlayerB->>GroupService: modify playerB's custom data GroupService-->>PlayerB: notify custom data changed GroupService-->>PlayerA(Owner): notify custom data changed GroupService-->>PlayerC: notify custom data changed

调用 SetGroupGlobalCustomData 来设置群组的全局自定义数据:

  #include "PgosSDKCpp.h"
#include "Core/PgosErrorCode.h"

void SomeUObjectClass::SetGroupGlobalCustomData()
{
auto group = IPgosSDKCpp::Get().GetClientGroupAPI();
if (group)
{
SetGroupGlobalCustomDataParams params;
group->SetGroupGlobalCustomData(params, [](
const FPgosResult& Ret, const SetGroupGlobalCustomDataResult * Data) {
if (Ret.err_code == (int32)Pgos::PgosErrCode::kSuccess)
{
UE_LOG(LogTemp, Log, TEXT("Success"));
}
else
{
UE_LOG(LogTemp, Log, TEXT("Failed: err_code=%d, err_msg=%s"), Ret.err_code, *Ret.msg);
}
});
}
}

调用 DelGroupGlobalCustomData 通过Key删除全局自定义数据的任意字段:

  #include "PgosSDKCpp.h"
#include "Core/PgosErrorCode.h"

void SomeUObjectClass::DelGroupGlobalCustomData()
{
auto group = IPgosSDKCpp::Get().GetClientGroupAPI();
if (group)
{
DelGroupGlobalCustomDataParams params;
group->DelGroupGlobalCustomData(params, [](
const FPgosResult& Ret, const DelGroupGlobalCustomDataResult * Data) {
if (Ret.err_code == (int32)Pgos::PgosErrCode::kSuccess)
{
UE_LOG(LogTemp, Log, TEXT("Success"));
}
else
{
UE_LOG(LogTemp, Log, TEXT("Failed: err_code=%d, err_msg=%s"), Ret.err_code, *Ret.msg);
}
});
}
}

调用 ClearGroupGlobalCustomData 以清除所有群组全局自定义数据字段:

  #include "PgosSDKCpp.h"
#include "Core/PgosErrorCode.h"

void SomeUObjectClass::ClearGroupGlobalCustomData()
{
auto group = IPgosSDKCpp::Get().GetClientGroupAPI();
if (group)
{
ClearGroupGlobalCustomDataParams params;
group->ClearGroupGlobalCustomData(params, [](const FPgosResult& Ret) {
if (Ret.err_code == (int32)Pgos::PgosErrCode::kSuccess)
{
UE_LOG(LogTemp, Log, TEXT("Success"));
}
else
{
UE_LOG(LogTemp, Log, TEXT("Failed: err_code=%d, err_msg=%s"), Ret.err_code, *Ret.msg);
}
});
}
}

调用 SetGroupMyPlayerCustomData 来设置玩家自定义数据:

  #include "PgosSDKCpp.h"
#include "Core/PgosErrorCode.h"

void SomeUObjectClass::SetGroupMyPlayerCustomData()
{
auto group = IPgosSDKCpp::Get().GetClientGroupAPI();
if (group)
{
SetGroupMyPlayerCustomDataParams params;
group->SetGroupMyPlayerCustomData(params, [](
const FPgosResult& Ret, const SetGroupMyPlayerCustomDataResult * Data) {
if (Ret.err_code == (int32)Pgos::PgosErrCode::kSuccess)
{
UE_LOG(LogTemp, Log, TEXT("Success"));
}
else
{
UE_LOG(LogTemp, Log, TEXT("Failed: err_code=%d, err_msg=%s"), Ret.err_code, *Ret.msg);
}
});
}
}

调用 BatchSetGroupPlayerCustomData 来批量设置玩家自定义数据。请注意,只有群组所有者或管理员有权限修改其他群组成员的玩家自定义数据:

  #include "PgosSDKCpp.h"
#include "Core/PgosErrorCode.h"

void SomeUObjectClass::BatchSetGroupPlayerCustomData()
{
auto group = IPgosSDKCpp::Get().GetClientGroupAPI();
if (group)
{
BatchSetGroupPlayerCustomDataParams params;
group->BatchSetGroupPlayerCustomData(params, [](
const FPgosResult& Ret, const BatchSetGroupPlayerCustomDataResult * Data) {
if (Ret.err_code == (int32)Pgos::PgosErrCode::kSuccess)
{
UE_LOG(LogTemp, Log, TEXT("Success"));
}
else
{
UE_LOG(LogTemp, Log, TEXT("Failed: err_code=%d, err_msg=%s"), Ret.err_code, *Ret.msg);
}
});
}
}

2.11 群组管理事件

总的来说,群组服务将向玩家发送的事件如下所示:

事件名称事件描述
OnGroupInvitation当收到群组邀请时将触发此事件。
OnRemovedFromGroup当玩家被移出群组时将触发此事件。
OnMutedInGroup当玩家在群组中被禁言时将触发此事件。
OnUnmutedInGroup当玩家在群组中被取消禁言时,将触发此事件。
OnGroupDismissed当群组被群主解散时将触发此事件。
OnBadgeNumOfGroupInvitation当玩家登录PGOS或群组邀请列表发生变化时,将触发此事件。

您可以使用 UPgosClientEventDispatcher 来处理这些事件的绑定问题。

按照以下方式将动态委托绑定到 OnGroupInvitation

  #include "PgosSDKCpp.h"

void SomeUObjectClass::SomeFunction()
{
auto Group = IPgosSDKCpp::Get().GetClientGroupAPI();
if (Group)
{
Group->OnGroupInvitation().AddUObject(this, &SomeUObjectClass::OnGroupInvitation);
}
}

void SomeUObjectClass::OnGroupInvitation(const FClientGroupInvitationEvt& event)
{
UE_LOG(LogTemp, Log, TEXT("OnGroupInvitation"));
}
  #include "PgosClientEventDispatcher.h"

void SomeUObjectClass::SomeFunction()
{
auto pDispatcher = UPgosClientEventDispatcher::GetPgosClientEventDispatcher();
if (pDispatcher)
{
pDispatcher->OnPlayerSessionChanged.Clear();
// add delegate callback
UPgosClientEventDispatcher::GetPgosClientEventDispatcher()->OnGroupInvitation.AddDynamic(
this,
&SomeUObjectClass::OnGroupInvitation);
// Before current object is destroyed, you should call
// `UPgosClientEventDispatcher::GetPgosClientEventDispatcher()->RemoveAll(this)`
// to clear event bindings.
}
}

void SomeUObjectClass::OnGroupInvitation(const FClientGroupInvitationEvt& event)
{
UE_LOG(LogTemp, Log, TEXT("OnGroupInvitation"));
}

3. 群组聊天

3.1 一般流程

以下是使用群组聊天的一般流程:

sequenceDiagram participant t as Client(Tom) participant group as PGOS Group participant j as Client(Jerry) t->>group: Get Group List group-->>t: Return group list Tom joins j->>group: Join group(GroupA) group-->>t: Event(OnReceiveGroupMsg): [Event Msg] Jerry joins group-->>j: Event(OnReceiveGroupMsg): [Event Msg] Jerry joins t->>group: Send group message group-->>j: Event(OnReceiveGroupMsg): message from Tom t->>group: Leave group(GroupA) group-->>j: Event(OnReceiveGroupMsg): [Event Msg] Tom leaves

群组聊天的可能交互流程如下:

  1. 登录成功后,群组入口图标显示未读群消息数量。
  2. 点击群组图标,打开群聊窗口。
  3. 窗口首先显示群聊会话列表。
  4. 选择会话,显示与相应群组的聊天内容。
  5. 进行群组聊天。

img

3.2 数据结构

群组聊天使用两个重要的数据结构:FClientGroupMsgInfoFClientGroupChatItem

  • FClientGroupMsgInfo:用于显示消息内容,
  • FClientGroupChatItem:用于显示玩家对话。
struct FClientGroupMsgInfo
{
/** The member who sends the message */
FClientGroupMember sender;
/** The time the message was sent */
int64 sent_time;
/** The type of messages */
EClientChatMsgType msg_type;
/** The message to be sent */
FClientGroupMsgContent content;
/** The sequence number of messages */
int64 seq;
/** Additional information added by the game */
FString custom_data;
/**
* The mentioned player list.
* The mentioned players will receive the corresponding events. When the value of player_id is '@ALL', it means mentioning all players.
*/
TArray<FClientMentionedPlayer> mentioned_players;
};

struct FClientGroupChatItem
{
/** Group brief introduction */
FClientGroupBrief group_brief;
/** Recent chat message info */
FClientGroupMsgInfo latest_msg;
/** Unread messages count */
int32 unread_count;
};

enum class EClientChatMsgType : uint8
{
/** Type of text message, sent via SendGroupTextMsg. */
MsgTypeText = 0,
/** Type of custom message, sent via SendGroupCustomMsg. */
MsgTypeCustom = 1,
/** Type of event message, sent by the system. */
MsgTypeEvent = 2
};

struct FClientGroupMsgContent
{
/** Take the value from here when msg type is MsgTypeText */
FString text_content;
/** Take the value from here when msg type is MsgTypeEvent */
FClientGroupMsgEventContent event_content;
/** Take the value from here when msg type is MsgTypeCustom */
FString custom_content;
};

EClientChatMsgType 类型对应 FClientGroupMsgContent 中的字段。

目前,EClientChatMsgType 包括 MsgTypeTextMsgTypeCustomMsgTypeEvent

  • EClientChatMsgTypeMsgTypeText 时,消息内容位于 FClientGroupMsgContenttext_content 中。
  • EClientChatMsgTypeMsgTypeCustom 时,消息内容位于 FClientGroupMsgContentcustom_content 中。
  • EClientChatMsgTypeMsgTypeEvent 时,消息内容位于 FClientGroupMsgContentevent_content 中。

除了上述数据结构外,您可能还需要了解以下概念:

  • seq: 消息序列号,随着玩家从特定玩家收到的消息增多而递增。
  • 未读群聊消息: 尚未阅读的群聊消息。

3.2.1 群组聊天存储

群组聊天中发送和接收的消息都存储在客户端的本地数据库中。每个账号使用单独的数据库文件来保存群组聊天消息。您可以通过配置 chat_msg_cache_max_bytes 值来限制单个数据库文件的最大大小。我们还提供了 GetLocalCacheSize API 来获取数据库文件大小,以及 ClearLocalCache API 来清除聊天记录。当数据库文件过大时,您可以使用这些 API 提示用户清除聊天记录。

注意:

chat_msg_cache_max_bytes 的最小值为 1 MB(1048576)。我们不建议设置超过 100 MB 的值。如果您未指定值,则默认值为 100 MB。当单个数据库文件超过 chat_msg_cache_max_bytes 的值时,PGOS 将自动删除较早的消息以释放空间。

3.2.2 事件消息过滤

目前存在多种事件消息,游戏可能需要全部或部分类型。

默认支持所有事件消息,所有事件消息都会触发 OnReceiveGroupMsg 事件,所有事件消息类型也会出现在 GetMyGroupChatListGetGroupMsgList 接口中。如果游戏只需要某些事件消息类型,并完全过滤掉其他事件消息,可以通过配置文件(pgos_client.ini)或 UPgosClientAPI::InitConfig API 进行修改:

  • group_event_msg_filter 为空:需要所有事件消息类型。
group_event_msg_filter =
  • group_event_msg_filter 为 0(EClientGroupEventType::Dummy 的值):表示不需要任何事件消息,所有事件消息都将被过滤掉。

    group_event_msg_filter = 0
  • group_event_msg_filter 用于多个事件类型:使用","连接需要支持的事件消息的事件类型值。

    ; For example, only need GroupNameChanged, GroupDescChanged, GroupMemberJoined
    group_event_msg_filter = 1,2,3

注意:

事件消息不会被计入未读消息。

3.3 使用群组聊天

3.3.1 获取我的群组列表

当玩家准备发起群组聊天时,可能需要使用 GetMyGroupList API 来查看已加入的群组列表。

/**
* Get my group chat list
*/
void GetMyGroupChatList(TFunction<void(const FPgosResult& Ret, const FClientGetMyGroupChatListResult* Data)> Callback) const;

返回结果保存在FClientGetMyGroupChatListResult结构中。返回值列表的优先级顺序如下:未读消息数量 > 最后一条消息的时间。

struct FClientGetMyGroupChatListResult
{
/** My group conversation list */
TArray<FClientGroupChatItem> group_chat_list;
};

struct FClientGroupChatItem
{
/** Group brief information */
FClientGroupBrief group_brief;
/** Recent chat message info */
FClientGroupMsgInfo latest_msg;
/** Unread message count */
int32 unread_count;
};

群组模块调用GetMyGroupChatList,如下所示:

#include "PgosSDKCpp.h"

void SomeUObjectClass::SomeFunction()
{
auto Group = IPgosSDKCpp::Get().GetClientGroupAPI();
if (Group)
{

Group->GetMyGroupChatList([](const FPgosResult& Ret, const FClientGetMyGroupChatListResult* Data) {
if (Ret.err_code == (int32)Pgos::PgosErrCode::kSuccess)
{
UE_LOG(LogTemp, Log, TEXT("GetMyGroupChatList "));
}
else
{
UE_LOG(LogTemp, Log, TEXT("GetMyGroupChatList Failed: err_code=%d, err_msg=%s"), Ret.err_code, *Ret.msg);
}
});
}
}

3.3.2 获取群聊消息列表

PGOS会在本地存储群聊消息。您可以使用GetGroupMsgList分页查询功能来获取指定群组的聊天记录。

/**
* Get chat message list in a group
*
* @param GroupId The group ID
* @param StartSeq The start sequence number of the message to query, 0 means starting from the latest message.
* @param Count The count of messages to get
*/
void GetGroupMsgList(
const FString& GroupId,
int64 StartSeq,
int32 Count,
TFunction<void(const FPgosResult& Ret, const FClientGetGroupChatMsgListResult* Data)> Callback) const;

返回的结果保存在 FClientGetGroupChatMsgListResult 结构体中:

struct FClientGetGroupChatMsgListResult
{
/** Chat messages list */
TArray<FClientGroupMsgInfo> msg_list;
/** Whether there are more messages */
bool has_more;
/** The starting sequence for the next query */
int64 next_seq;
};

本地存储和获取聊天记录的机制如下图所示:

img

群组模块调用GetGroupMsgList,如下所示:

#include "PgosSDKCpp.h"

void SomeUObjectClass::SomeFunction()
{
auto Group = IPgosSDKCpp::Get().GetClientGroupAPI();
if (Group)
{

FString GroupId;
int64 StartSeq;
int32 Count;
Group->GetGroupMsgList(GroupId, StartSeq, Count, [](const FPgosResult& Ret, const FClientGetGroupChatMsgListResult* Data) {
if (Ret.err_code == (int32)Pgos::PgosErrCode::kSuccess)
{
UE_LOG(LogTemp, Log, TEXT("GetGroupMsgList "));
}
else
{
UE_LOG(LogTemp, Log, TEXT("GetGroupMsgList Failed: err_code=%d, err_msg=%s"), Ret.err_code, *Ret.msg);
}
});
}
}

3.3.3 发送群组聊天消息

玩家可以向已加入的群组发送消息。群组聊天消息发送后,群组中的其他在线玩家将会收到该消息。群组聊天无法获取离线消息,也就是说,玩家上线后无法获取其离线期间群组内发送的消息。

发送消息有两种方式:

  • SendGroupTextMsg: 发送简单的文本消息。消息的msg_typeMsgTypeText,消息的文本内容存储在content.text_content中。
  • SendGroupCustomMsg: 用于发送游戏自定义消息。消息的msg_typeMsgTypeCustom,消息内容存储在content.custom_content中。
/**
* Send group chat text messages
*
* @param TextMsgReq Request struct for sending text message
*/
void SendGroupTextMsg(
const FClientSendGroupMsgParams& Params,
TFunction<void(const FPgosResult& Ret, const FClientSendGroupMsgResult* Data)> Callback) const;

/**
* Send group chat custom messages
*
* @param CustomMsgReq Request struct for sending custom messages
*/
void SendGroupCustomMsg(
const FClientSendGroupMsgParams& Params,
TFunction<void(const FPgosResult& Ret, const FClientSendGroupMsgResult* Data)> Callback) const;

在此您需要注意输入参数。以SendGroupTextMsg接口的参数FClientSendGroupMsgParams为例:

struct FClientSendGroupMsgParams
{
/** Group ID */
FString group_id;
/** Text content */
FString content;
/** Additional information added by the game */
FString custom_data;
/**
* The mentioned player list.
* The mentioned players will receive the corresponding events. When the value of player_id is '@ALL', it means mentioning all players.
*/
TArray<FClientMentionedPlayer> mentioned_players;
};

content 通常是用户的输入内容,会经过内容审核过滤。custom_data 用于游戏开发者添加的额外信息,不会经过内容审核过滤。这里需要注意的是不要在 custom_data 中存储用户输入。

mentioned_players 为可选项,用于提及指定玩家。FClientMentionedPlayerPlayer_id 为 "@ALL" 时表示提及所有玩家。被提及的玩家将收到 OnGroupNewMentioned 事件。

返回结果保存在 FClientGroupMsgInfo 结构中,详细说明见上文。

Group 模块调用 SendGroupTextMsg/SendGroupCustomMsg,示例如下:

  #include "PgosSDKCpp.h"

void SomeUObjectClass::SendGroupTextMsg()
{
auto Group = IPgosSDKCpp::Get().GetClientGroupAPI();
if (Group)
{
FClientSendGroupMsgParams Params;
Params.group_id = FString("1234");
Params.content = FString("hello");
Params.custom_data = FString("");
FClientMentionedPlayer MentionedAll;
MentionedAll.player_id = FString("@ALL"); // mention all members in the group
Params.mentioned_players.Add(MentionedAll);
Group->SendGroupTextMsg(Params, [](const FPgosResult& Ret, const FClientSendGroupMsgResult* Data) {
if (Ret.err_code == (int32)Pgos::PgosErrCode::kSuccess)
{
UE_LOG(LogTemp, Log, TEXT("SendGroupTextMsg "));
}
else
{
UE_LOG(LogTemp, Log, TEXT("SendGroupTextMsg Failed: err_code=%d, err_msg=%s"), Ret.err_code, *Ret.msg);
}
});
}
}

void SomeUObjectClass::SendGroupCustomMsg()
{
auto Group = IPgosSDKCpp::Get().GetClientGroupAPI();
if (Group)
{
FClientSendGroupMsgParams Params;
Params.group_id = FString("1234");
Params.content = FString("hello");
Params.custom_data = FString("");
FClientMentionedPlayer MentionedPlayer;
MentionedPlayer.player_id = FString("123456"); // mention player "123456"
MentionedPlayer.player_display_name = FString("Will");
Params.mentioned_players.Add(MentionedAll);
Group->SendGroupCustomMsg(Params, [](const FPgosResult& Ret, const FClientSendGroupMsgResult* Data) {
if (Ret.err_code == (int32)Pgos::PgosErrCode::kSuccess)
{
UE_LOG(LogTemp, Log, TEXT("SendGroupCustomMsg "));
}
else
{
UE_LOG(LogTemp, Log, TEXT("SendGroupCustomMsg Failed: err_code=%d, err_msg=%s"), Ret.err_code, *Ret.msg);
}
});
}
}

3.3.4 激活群聊

玩家可以通过 ActiveGroupChat API 激活指定的群聊。

通常情况下,玩家在群聊中会持续收到消息。每次收到消息时,未读消息数量都会增加,触发 OnUnreadGroupMsgCountNotify 事件和 OnReceiveGroupMsg 事件,通知总未读消息数和该群组的未读消息数的变化。当玩家在群聊界面时,新消息应该自动标记为已读。为了避免总未读数的重复变化再次触发 OnUnreadGroupMsgCountNotify 事件,我们提供了 ActiveGroupChat API,用于激活指定的群聊。

  • 激活前,每条新消息都会改变未读消息数,并触发 OnUnreadGroupMsgCountNotify 事件。
  • 激活后,指定群组的未读消息数将保持为0,因此新消息不会触发 OnUnreadGroupMsgCountNotify 事件。

如下图所示,您当前正在与GroupB聊天。当您收到来自GroupB的消息时,这些消息默认标记为已读,不会改变总未读消息数。当您在与Jerry的聊天界面时,ActiveGroupChat API激活了聊天,Jerry的未读消息数保持为0,因此当您收到来自Jerry的群聊消息时,不会触发 OnUnreadGroupMsgCountNotify 事件。当您离开聊天界面时,调用API时传入null GroupId,这样当前会话将被取消激活。

img

/**
* Set the current group chat. When setting, OnUnreadGroupMsgCountNotifyEvt for this group will NOT be pushed, and subsequent group chat messages currently in progress are automatically marked as read
*
* @param GroupId The group to set as active
*/
void ActiveGroupChat(const FString& GroupId) const;

按照以下方式调用 群组 模块中的 ActiveGroupChat:

#include "PgosSDKCpp.h"

void SomeClass::ActiveGroupChat(const FString& GroupId)
{
auto GroupChat = IPgosSDKCpp::Get().GetClientGroupAPI();
if (GroupChat)
{
GroupChat->ActiveGroupChat(GroupId);
}
}

注意:

  • 同一时间只能激活一个群组聊天。
  • 使用空的 GroupId 调用可以取消激活当前会话。

3.3.5 清除群组未读消息数

当您已阅读完指定群组的所有聊天消息后,可以使用 ClearGroupUnreadCount API 将该群组的未读消息数设置为零。

/**
* Mark messages in group as all read
*
* @param GroupId The group ID. If empty, it means all groups
*/
void ClearGroupUnreadCount(
const FString& GroupId,
TFunction<void(const FPgosResult& Ret)> Callback) const;

按照以下方式调用 群组 模块中的 ClearGroupUnreadCount

#include "PgosSDKCpp.h"

void SomeClass::ClearGroupUnreadCount(const FString& GroupId)
{
auto group = IPgosSDKCpp::Get().GetClientGroupAPI();
if (group)
{
group->ClearGroupUnreadCount(GroupId, [](const FPgosResult& Ret)
{
if (Ret.err_code == 0)
{
UE_LOG(LogTemp, Log, TEXT("ClearGroupUnreadCount "));
}
else
{
UE_LOG(LogTemp, Log, TEXT("ClearGroupUnreadCount Failed: err_code=(%d), err_msg=%s]"), Ret.err_code,
*Ret.msg);
}
});
}
}

3.3.6 提及玩家

发送消息时可以提及指定玩家。详情请查看发送群聊消息

如果群组中有人提及了您,您将收到 OnGroupNewMentioned 事件。

您可以通过调用 GetGroupMentionedInfo 接口查询指定群组的最后一条提及记录。

您可以通过调用 ClearGroupMentionedInfo 接口清除指定群组提及您的记录。当 group_id 为空时,表示清除所有群组的提及记录。

接口原型:

接口原型:

/**
* Get latest record mentioning me in the group.
*
* @param GroupId Group ID.
* @param Dst The latest record mentioned me.
* @return True if there are any mentions of me.
*/
bool GetGroupMentionedInfo(
const FString& GroupId,
FClientGroupMentionedInfo& Dst) const;

/**
* Clear record mentioning me in the group.
*
* @param GroupId Group ID. If empty, it means all groups
*/
void ClearGroupMentionedInfo(const FString& GroupId) const;

示例代码:

#include "PgosSDKCpp.h"

void SomeUObjectClass::SomeFunction1()
{
auto Group = IPgosSDKCpp::Get().GetClientGroupAPI();
if (Group)
{

FString GroupId = TEXT("123");
FClientGroupMentionedInfo Dst;
auto result = Group->GetGroupMentionedInfo(GroupId, Dst);
if (result)
{
UE_LOG(LogTemp, Log, TEXT("%s mentioned you"), *Dst.msg.sender.player_info.display_name);
}
else
{
UE_LOG(LogTemp, Log, TEXT("No one mentioned you"));
}
}
}


void SomeUObjectClass::SomeFunction2()
{
auto Group = IPgosSDKCpp::Get().GetClientGroupAPI();
if (Group)
{
FString GroupId = TEXT("123");
Group->ClearGroupMentionedInfo(GroupId);
}
}

3.3.7 监控群组聊天事件

使用群组聊天时会触发以下事件:

  • OnUnreadGroupMsgCountNotify: 在登录后立即触发,以及当总未读消息数发生变化时触发。事件结果保存在 FClientUnreadMsgCountNotifyEvt 中。
  • OnReceiveGroupMsg: 接收到群组聊天消息时触发。事件结果保存在 FClientReceiveGroupMsgEvt 中。
  • OnGroupNewMentioned: 接收到提及(@)你的群组聊天消息时触发。事件结果保存在 FClientGroupNewMentionedEvt 中。
struct FClientUnreadMsgCountNotifyEvt
{
/** Total unread count of personal chat or group chat */
int32 total_unread_count;
};

struct FClientReceiveGroupMsgEvt
{
/** Group chat message received */
FClientGroupMsgInfo msg;
/** Group brief info */
FClientGroupBrief group_brief;
/** Count of unread message from group */
int32 unread_count;
};

struct FClientGroupNewMentionedEvt
{
/** Group chat message received */
FClientGroupMsgInfo msg;
/** Group brief info */
FClientGroupBrief group_brief;
};

OnReceiveGroupMsg: 可以根据 msg_type 区分可能收到的消息类型:

enum class EClientChatMsgType : uint8
{
/** Type of text message, sent via SendGroupTextMsg. */
MsgTypeText = 0,
/** Type of custom message, sent via SendGroupCustomMsg. */
MsgTypeCustom = 1,
/** Type of event message, sent by the system. */
MsgTypeEvent = 2
// New msg_types such as MsgTypeImage, MsgTypeVoice may be added in the future.
};

现在有三种消息类型:MsgTypeTextMsgTypeCustomMsgTypeEvent。其中 MsgTypeTextMsgTypeCustom 由玩家发送,MsgTypeEvent 由系统发送。

类型为 MsgTypeEvent 的消息称为事件消息,可以根据 event_type 区分不同的事件消息:

enum class EClientGroupEventType : uint8
{
Unknown = 0,
/** Group name has been changed. */
GroupNameChanged = 1,
/** Group description has been changed. */
GroupDescChanged = 2,
/** New member joined. */
GroupMemberJoined = 3,
/** Member left the group. */
GroupMemberLeft = 4,
/** Member removed. */
GroupMemberRemoved = 5,
/** Group announcement has been changed. */
GroupAnnouncementChanged = 6,
/** Group owner has changed. */
GroupOwnerChanged = 7
// New event_types may be added in the future.
};

通过群组模块监控群聊事件,具体如下:

#include "PgosSDKCpp.h"

void SomeClass::MonitorGroupChatEvents()
{
auto GroupChat = IPgosSDKCpp::Get().GetClientGroupAPI();
if (GroupChat)
{
GroupChat->OnUnreadGroupMsgCountNotify().AddUObject(this, &SomeClass::OnUnreadGroupMsgCountNotify);
GroupChat->OnReceiveGroupMsg().AddUObject(this, &SomeClass::OnReceiveGroupMsg);
}
}

void SomeClass::OnUnreadGroupMsgCountNotify(const FClientUnreadMsgCountNotifyEvt& Event)
{
UE_LOG(LogTemp, Log, TEXT("OnUnreadGroupMsgCountNotify"));
}

void SomeClass::OnReceiveGroupMsg(const FClientReceiveGroupMsgEvt& Event)
{
UE_LOG(LogTemp, Log, TEXT("OnReceiveGroupMsg"));
const auto MsgType = Event.msg.msg_type;
if (MsgType == EClientChatMsgType::MsgTypeText)
{
UE_LOG(LogTemp, Log, TEXT("receive text msg, content=(%s)"), *Event.msg.content.text_content);
} else if (MsgType == EClientChatMsgType::MsgTypeCustom) {
UE_LOG(LogTemp, Log, TEXT("receive custom msg, content=(%s)"), *Event.msg.content.custom_content);
} else if (MsgType == EClientChatMsgType::MsgTypeEvent) {
const auto EventType = Event.msg.content.event_content.event_type;
UE_LOG(LogTemp, Log, TEXT("receive event msg, event_type=(%d)"), *EventType);
if (EventType == EClientGroupEventType::GroupNameChanged) {
const auto GroupNameChanged = Event.msg.content.event_content.group_name_changed;
UE_LOG(LogTemp, Log, TEXT("group name changed, new group_name=(%s)"), *GroupNameChanged.group_name);
} else if (EventType == EClientGroupEventType::GroupMemberJoined) {
const auto GroupMemberJoined = Event.msg.content.event_content.group_member_joined;
UE_LOG(LogTemp, Log, TEXT("new member joined, name=(%s)"), *GroupMemberJoined.member_info.display_name);
}
// ......
}
}

4. 门户操作

您可以在网页门户上查询群组列表,如下所示。

image-20220622161030871

您可以点击"添加官方群组"按钮来创建一个官方群组。

image-20220629183034145

image-20220629162413198

您可以进入指定群组,在群组详情页面点击"解散"按钮来解散该群组。

image-20220629162730982

您可以按照以下所示在网页门户中查询指定玩家的群组列表。

image-20220622161219867

5. 关键错误处理

Error Code相关 API处理建议
PgosErrCode::kBackendProfaneNameCreateGroup ModifyGroupInfo当存在敏感词时,API会返回错误。请检查API的输入数据。
PgosErrCode::kBackendGroupNotPublicGetGroupInfo这表示未加入的玩家无法访问该群组信息。
PgosErrCode::kBackendIMChatFrequencyLimitSendGroupTextMsg SendGroupCustomMsg这表示操作频率超出限制。建议采用指数级递增的时间间隔重试:5秒、10秒、20秒等。
PgosErrCode::kBackendIMChatMsgIsTooLongSendGroupTextMsg SendGroupCustomMsg这表示消息内容过长。字符限制为1000个字符。游戏应该向玩家提示类似"消息过长,请缩短内容或将其分成多条消息发送"的提示信息。
PgosErrCode::kBackendIMChatCustomDataIsTooLongSendGroupTextMsg SendGroupCustomMsg这表示自定义数据过长。限制为8000字节。游戏会捕获此错误并避免使用超长的自定义数据。
PgosErrCode::kBackendIMPlayerBlockedByAdminSendGroupTextMsg SendGroupCustomMsg这表示该玩家已被管理员禁言。游戏应该向玩家显示类似"已被管理员禁言,请稍后重试"的提示信息。
PgosErrCode::kBackendProfaneWordsSendGroupTextMsg SendGroupCustomMsg CreateGroup ModifyGroupInfo这表示消息内容包含不当用语且无法被屏蔽。游戏应该向玩家提示类似"请删除不当用语后再发送消息"的提示。
PgosErrCode::kBackendErrPlayerIsPunishedSendGroupTextMsg SendGroupCustomMsg ModifyGroupInfo玩家受到惩罚。msg是一个JSON序列化字符串,包含截止时间(惩罚结束时的Unix时间戳(以秒为单位),小于等于0表示永久惩罚)和原因。