본문으로 건너뛰기

채팅 시스템 개요

PlayNANOO 채팅 시스템은 실시간 WebSocket 기반의 채팅 기능을 제공합니다. 이 문서에서는 채팅 시스템의 전체 구조와 사용 방법을 설명합니다.

시스템 구성

채팅 시스템은 3개의 핵심 클래스로 구성됩니다.

클래스역할
ChatManager싱글톤 매니저로, 채팅 서버 연결 및 API 호출을 관리합니다
ChatClientWebSocket 연결 및 메시지 송수신을 처리하는 코어 클래스입니다
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 {}
};

주의사항

  1. Tick에서 Service 호출: ChatManager의 Tick()에서 ChatClient.Service()가 호출되어 WebSocket 콜백을 메인 스레드에서 처리합니다. ChatManager를 직접 수정하는 경우 이 부분을 유지해야 합니다.

  2. 싱글톤 패턴: ChatManager는 싱글톤으로 구현되어 있어 UChatManager::GetInstance()로 접근합니다. 레벨 전환 시에도 유지됩니다.

  3. 연결 해제: 앱 종료 시 ChatManager의 소멸자에서 자동으로 연결이 해제됩니다.