聊天系统概述
PlayNANOO聊天系统提供基于实时WebSocket的聊天功能。本文档介绍聊天系统的整体结构和使用方法。
系统 组成
聊天系统由3个核心类组成。
| 类 | 作用 |
|---|---|
| ChatManager | 单例管理器,管理聊天服务器连接及API调用 |
| ChatClient | 处理WebSocket连接及消息收发的核心类 |
| ChatModels | 定义数据模型和事件监听器接口 |
必填 实现 说明
以上3个类是必须实现的类,可以根据用户的项目结构自由变更。
必须实现的要素:
- 实现
IChatListener接口的所有回调方法 - 调用用于WebSocket连接的服务器列表API (
PUT /chat/v20211101/server) - 实现HMAC-SHA256哈希认证逻辑 (参考ChatClient)
模块依赖 (Build.cs):
PublicDependencyModuleNames.AddRange(new string[]
{
"WebSockets", // WebSocket 연결
"OpenSSL" // HMAC-SHA256 해시
});
工作流程
1. ChatManager.Connect() 호출
↓
2. 서버 목록 API 요청 (PUT /chat/v20211101/server)
↓
3. 서버 목록 수신 후 랜덤 서버 선택
↓
4. ChatClient가 WebSocket 연결 수행
↓
5. 연결 성공 시 IChatListener.OnConnected() 호출
↓
6. 채널 구독 및 메시지 송수신
第1阶段:实现监听器
要接收聊天事件,需要实现IChatListener接口。
// MyChatHandler.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "ChatModels.h"
#include "MyChatHandler.generated.h"
UCLASS()
class YOURPROJECT_API AMyChatHandler : public AActor, public IChatListener
{
GENERATED_BODY()
public:
// IChatListener 구현
virtual void OnConnected() override
{
// 서버 연결 성공
UE_LOG(LogTemp, Log, TEXT("채팅 서버에 연결되었습니다."));
}
virtual void OnDisconnected() override
{
// 서버 연결 해제
UE_LOG(LogTemp, Log, TEXT("채팅 서버와 연결이 해제되었습니다."));
}
virtual void OnError(const FString& Code, const FString& Message) override
{
// 오류 발생
UE_LOG(LogTemp, Error, TEXT("오류: [%s] %s"), *Code, *Message);
}
virtual void OnChannels(const TArray<FChatChannelInfo>& Channels) override
{
// 채널 목록 수신
for (const auto& Ch : Channels)
{
UE_LOG(LogTemp, Log, TEXT("채널: %s, 접속자: %d명"), *Ch.Channel, Ch.Count);
}
}
virtual void OnSubscribed(const FChatUserInfo& User) override
{
// 사용자가 채널에 입장
UE_LOG(LogTemp, Log, TEXT("%s님이 입장했습니다."), *User.VisitorName);
}
virtual void OnUnSubscribed(const FChatUserInfo& User) override
{
// 사용자가 채널에서 퇴장
UE_LOG(LogTemp, Log, TEXT("%s님이 퇴장했습니다."), *User.VisitorName);
}
virtual void OnPublicMessage(const FChatUserInfo& Sender, const FString& Message) override
{
// 공개 메시지 수신
UE_LOG(LogTemp, Log, TEXT("[%s]: %s"), *Sender.VisitorName, *Message);
}
virtual void OnPrivateMessage(const FChatUserInfo& Sender, const FString& Message) override
{
// 귓속말 수신
UE_LOG(LogTemp, Log, TEXT("[귓속말][%s]: %s"), *Sender.VisitorName, *Message);
}
virtual void OnNotifyMessage(const FChatUserInfo& Sender, const FString& Message) override
{
// 시스템 알림 수신
UE_LOG(LogTemp, Log, TEXT("[시스템]: %s"), *Message);
}
virtual void OnPlayerOnline(const TArray<FChatPlayerInfo>& Players) override
{
// 플레이어 온라인 상태 수신
for (const auto& P : Players)
{
FString Status = (P.Online == TEXT("Y")) ? TEXT("온라인") : TEXT("오프라인");
UE_LOG(LogTemp, Log, TEXT("%s: %s"), *P.UserUniqueId, *Status);
}
}
};
2阶段: 聊天 服务器 连接
调用ChatManager::GetInstance().Connect()连接聊天服务器。
void AMyChatHandler::BeginPlay()
{
Super::BeginPlay();
// IChatListener를 구현한 객체를 전달
UChatManager::GetInstance()->Connect(this);
}
连接成功后将调用OnConnected()回调。
3阶段: 频道订阅
连接后订阅所需频道。
void AMyChatHandler::OnConnected()
{
// 글로벌 채널 구독
UChatManager::GetInstance()->Subscribe(TEXT("global"));
// 또는 특정 채널 구독
UChatManager::GetInstance()->Subscribe(TEXT("room_123"));
}
4阶段: 消息 发送
公开消息 发送
频道的所有 用户给 消息发送。
void AMyChatHandler::SendChatMessage(const FString& Text)
{
UChatManager::GetInstance()->SendPublicMessage(TEXT("global"), Text);
}
私聊 发送
仅向特定用户发送消息。
void AMyChatHandler::SendWhisper(const FString& TargetUserId, const FString& Text)
{
UChatManager::GetInstance()->SendPrivateMessage(TargetUserId, Text);
}
5阶段: 其他 功能
频道列表 查询
UChatManager::GetInstance()->GetChannels();
// 결과는 OnChannels() 콜백으로 수신
频道订阅 解除
UChatManager::GetInstance()->Unsubscribe(TEXT("global"));
玩家 在线 状态 查询
TArray<FString> UserIds = {TEXT("user_001"), TEXT("user_002"), TEXT("user_003")};
UChatManager::GetInstance()->GetPlayersOnline(UserIds);
// 결과는 OnPlayerOnline() 콜백으로 수신
连接 状态 确认
if (UChatManager::GetInstance()->IsConnected())
{
UE_LOG(LogTemp, Log, TEXT("채팅 서버에 연결되어 있습니다."));
}
全部 使用示例
// ChatExample.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "ChatManager.h"
#include "ChatModels.h"
#include "ChatExample.generated.h"
UCLASS()
class YOURPROJECT_API AChatExample : public AActor, public IChatListener
{
GENERATED_BODY()
public:
virtual void BeginPlay() override
{
Super::BeginPlay();
// 1. 채팅 서버 연결
UChatManager::GetInstance()->Connect(this);
}
// 2. 연결 성공 시 채널 구독
virtual void OnConnected() override
{
UE_LOG(LogTemp, Log, TEXT("연결 성공!"));
UChatManager::GetInstance()->Subscribe(TEXT("global"));
}
// 3. 채널 입장 알림
virtual void OnSubscribed(const FChatUserInfo& User) override
{
UE_LOG(LogTemp, Log, TEXT("%s님이 입장했습니다."), *User.VisitorName);
}
// 4. 메시지 수신
virtual void OnPublicMessage(const FChatUserInfo& Sender, const FString& Message) override
{
UE_LOG(LogTemp, Log, TEXT("[%s]: %s"), *Sender.VisitorName, *Message);
}
// 5. 메시지 전송 (UI 버튼에서 호출)
UFUNCTION(BlueprintCallable, Category = "Chat")
void OnSendButtonClick()
{
UChatManager::GetInstance()->SendPublicMessage(TEXT("global"), TEXT("안녕하세요!"));
}
// 기타 콜백 구현
virtual void OnDisconnected() override { UE_LOG(LogTemp, Log, TEXT("연결 해제됨")); }
virtual void OnError(const FString& Code, const FString& Message) override
{
UE_LOG(LogTemp, Error, TEXT("[%s] %s"), *Code, *Message);
}
virtual void OnChannels(const TArray<FChatChannelInfo>& Channels) override {}
virtual void OnUnSubscribed(const FChatUserInfo& User) override
{
UE_LOG(LogTemp, Log, TEXT("%s님이 퇴장했습니다."), *User.VisitorName);
}
virtual void OnPrivateMessage(const FChatUserInfo& Sender, const FString& Message) override
{
UE_LOG(LogTemp, Log, TEXT("[귓속말] %s"), *Message);
}
virtual void OnNotifyMessage(const FChatUserInfo& Sender, const FString& Message) override
{
UE_LOG(LogTemp, Log, TEXT("[알림] %s"), *Message);
}
virtual void OnPlayerOnline(const TArray<FChatPlayerInfo>& Players) override {}
};
注意事项
在Tick中调用Service: ChatManager的Tick()中调用ChatClient.Service(),在主线程中处理WebSocket回调。直接修改ChatManager时需要保持此部分。
单例模式: ChatManager以单例方式实现,通过
UChatManager::GetInstance()访问。关卡切换时也会保持。断开连接: 应用退出时,ChatManager的析构函数会自动断开连接。