본문 바로가기
언리얼(Unreal)/네트워크

25.04.08 채팅시스템 구현 (상)

by alwaysyoung2 2025. 4. 8.
728x90
반응형

1ㅂ프로젝트 개요

이 가이드에서는 언리얼 엔진 5.5.4 버전을 사용해 간단하지만 완벽하게 동작하는 채팅 시스템을 구현합니다. C++로 핵심 로직을 작성하고 블루프린트로 연결하는 방법을 단계별로 설명드리겠습니다.

 

그전에

서버 모델과 동작 원리를 알아봐야합니다.

 

서버 유형 비교표

서버 유형설명장점단점대표 게임

P2P (Peer-to-Peer) 모든 클라이언트가 서버 역할 동시 수행 서버 유지비용 없음, 빠른 통신 보안 취약, 호스트 나가면 게임 종료 다크소울, 토렌트
Listen Server 한 클라이언트가 호스트 역할 수행 간편한 설정, 추가 인프라 불필요 호스트 성능에 전체 게임 영향 마인크래프트, 어몽어스
Dedicated Server 전용 서버 프로그램 운영 안정적 성능, 공정한 게임 환경 별도 서버 운영 비용 발생 배틀그라운드, 오버워치

데디케이티드 서버 작동 메커니즘

1. 서버 실행 과정

mermaid
Copy
sequenceDiagram
    participant Dev as 개발자
    participant Client as 클라이언트
    participant Server as 데디케이티드 서버
    
    Dev->>Server: "Run as Client" 선택 또는 server.exe 실행
    Server->>Server: Open Level 명령 실행
    Server->>Server: Listen 모드 활성화 (포트 개방)
    Server->>Server: GameMode 액터 생성 (단일 인스턴스)
    Client->>Server: "Open [서버IP]" 명령 전송
    Server->>Client: 레벨 정보 전달
    Client->>Client: 동일 레벨 로드
    Server->>Server: 전용 PlayerController 생성
    Server->>Client: PC 복제 (네트워크 복제)
    Server->>Server: Pawn 생성 및 복제

2. 클라이언트 연결 아키텍처

  1. 서버 측:
    • 유일한 GameMode 인스턴스 보유
    • 모든 클라이언트의 PlayerController 보유
    • 모든 Pawn의 원본 관리
  2. 클라이언트 측:
    • 자신의 PlayerController만 보유 (로컬 0번)
    • 다른 클라이언트의 Pawn은 복제본으로 보유
    • 직접적인 클라이언트 간 통신 불가

3. 핵심 특징 설명

GameMode의 특수성:

  • 물리적으로 서버에만 존재하는 싱글톤 액터
  • 게임 규칙, 점수, 상태 등 핵심 로직 처리
  • GetGameMode() 호출 시 클라이언트에서는 nullptr 반환

PlayerController 복제 원리:

cpp
Copy
// 서버에서의 생성 예시
APlayerController* NewPC = SpawnPlayerController(ROLE_Authority);
if(NewPC)
{
    NewPC->NetConnection = ClientConnection; // 네트워크 연결 매핑
    ClientConnection->PlayerController = NewPC;
}

네트워크 트래픽 최적화:

  • 관심 영역(Relevancy) 시스템: 주변 액터만 복제
  • 네트워크 우선순위: 중요한 액터부터 전송
  • 압축 기술: 위치, 회전 값 압축 전송

세션 서비스 작동 방식

mermaid
Copy
flowchart TB
    subgraph 세션서비스[세션 서비스]
        A[방 생성 요청] --> B[서버 IP 등록]
        C[방 검색 요청] --> D[IP 주소 반환]
    end
    
    subgraph 게임서버[게임 서버]
        E[레벨 로드] --> F[Listen 모드 활성화]
    end
    
    subgraph 클라이언트
        G[세션 조회] --> H[IP 획득]
        H --> I["Open [IP] 명령"]
    end
    
    B -.-> 세션서비스
    D -.-> 클라이언트
    I --> 게임서버

