跳到主要内容

Typical Use Case

案例1:实现好友功能

相关内容请参考好友功能

想要在游戏中实现好友功能:在游戏主界面上有一个"好友"图标,该图标会显示当前的好友请求数量。点击"好友"图标会弹出好友信息窗口,其中包含三个部分:好友列表、好友请求列表和搜索入口。如下图所示:

all

1.1 监控添加好友请求

image-20211108162443898

工作室首先需要显示"好友"图标以及其上的好友请求数量徽章。众所周知,当玩家登录 PGOS 或收到的好友请求列表发生变化时,会触发 OnBadgeNumOfFriendReq 事件。因此,我们需要在登录 PGOS 之前将动态委托绑定到 OnBadgeNumOfFriendReq

#include "PgosSDKCpp.h"

// monitor before login PGOS
void SomeUObjectClass::MonitorFriendEvents()
{
auto friend = IPgosSDKCpp::Get().GetClientFriendAPI();
if (!friend) return;

friend->OnBadgeNumOfFriendReq().AddUObject(this, &SomeUObjectClass::OnBadgeNumOfFriendReq);
}

void SomeUObjectClass::OnBadgeNumOfFriendReq(const FClientBadgeNumOfFriendReqEvt& event)
{
RenderFriendsBadgeNum(event.badge_num);
}

void SomeUObjectClass::RenderFriendsBadgeNum(int32 badge_num)
{
// perform the actual rendering work.
}

1.2 获取好友列表

image-20211108162535908

当玩家点击"好友"图标时,会弹出好友信息窗口。默认选中"好友"标签页,我们需要查询玩家的好友列表并显示。

#include "PgosSDKCpp.h"

// when the "Friends" icon is clicked
void SomeUObjectClass::OnFriendsIconClicked()
{
auto friend = IPgosSDKCpp::Get().GetClientFriendAPI();
if (!friend) return;

friend->GetFriendList([](const FPgosResult& Ret, const FClientFriendListInfo* Data) {
if (Ret.err_code == (int32)Pgos::PgosErrCode::kSuccess)
{
UE_LOG(LogTemp, Log, TEXT("OnGetFriendListSuccess"));
RenderFriendsList(Data);
}
else
{
UE_LOG(LogTemp, Log, TEXT("OnGetFriendListFailed: err_code=%d, err_msg=%s"), Ret.err_code, *Ret.msg);
}
});
}

void SomeUObjectClass::RenderFriendsList(const FClientFriendListInfo* Data)
{
if (!Data) return;

for (const FClientPlayerFriendInfo& FriendInfo: Data->friend_info_list)
{
FriendInfo;
// FriendInfo.player_info: basic info such as display name and avatar...
// FriendInfo.player_presence: online/offline, and status set by game.
// FriendInfo.remark: remark to the player, for example, "Tommy, a humorous man, met in the battle."
}

// perform the actual rendering work...
}

1.3 获取好友请求列表

image-20211108162615552

当选择"请求"标签页时,我们应当查询好友请求列表并显示出来。

#include "PgosSDKCpp.h"

// when the "Requests" tab is selected
void SomeUObjectClass::OnFriendRequestSelected()
{
auto friend = IPgosSDKCpp::Get().GetClientFriendAPI();
if (!friend) return;

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

// Reset the friends request num on the icon.
ResetFriendReqNumOnIcon();
}

void SomeUObjectClass::RenderFriendRequests(const FClientFriendReqListInfo* Data)
{
if (!Data) return;

for (const FClientFriendReqInfo& Request: Data->friend_request_info_list)
{
Request;
// Request.player_info: basic info such as display name and avatar...
// Request.message: the request message sent from the player who wants to add you as a friend.
// Request.created_time: The UTC timestamp(in seconds) when the request was created.
}

// perform the actual rendering work...
}

void SomeUObjectClass::ResetFriendReqNumOnIcon()
{
// perform the actual reset work
}

对于特定玩家的请求,您可以查看该玩家的详细信息,然后接受该玩家的好友请求,或者直接拒绝他/她的好友请求。

#include "PgosSDKCpp.h"

