Skip to main content

Typical Use Case

1. Case Implement Friends Feature

Related to Friends.

The studio wants to implement friends feature in the game: in the main interface of the game, there is a "Friends" icon, this icon will display the current number of add-friend requests. Touching the "Friends" icon will pop up a friends information window, which contains three parts: a Friends list , an add-friend Request list and a Search entry. As shown below:

all

The studio uses PGOS Friend to get through this.

1.1 Monitor the Add-Friend Request

image-20211108162443898

The first thing the studio needs to do is display the "Friends" icon and the number of add-friend request badges on it. As known that when the player login PGOS or the list of received add-friend requests changes, there will be an OnBadgeNumOfFriendReq event. So, we need to bind a dynamic delegate to OnBadgeNumOfFriendReq before login PGOS.

#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 Get Friends List

image-20211108162535908

When the player clicks the "Friends" icon, there will pop up a friends information window. By default, the Friends tab is selected, we should query the friends list of the player and display it.

#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 Get Request List

image-20211108162615552

When the Requests tab is selected, we should query the add-friend requests list and display it.

#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
}

For a specific player's request, you can view the player's detail info, then accept the player's friend request, or just reject his/her friend request.

#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 Search Players

image-20211108162925101

A player can search for players he/she is interested in (by player id or partial nickname), and send a request to the player to add friends.

#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...
}

If the player is interested in someone, he/she can click the add friend button to send a add-friend request.

#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);
}
});
}

So far, the studio has implemented a friends feature with basic functions. For more functions of friends, go to the Friends module.

2. Case Personal Chat between Players

Related to Personal Chat.

The studio needs to implement a personal chat feature for players: players can have 1vs1 text chat with each other. When players have new unread messages, the main interface will prompt the number of unread messages. After clicking, a window containing all recent personal chat players will pop up, the logged-in player can chat with other players in this window. As shown below:

image-20211109192811035

The studio uses PGOS Personal Chat to get through this.

2.1 Monitor the Unread Messages

We will talk about how to make a personal chat with other players in a later chapter. Now, let's talk about how the studio displays the number of unread messages in the main interface first. As known that after the player login PGOS or when the number of unread messages changes, there will be an OnUnreadPersonalMsgCountNotify event. So, we bind a dynamic delegate to OnUnreadPersonalMsgCountNotify before login PGOS.

#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 Get Recent Personal Chats

image-20211110145332272

When the Unread Message Icon in the main interface is clicked, a window containing all recent personal chat players will pop up. We should query and display the recent personal chat first.

#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 Get Chat Msgs of a Player

Now we have a recent personal chat list. As a design interaction, we will select / activate the first player in the list (which is also the player with the largest number of unread messages) to see the chat message list with this player.

#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 Send Personal Message

Obviously, you can choose to have a personal chat with any player in the recent personal chats, and of course, to receive new messages, you should bind a dynamic delegate to OnReceivePersonalMsg first.

#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.
}

On the other hand, as long as you can get another player's player ID, you can have a personal chat with him. For example, in lobby, battle and other scenes, you have the opportunity to obtain the player ID of another player.

#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.
}

So far, the studio has implemented a personal chat feature with basic functions. For more functions of personal chat, go to the Personal Chat module.

3. Case Multiple Players Chat Together

Related to Instant Chat.

Although Party and Lobby have built-in multi-player chat functions, in some scenarios that are not covered, the studio still hopes to customize multi-player chats by themselves. For example, in the battle scene, the game hopes to have a private chat within the team and have a open chat between all players, and players can switch chat channels according to their needs. As shown below:

image-20211112162710634

The studio uses PGOS Instant Chat to get through this.

3.1 Join/Leave Instant Chat

Take the battle scene as an example. When the player enters the battle, he/she may needs to join two chat channels: the team channel and the all-member channel. The chat channel id of Instant Chat is generated by the game itself, as long as it is unique. Since it is in battle, the studio can use PGOS battle session id to construct the chat channel id:

  • the team channel id = battle session id + team flag
  • the all-member channel id = battle session 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);
}
});
}

If the player leaves the battle, he/she needs to leave the chat channel he joined in the battle.

When players go offline, PGOS will automatically remove them from the instant chat channels they joined.

#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 Monitor the Instant Chat Events

The studio needs to render the message on the chat panel when the instant chat message arrives. And when players join/leave the chat channel, the studio also wants to push a system message to the chat panel. So, the game client needs to monitor the 3 events: OnReceiveInstantChatMsg, OnInstantChatMemberJoined and OnInstantChatMemberLeft.

#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 Send Instant Chat Message

After joining instant chat channels, players can send messages to the channel, which the other players on the channel will receive.

#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.
}

So far, the studio has implemented a multi-player chat feature with basic functions. For more functions of instant chat, go to the Instant Chat module.