개발자 핵심 체크리스트

  1. 네트워크 변수 설정:
  2. cpp
    Copy
    UPROPERTY(Replicated)
    int32 Health; // 서버에서만 수정 가능
    
    UPROPERTY(ReplicatedUsing=OnRep_Ammo)
    int32 Ammo; // 변경 시 클라이언트 콜백
    
    UFUNCTION(Client)
    void ClientShowMessage(FString Text); // RPC 예시
  3. 중요 디버깅 명령어:
    • NetStats: 네트워크 트래픽 모니터링
    • DebugReplication: 복제 현황 시각화
    • NetDormancy: 휴면 액터 확인
  4. 최적화 기법:
    • 자주 변경되는 변수는 ReplicatedUsing 사용
    • 중요도 낮은 업데이트는 NetUpdateFrequency 조정
    • 이동 액터는 AActor::SetReplicateMovement(true)

실제 구현 사례 분석

채팅 시스템 확장 예시:

cpp
Copy
// GameMode에서의 브로드캐스트 구현
void AChatGameMode::BroadcastMessage(const FString& Sender, const FString& Text)
{
    for(FConstPlayerControllerIterator It = GetWorld()->GetPlayerControllerIterator(); It; ++It)
    {
        AChatPlayerController* PC = Cast<AChatPlayerController>(*It);
        if(PC)
        {
            PC->ClientReceiveMessage(Sender, Text); // 각 클라이언트에 RPC 전송
        }
    }
}

이 아키텍처 분석을 통해 언리얼 네트워킹의 핵심 원리를 이해하시고, 더 안정적인 멀티플레이어 게임 개발에 적용하실 수 있을 것입니다. 각 모델의 특징을 고려해 프로젝트 요구사항에 가장 적합한 네트워크 방식을 선택하시기 바랍니다.

 

이제 채팅시스템을 하나 만들어보겠습니다.

 

핵심 컴포넌트 구조

mermaid
Copy
graph TD
    A[GameModeBase] --> B[PlayerController]
    B --> C[ChatInput Widget]
    C --> D[EditableTextBox]
    B --> E[Message Display]

1. 게임 프레임워크 설정

CXGameModeBase

게임의 기본 규칙을 정의하는 클래스로, 현재는 특별한 기능 없이 기본 틀만 구성됩니다.

cpp
Copy
// Game/CXGameModeBase.h
UCLASS()
class CHATX_API ACXGameModeBase : public AGameModeBase
{
    GENERATED_BODY()
};

CXPlayerController

플레이어 입력과 UI를 관리하는 핵심 클래스입니다.

cpp
Copy
// Player/CXPlayerController.h
UCLASS()
class CHATX_API ACXPlayerController : public APlayerController
{
    GENERATED_BODY()
    
public:
    virtual void BeginPlay() override;
    
    // 채팅 관련 기능
    void SetChatMessageString(const FString& InChatMessageString);
    void PrintChatMessageString(const FString& InChatMessageString);

protected:
    UPROPERTY(EditDefaultsOnly)
    TSubclassOf<UCXChatInput> ChatInputWidgetClass;
    
    UPROPERTY()
    TObjectPtr<UCXChatInput> ChatInputWidgetInstance;
};

2. 채팅 UI 구현

CXChatInput 위젯

채팅 입력을 처리하는 사용자 정의 위젯입니다.

cpp
Copy
// UI/CXChatInput.h
UCLASS()
class CHATX_API UCXChatInput : public UUserWidget
{
    GENERATED_BODY()

public:
    UPROPERTY(meta = (BindWidget))
    TObjectPtr<UEditableTextBox> EditableTextBox_ChatInput;

protected:
    UFUNCTION()
    void OnChatInputTextCommitted(const FText& Text, ETextCommit::Type CommitMethod);
};

위젯 이벤트 처리