// when the "detail" button is clicked.
void SomeUObjectClass::OnDetailBtnClicked(const FPlayerInfo& PlayerInfo)
{
// display a player info window.
}

// when the "accept" button is clicked.
void SomeUObjectClass::OnAcceptBtnClicked(const FPlayerInfo& PlayerInfo)
{
auto friend = IPgosSDKCpp::Get().GetClientFriendAPI();
if (!friend) return;

friend->AcceptFriendRequest(PlayerInfo.player_id, [](const FPgosResult& Ret) {
if (Ret.err_code == (int32)Pgos::PgosErrCode::kSuccess)
{
UE_LOG(LogTemp, Log, TEXT("OnAcceptFriendRequest Success"));
}
else
{
UE_LOG(LogTemp, Log, TEXT("OnAcceptFriendRequest Failed: err_code=%d, err_msg=%s"), Ret.err_code, *Ret.msg);
}
});
}

// when the "reject" button is clicked.
void SomeUObjectClass::OnRejectBtnClicked(const FPlayerInfo& PlayerInfo)
{
auto friend = IPgosSDKCpp::Get().GetClientFriendAPI();
if (!friend) return;

friend->RejectFriendRequest(PlayerInfo.player_id, [](const FPgosResult& Ret) {
if (Ret.err_code == (int32)Pgos::PgosErrCode::kSuccess)
{
UE_LOG(LogTemp, Log, TEXT("OnRejectFriendRequest Success"));
}
else
{
UE_LOG(LogTemp, Log, TEXT("OnRejectFriendRequest Failed: err_code=%d, err_msg=%s"), Ret.err_code, *Ret.msg);
}
});
}

1.4 搜索玩家

image-20211108162925101

玩家可以搜索感兴趣的其他玩家(通过玩家ID或部分昵称),并向该玩家发送好友请求。

#include "PgosSDKCpp.h"

// when the textbox input changed.
void SomeUObjectClass::OnInputChanged(const FString& InputText)
{
auto friend = IPgosSDKCpp::Get().GetClientFriendAPI();
if (!friend) return;

FString SearchKey = InputText;
int32 Offset = 0; // 0: search for the first page.
int32 Count = 10; // 10: max count of items in one page.
friend->SearchPlayers(SearchKey, Offset, Count, [](const FPgosResult& Ret, const FClientPlayerSearchInfoList* Data) {
if (Ret.err_code == (int32)Pgos::PgosErrCode::kSuccess)
{
UE_LOG(LogTemp, Log, TEXT("OnSearchPlayers Success"));
RenderPlayerSearchInfo(Data);
}
else
{
UE_LOG(LogTemp, Log, TEXT("OnSearchPlayers Failed: err_code=%d, err_msg=%s"), Ret.err_code, *Ret.msg);
}
});
}

void SomeUObjectClass::RenderPlayerSearchInfo(const FClientPlayerSearchInfoList* Data)
{
if (!Data) return;

for (const FClientPlayerSearchInfo& SearchInfo: Data->info_list)
{
SearchInfo;
// SearchInfo.player_info: basic info such as display name and avatar...
// SearchInfo.player_presence: online/offline, and status set by game.
// SearchInfo.remark: remark to the player
// SearchInfo.is_friend: whether the searched player is already a friend of mine.
// SearchInfo.is_blocked: whether the searched player is placed in my blocked list.
}

// perform the actual rendering work...
}

如果玩家对某人感兴趣,可以点击"添加好友"按钮发送好友请求。

#include "PgosSDKCpp.h"

// when the "add friend" button is clicked.
void SomeUObjectClass::OnAddFriendClicked(const FPlayerInfo& PlayerInfo)
{
auto friend = IPgosSDKCpp::Get().GetClientFriendAPI();
if (!friend) return;

FString Message = TEXT("Hi there!");
friend->AddFriend(PlayerInfo.player_id, Message, [](const FPgosResult& Ret) {
if (Ret.err_code == (int32)Pgos::PgosErrCode::kSuccess)
{
UE_LOG(LogTemp, Log, TEXT("OnAddFriend Success"));
}
else
{
UE_LOG(LogTemp, Log, TEXT("OnAddFriend Failed: err_code=%d, err_msg=%s"), Ret.err_code, *Ret.msg);
}
});
}

这一步就已经实现了基础功能的好友功能。关于更多好友功能,请参考好友模块。

案例2:玩家之间的私聊

相关内容请参考私聊

实现私聊功能:玩家之间可以进行一对一的文字聊天。当玩家有新的未读消息时,主界面会提示未读消息数量。点击后会弹出包含所有最近私聊玩家的窗口,登录玩家可以在此窗口与其他玩家进行聊天。如下图所示:

image-20211109192811035

2.1 监控未读消息

我们将在后面的章节中讨论如何与其他玩家进行私聊。现在,让我们先来讨论工作室如何在主界面显示未读消息数量。众所周知,当玩家登录 PGOS 后或未读消息数量发生变化时,会触发 OnUnreadPersonalMsgCountNotify 事件。因此,我们需要在登录 PGOS 之前为 OnUnreadPersonalMsgCountNotify 绑定一个动态委托。

#include "PgosSDKCpp.h"

// monitor before login PGOS
void SomeUObjectClass::MonitorPersonalChatEvents()
{
auto chat = IPgosSDKCpp::Get().GetClientPersonalChatAPI();
if (!chat) return;

chat->OnUnreadPersonalMsgCountNotify().AddUObject(this, &SomeUObjectClass::OnUnreadPersonalMsgCountNotify);
}

void SomeUObjectClass::OnUnreadPersonalMsgCountNotify(const FClientUnreadMsgCountNotifyEvt& Event)
{
UE_LOG(LogTemp, Log, TEXT("OnUnreadPersonalMsgCountNotify"));
RenderUnreadPersonalMsgNum(Event.total_unread_count);
}

void SomeUObjectClass::RenderUnreadPersonalMsgNum(int32 UnreadMsgNum)
{
// perform the actual rendering work.
}

2.2 获取最近的个人聊天记录

image-20211110145332272

当点击主界面中的"未读消息图标"时,会弹出一个包含所有最近私聊玩家的窗口。我们应该首先查询并显示最近的私聊记录。

#include "PgosSDKCpp.h"

// update recent personal chat list
void SomeUObjectClass::UpdateRecentPersonalChat()
{
auto chat = IPgosSDKCpp::Get().GetClientPersonalChatAPI();
if (!chat) return;

chat->GetPersonalChatList([](const FPgosResult& Ret, const FClientGetMyPersonalChatListResult* Data) {
if (Ret.err_code == (int32)Pgos::PgosErrCode::kSuccess)
{
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);
}
});
}

void SomeUObjectClass::RenderRecentPersonalChat(const FClientGetMyPersonalChatListResult* Data)
{
if (!Data) return;

for (const FClientPersonalChatInfo& ChatInfo: Data->personal_chat_list)
{
ChatInfo;
// ChatInfo.peer_player_info: the peer player info.
// ChatInfo.unread_msg_count: unread msg count.
// ChatInfo.msg_latest: the latest chat message, including: time, message body, seq...
}

// perform the actual rendering work...
}

2.3 获取玩家聊天消息

现在我们有了最近的私聊列表。根据交互设计,我们将选择/激活列表中的第一个玩家(也是未读消息数量最多的玩家)来查看与该玩家的聊天消息列表。

#include "PgosSDKCpp.h"

// when the personal chat is selected.
void SomeUObjectClass::OnPersonalChatSelected(const FString& PlayerId)
{
auto chat = IPgosSDKCpp::Get().GetClientPersonalChatAPI();
if (!chat) return;

// after activation, the subsequent personal chat messages will be automatically marked as read,
// until another player chat activation occurs.
chat->ActivePersonalChat(PlayerId);

// query and display the latest 30 messages
int64 StartSeq = 0; // the start sequence number of the message to query, 0 means start from the latest message.
int32 Count = 30; // get up to 30 messages
chat->GetPersonalChatMsgList(PlayerId, StartSeq, Count, [](const FPgosResult& Ret, const FClientGetPersonalChatMsgListResult* Data) {
if (Ret.err_code == (int32)Pgos::PgosErrCode::kSuccess)
{
UE_LOG(LogTemp, Log, TEXT("GetPersonalChatMsgList Success"));
RenderMessageList(Data);
}
else
{
UE_LOG(LogTemp, Log, TEXT("GetPersonalChatMsgList Failed: err_code=(%d), err_msg=%s]"), Ret.err_code, *Ret.msg);
}
});
}