cpp
Copy
// UI/CXChatInput.cpp
void UCXChatInput::OnChatInputTextCommitted(const FText& Text, ETextCommit::Type CommitMethod)
{
    if (CommitMethod == ETextCommit::OnEnter && GetOwningPlayer())
    {
        if (ACXPlayerController* PC = Cast<ACXPlayerController>(GetOwningPlayer()))
        {
            PC->SetChatMessageString(Text.ToString());
            EditableTextBox_ChatInput->SetText(FText());
        }
    }
}

3. 시스템 연결

PlayerController 구현

cpp
Copy
// Player/CXPlayerController.cpp
void ACXPlayerController::BeginPlay()
{
    Super::BeginPlay();
    
    // UI 전용 입력 모드 설정
    SetInputMode(FInputModeUIOnly());

    // 채팅 위젯 생성 및 추가
    ChatInputWidgetInstance = CreateWidget<UCXChatInput>(this, ChatInputWidgetClass);
    ChatInputWidgetInstance->AddToViewport();
}

void ACXPlayerController::PrintChatMessageString(const FString& InChatMessageString)
{
    // 화면 하단에 빨간색으로 메시지 출력
    UKismetSystemLibrary::PrintString(this, InChatMessageString, 
        true, true, FLinearColor::Red, 5.0f, TEXT("None"));
}

4. 블루프린트 설정

  1. 블루프린트 클래스 생성:
    • BP_GameModeBase (CXGameModeBase 상속)
    • BP_PlayerController (CXPlayerController 상속)
    • WBP_ChatInput (CXChatInput 상속)
  2. 에디터 설정:
    • World Settings → GameMode Override: BP_GameModeBase 선택
    • BP_GameModeBase → Player Controller Class: BP_PlayerController 지정
    • BP_PlayerController → Chat Input Widget Class: WBP_ChatInput 지정

5. UI 디자인 (WBP_ChatInput)

  1. 계층 구조:
    • Canvas Panel (루트)
      • EditableTextBox (이름: EditableTextBox_ChatInput)
  2. 디자인 설정:
    • 앵커(Anchor): 하단 가운데 고정
    • 위치/크기: 화면 하단에 적절히 배치
    • Placeholder Text: "메시지를 입력하세요..."

시스템 동작 흐름

mermaid
Copy
sequenceDiagram
    participant Player
    participant ChatInput
    participant PlayerController
    participant GameMode
    
    Player->>ChatInput: 메시지 입력 후 엔터
    ChatInput->>PlayerController: SetChatMessageString()
    PlayerController->>PlayerController: PrintChatMessageString()
    PlayerController->>ChatInput: 입력창 초기화

 

 

이제 다음과 같은 설정으로 플레이해주고 나면 에러메세지가 밑과 같이 뜨게됩니다

  • 기존 로직의 문제점
  • 굳이 내 UI를 다른 친구가 볼 필요는 없음. 위젯 개체는 Owning Client에서만 생성되면 됨. Owning Client를 판별하기 위해서 IsLocalController() 함수를 호출. IsLocalController() 함수에 대해서는 뒤에서 배움.
  • 왜 채팅을 쳤는데 다른 클라에서도 보일까? 분명 우리는 아직 채팅 메세지의 멀티플레이 설정을 하나도 건들지 않음. 이는 Run Under One Process 속성때문. 하나의 언리얼 엔진 프로세스 위에서 모든 로직이 돌다보니, PrintString()도 모든 화면에서 뜸. 좀 더 실제적인 멀티플레이 환경을 실습해보려면, Run Under One Process 속성을 false 지정.
  • 데디케이티드 서버 환경 설정 Toolbar > Edit > EditorPreferences에서 아래 옵션들을 체크.
  1. Allow Late Joining 체크 하면 Add another cliner 버튼으로 클라이언트 추가 가능.
  2. Always on top 체크하면 새 클라이언트 화면이 항상 위에 뜸.
  3. Multiplayer Options에서 추가적인 설정 가능. Launch Seperate Server: 체크시 서버 따로 띄워줌. 안하면 에디터 화면이 클라이자 서버 Run Under One Process: 체크하면 같은 프로세스 내라서 공유하는 애셋이 생김. 따라서 확실한 멀티플레이 환경이라고 보기엔 무리. 다만 성능상의 잇점이 있음. 체크하지 않으면 서버와 각 클라들이 다른 프로세스로 분기됨. Net Mode는 Play as Client로 설정.
  4. 플레이 버튼 우측에 점 세개 기호 클릭 > Number of Players에 2를 설정.