void SomeUObjectClass::RenderMessageList(const FClientGetPersonalChatMsgListResult* Data)
{
if (!Data) return;

// save for the next possible query
Data->has_more; // Whether has more message
Data->next_seq; // The starting sequence for the next query

for (const FClientPersonalMsgInfo& MsgInfo: Data->message_list)
{
MsgInfo;
// MsgInfo.sender: the player who send the message.
// MsgInfo.content: the message to be sent.
// MsgInfo.sent_time: the created time of the message (UTC).
// MsgInfo.seq: the sequence number of the message
// ...
}

// perform the actual rendering work...
}

2.4 发送私聊消息

显然,您可以选择与最近私聊列表中的任何玩家进行私聊,当然,要接收新消息,您需要先将动态委托绑定到 OnReceivePersonalMsg

#include "PgosSDKCpp.h"

// monitor before login PGOS
void SomeUObjectClass::MonitorPersonalChatEvents()
{
auto chat = IPgosSDKCpp::Get().GetClientPersonalChatAPI();
if (!chat) return;

//chat->OnUnreadPersonalMsgCountNotify().AddUObject(this, &SomeUObjectClass::OnUnreadPersonalMsgCountNotify);
chat->OnReceivePersonalMsg().AddUObject(this, &SomeUObjectClass::OnReceivePersonalMsg);
}

void SomeUObjectClass::OnReceivePersonalMsg(const FClientReceivePersonalMsgEvt& Event)
{
UE_LOG(LogTemp, Log, TEXT("OnReceivePersonalMsg"));

// if the sender player is not in the current personal chat list, then add to the list.
FString SenderPlayerID = Event.msg.sender.player_id;
if(!IsPlayerInPersonalChatList(SenderPlayerID))
{
AddPlayerToPersonalChatListUI(SenderPlayerID);
}

// if the sender player is not actived, update the unread msg num on UI.
if(IsActivePersonalChat(SenderPlayerID))
{
// add message to the message list panel ui
AddtoMsgListPanelUI(Event.msg);
}
else
{
// update the unread msg num on UI
UpdatePlayerUnreadMsgNum(SenderPlayerID, Event.unread_count);
}

}

// add message to the message list panel ui
void SomeUObjectClass::AddtoMsgListPanelUI(const FClientPersonalMsgInfo& MsgInfo)
{
MsgInfo;
// MsgInfo.sender: the player who send the message
// MsgInfo.content: the message to be sent.
// MsgInfo.sent_time: the created time of the message (UTC).
// MsgInfo.seq: the sequence number of the message
// ...

// perform the actual rendering work.
}

void SomeUObjectClass::SendMsgToPlayer(const FString& PlayerID, const FString& Message)
{
auto chat = IPgosSDKCpp::Get().GetClientPersonalChatAPI();
if (!chat) return;
FClientPersonalTextMsgReq req;
req.peer_player_id = PlayerID;
req.content = Message;
req.user_data = FString("");
chat->SendPersonalTextMsg(req, [](const FPgosResult& Ret, const FClientPersonalMsgInfo* Info) {
if (Ret.err_code == (int32)Pgos::PgosErrCode::kSuccess)
{
UE_LOG(LogTemp, Log, TEXT("SendPersonalTextMsg Success"));
AddtoMsgListPanelUI(Event.chat_msg_info); // add my msg to chat msg list on UI.
}
else
{
UE_LOG(LogTemp, Log, TEXT("SendPersonalTextMsg Failed: err_code=(%d), err_msg=%s]"), Ret.err_code, *Ret.msg);
}
});
}

// Check whether the player is already on the personal chat list
bool SomeUObjectClass::IsPlayerInPersonalChatList(const FString& PlayerID)
{
return false;
}

// Add this player to the UI of the personal chat list
void SomeUObjectClass::AddPlayerToPersonalChatListUI(const FString& PlayerID)
{
}

// Check whether the personal chat with this player has been activated
bool SomeUObjectClass::IsActivePersonalChat(const FString& PlayerID)
{
return false;
}

// update the unread msg num on UI
void SomeUObjectClass::UpdatePlayerUnreadMsgNum(const FString& PlayerID, int32 UnreadCnt)
{
// perform the actual rendering work.
}

另一方面,只要您获得其他玩家的玩家ID,就可以与他进行私聊。例如,在房间、战斗和其他场景中,您都有机会获取其他玩家的玩家ID。

#include "PgosSDKCpp.h"

// have a chat with someone
// We will gather all personal chats into the personal chat window
void SomeUObjectClass::HavePersonalChat(const FString& PlayerID)
{
ShowPersonalChatWindow();

// if the player is not in the current personal chat list, then add to the list.
if(!IsPlayerInPersonalChatList(PlayerID))
{
AddPlayerToPersonalChatListUI(PlayerID);
}
}

// Check whether the player is already on the personal chat list
bool SomeUObjectClass::IsPlayerInPersonalChatList(const FString& PlayerID)
{
return false;
}

// Add this player to the UI of the personal chat list
void SomeUObjectClass::AddPlayerToPersonalChatListUI(const FString& PlayerID)
{
}

// Show the Personal Chat Window
void SomeUObjectClass::ShowPersonalChatWindow(const FString& PlayerID)
{
// perform the actual rendering work.
}

到这一步就已经实现了基础功能的私聊。关于私聊的更多功能,请参考私聊模块。

案例3:多人聊天

相关内容参考即时聊天

虽然组队房间已经内置了多人聊天功能,但在一些未覆盖的场景中,工作室仍希望能自行定制多人聊天。例如在战斗场景中,游戏希望在队伍内有私密聊天,在所有玩家之间有公开聊天,玩家可以根据需要切换聊天频道。如下图所示:

image-20211112162710634

3.1 加入/退出即时聊天

以战斗场景为例。当玩家进入战斗时,他/她可能需要加入两个聊天频道:队伍频道和全体成员频道。即时聊天的聊天频道 ID 由游戏自行生成,只要保证其唯一性即可。由于是在战斗中,游戏可以使用 PGOS 战斗会话 ID 来构建聊天频道 ID:

  • 队伍频道 ID = 战斗会话 ID + 队伍标识
  • 全体成员频道 ID = 战斗会话 ID + "all"
#include "PgosSDKCpp.h"

// generate team chat channel id
FString SomeUObjectClass::GetMyTeamChatChannelID()
{
return CurBattleSessionID + MyTeamFlag;
}

// generate all-member chat channel id
FString SomeUObjectClass::GetAllMemberChatChannelID()
{
return CurBattleSessionID + TEXT("All");
}

// After entering the battle, the player automatically joins two chat channels
void SomeUObjectClass::JoinBattleChats(const FString& ChatChannelId)
{
JoinInstantChat(GetMyTeamChatChannelID());
JoinInstantChat(GetAllMemberChatChannelID());
}

// join PGOS instant chat
void SomeUObjectClass::JoinInstantChat(const FString& ChatChannelId)
{
auto chat = IPgosSDKCpp::Get().GetClientInstantChatAPI();
if (!chat) return;

chat->JoinInstantChat(ChatChannelId, [](const FPgosResult& Ret, const FClientJoinInstantChatResult* Result) {
if (Ret.err_code == (int32)Pgos::PgosErrCode::kSuccess)
{
UE_LOG(LogTemp, Log, TEXT("JoinInstantChat Success"));
}
else
{
UE_LOG(LogTemp, Log, TEXT("JoinInstantChat Failed: err_code=(%d), err_msg=%s]"), Ret.err_code, *Ret.msg);
}
});
}

如果玩家离开战斗,他/她需要退出在战斗中加入的聊天频道。

当玩家离线时,PGOS 会自动将他们从已加入的即时聊天频道中移除。

#include "PgosSDKCpp.h"