언리얼 엔진 네트워킹 심층 분석: NetMode, NetConnection, NetDriver

NetMode의 핵심 역할과 중요성

NetMode 종류 및 특징

NetMode 값설명사용 시나리오

NM_Standalone 단일 플레이어 모드 오프라인 게임
NM_ListenServer 호스트 클라이언트 마인크래프트 같은 P2P 게임
NM_DedicatedServer 전용 서버 대규모 온라인 게임
NM_Client 일반 클라이언트 모든 온라인 게임 참가자

보안 사례: 데미지 처리 시스템

cpp
Copy
void APlayerCharacter::TakeDamage(float Amount)
{
    if(GetNetMode() != NM_Client) // 반드시 서버에서만 실행
    {
        Health -= Amount;
        if(Health <= 0) Die();
    }
}
  • 클라이언트 측 처리 시 발생하는 문제: 메모리 해킹으로 데미지 값 조작 가능
  • 서버 측 처리 장점: 핵심 계산을 서버에서 수행 → 클라이언트는 결과만 표시

네트워크 시스템 아키텍처

1. NetDriver 작동 원리

mermaid
Copy
graph TD
    UWorld -->|생성| UNetDriver
    UNetDriver -->|관리| UNetConnection
    UNetDriver -->|구분| ServerConnection
    UNetDriver -->|관리| ClientConnections[ClientConnections 배열]

2. 핵심 컴포넌트 상호작용

cpp
Copy
// UNetDriver.h
class UNetDriver : public UObject {
    UNetConnection* ServerConnection;  // 클라이언트 전용
    TArray<UNetConnection*> ClientConnections;  // 서버 전용
};

3. NetMode 결정 로직

cpp
Copy
ENetMode UNetDriver::GetNetMode() const
{
    if(IsServer()) {
        return GIsClient ? NM_ListenServer : NM_DedicatedServer;
    }
    return NM_Client;
}

개발자용 실용 가이드

디버깅 유틸리티 구현

cpp
Copy
// ChatXFunctionLibrary.h
static FString GetNetModeString(const AActor* Actor)
{
    switch(Actor->GetNetMode()) {
        case NM_Client: return TEXT("Client");
        case NM_ListenServer: return TEXT("ListenServer");
        case NM_DedicatedServer: return TEXT("DedicatedServer");
        default: return TEXT("Standalone");
    }
}

네트워크 로깅 모범 사례

cpp
Copy
void AMyCharacter::SomeNetworkFunction()
{
    FString NetRoleStr = GetNetRole() == ROLE_Authority ? 
        TEXT("Authority") : TEXT("Simulated");
    
    UE_LOG(LogNet, Warning, TEXT("[%s] %s - %s"), 
        *GetNetModeString(this),
        *GetName(),
        *NetRoleStr);
}

성능 최적화 기법

  1. 네트워크 트래픽 절감:
  2. cpp
    Copy
    UPROPERTY(Replicated, NetPriority=2.0)
    float CriticalValue; // 중요도 높은 변수
  3. 주기적 업데이트 조정:
  4. cpp
    Copy
    AActor::SetNetUpdateFrequency(10); // 초당 10회 업데이트
  5. 관심 영역 최적화:
  6. cpp
    Copy
    void AMyActor::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
    {
        DOREPLIFETIME_CONDITION(AMyActor, SomeVar, COND_OwnerOnly);
    }

 

728x90
반응형