// After leaving the battle, the player automatically leaves the two chat channels
void SomeUObjectClass::LeaveBattleChats(const FString& ChatChannelId)
{
LeaveInstantChat(GetMyTeamChatChannelID());
LeaveInstantChat(GetAllMemberChatChannelID());
}

// leave PGOS instant chat
void SomeUObjectClass::LeaveInstantChat(const FString& ChatChannelId)
{
auto chat = IPgosSDKCpp::Get().GetClientInstantChatAPI();
if (!chat) return;

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

3.2 监听即时聊天事件

当即时聊天消息到达时,工作室需要在聊天面板上渲染消息。当玩家加入/离开聊天频道时,工作室还需要向聊天面板推送系统消息。因此,游戏客户端需要监听3个事件:OnReceiveInstantChatMsgOnInstantChatMemberJoinedOnInstantChatMemberLeft

#include "PgosSDKCpp.h"

// monitor instant chat events before joining the chat channel.
void SomeUObjectClass::MonitorInstantChatEvents()
{
auto chat = IPgosSDKCpp::Get().GetClientInstantChatAPI();
if (!chat) return;

chat->OnReceiveInstantChatMsg().AddUObject(this, &SomeClass::OnReceiveInstantChatMsg);
chat->OnInstantChatMemberJoined().AddUObject(this, &SomeClass::OnInstantChatMemberJoined);
chat->OnInstantChatMemberLeft().AddUObject(this, &SomeClass::OnInstantChatMemberLeft);
}

void SomeUObjectClass::OnReceiveInstantChatMsg(const FClientReceiveInstantChatMsgEvt& Event)
{
Event;
// Event.chat_channel_id: chat channel where the message was received.
// Event.chat_msg_info: Instant chat message received.

// add msg to the channel msg list UI.
}

void SomeUObjectClass::OnInstantChatMemberJoined(const FClientInstantChatMemberJoinedEvt& Event)
{
Event;
// Event.chat_channel_id: instant chat channel the player joined.
// Event.added_members: players added to the instant chat.
// Event.all_members: all the players in the channel after adding.

for (const FPlayerInfo& PlayerInfo: Event.added_members)
{
// Push a system message to the chat panel:
// {PlayerInfo.display_name} joined the chat channel.
}
}

void SomeUObjectClass::OnInstantChatMemberLeft(const FClientInstantChatMemberLeftEvt& Event)
{
Event;
// Event.chat_channel_id: instant chat channel the player left.
// Event.left_members: players left from the channel.
// Event.all_members: all the players in the channel after leaving.

for (const FPlayerInfo& PlayerInfo: Event.left_members)
{
// Push a system message to the chat panel:
// {PlayerInfo.display_name} left the chat channel.
}
}

3.3 发送即时聊天消息

加入即时聊天频道后,玩家可以向频道发送消息,频道内的其他玩家将会收到这些消息。

#include "PgosSDKCpp.h"

// send msg to instant chat channel
void SomeUObjectClass::SendInstantChatMsg(const FString& ChatChannelId, const FString& Message)
{
auto chat = IPgosSDKCpp::Get().GetClientInstantChatAPI();
if (!chat) return;

chat->SendInstantChatMsg(ChatChannelId, Message, [](const FPgosResult& Ret, const FClientChatMsgInfo* Data) {
if (Ret.err_code == (int32)Pgos::PgosErrCode::kSuccess)
{
UE_LOG(LogTemp, Log, TEXT("SendInstantChatMsgChat Success"));
AddMyMsgToChatPanel(ChatChannelId, Data)
}
else
{
UE_LOG(LogTemp, Log, TEXT("SendInstantChatMsg Failed: err_code=(%d), err_msg=%s]"), Ret.err_code, *Ret.msg);
}
});
}

// Add my sent msg to the chat panel
void SomeUObjectClass::AddMyMsgToChatPanel(const FString& ChatChannelId, const FClientChatMsgInfo* Data)
{
if (!Data) return;

Data;
// Data->from: the player who send the message
// Data->message: the message to be sent.
// Data->created_time: the created time of the message (UTC).
// Data->seq: the sequence number of the message

// perform the actual rendering work.
}

这一步就实现了基本功能的多人聊天功能。有关即时聊天的更多功能,请前往即时聊天模块。