[TIL_250325] UE5로 숫자 야구 게임 만들기🔄️

2025. 3. 25. 21:08·Dev./UE 언리얼 엔진

💭회고

일단 복습부터...

2025.03.26 - [Dev./UE 언리얼 엔진] - 🎮언리얼 엔진 주요 클래스 가이드(복습)

 

🎮언리얼 엔진 주요 클래스 가이드(복습)

주요 클래스별 역할과 기능✅ GameMode (게임 모드)핵심 기능:게임 규칙과 흐름의 총괄 관리자게임 시작 시 단 한 번 생성되어 레벨 내 규칙, 승패 조건, 점수 계산, 플레이어 스폰 등을 관리예시:FPS

raindrovvv.tistory.com

멀티플레이 게임 시스템 만들기 정말 어렵다...
아직 이해가 안가는 부분이 많아 좀 길게 두고 공부를 해야할듯 싶다.

📒학습 내용

1. Game Rule

 

숫자야구

간단하게 내기삼아 할 수 있는 게임이며 연필 및 종이 게임이다. 원제는 Bulls and Cows 이다. Bulls

namu.wiki

1. 각 플레이어에게 3번의 기회가 있습니다.
2. “/124”과 같이 “/” 가 있으면 응답 패턴으로 간주합니다.
3. 아웃조건
    1. “/”다음에는 3자리의 중복이 없는 1~9까지의 숫자가 중복있음.
    2. “/”다음에 3자리가 아님
4. 무승부조건
    : 플레이어 2명 모두 3번의 기회를 소모하고도 3S(trike)를 하지 못했을 때
5. 승리조건
    1. 먼저 3번의 기회 안에 3S(trike)를 한 플레이어 승
    2. 다른 플레이어가 아웃된 경우 남은 플레이어 승
6. 승리 또는 무승부의 경우 게임이 리셋

2. 전체적인 구조

<레벨 시작 → UI 표시 → Host/Guest 구분 → 숫자 입력 → 서버(GameMode) 전달 → 판정 후 UI에 결과> 반영
전반적인 구조는 “Listen Server(호스트가 서버 역할을 하면서 클라이언트도 플레이)” 환경에서 사용하는 방식이다.
  1. 서버-클라이언트 통신
    • “GameMode에서 GotMessageFromClient 이벤트 실행 → 판정 → 결과를 다시 UI로 전송” 하는 흐름
    • 게임에서 서버 전용 로직(판정, 승패 처리)을 GameMode가 맡고, 클라이언트는 결과 UI 업데이트를 하는 방식은 정석적인 구조다.
  2. Game Flow
    • 게임 시작
      • ✅ 게임이 시작되면 GameMode가 랜덤 숫자를 만든다. (예: “386”)
        • ✅ 랜덤 숫자는 생성은 블루프린트 라이브러리를 통해 만들어준다 ➡️ GenerateRandomNuber
      • Host와 Guest가 각각 3번씩 기회를 갖는다. (Count = 3)
        • TurnIndex(또는 CurrentTurn)를 1로 설정해 시작한다. (홀수=Host 차례, 짝수=Guest 차례)
    • UI에서 숫자 입력
      • ✅ 플레이어가 UI에서 “/123” 같은 형식으로 숫자를 입력하고 Enter를 누른다.
      • ✅ 이 입력은 PlayerController를 거쳐 GameMode의 함수(서버 RPC)로 전달된다.
    • 서버에서 판정
      • GameMode는 현재 차례(Host인지 Guest인지)와 입력을 확인한다.
      • 만약 차례가 맞지 않으면(Host 차례인데 Guest가 입력 등) 무시하거나 에러 메시지를 보낸다.
      • ✅ 입력이 유효하다면 스트라이크/볼을 계산한다. (블루프린트 라이브러리 : IsValidInput, CaculateResult)
        • 3S0B → 즉시 승리
        • 0S0B → “OUT” 처리
        • 그 외(예: “1S2B”) → 그냥 결과만 반환
      • 해당 플레이어의 시도 횟수를 1 감소시킨다.
    • 승패 및 무승부 판정
      • 만약 3S0B라면 → 해당 플레이어 승리 → UI에 “Win!” 표시 후 게임 리셋
      • 만약 시도 횟수가 모두 소진되면(Host=0, Guest=0) → 무승부 → “Tie!” 표시 후 게임 리셋
      • 그 외에는 다음 턴으로 넘어가 TurnIndex++ (타이머 리셋 등)
    • UI 업데이트
      • GameMode가 결과(예: “1S2B”, “OUT”, “Win”, “Tie”)를 PlayerController에 알려준다. (Owning Client RPC)
      • PlayerController는 받은 정보를 UI 위젯에 표시한다. (SetResultText, SetWinText 등)
    • 게임 리셋
      • 승리나 무승부가 나오면, GameMode가 새로운 숫자를 생성하고(“749”), HostCount와 GuestCount를 다시 3으로 초기화한다.
      • UI도 초기화하고 다음 라운드를 시작한다.

2. 구현 가이드(📗Read.me)

2.1. Blueprint Library : 랜덤숫자 생성, 입력 검증, 결과 판정 ➡️ UBaseballLibrary 

더보기

BaseballLibrary.h

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "Kismet/BlueprintFunctionLibrary.h"
#include "BaseballLibrary.generated.h"

/**
 * 
 */
UCLASS()
class SAMPLECHAT_API UBaseballLibrary : public UBlueprintFunctionLibrary
{
	GENERATED_BODY()

public:
    // 중복 없는 3자리 랜덤 숫자 생성 (1-9)
    UFUNCTION(BlueprintCallable, Category = "Baseball Game")
    static FString GenerateRandomNumber();

    // 입력에서 숫자만 추출하여 정제된 문자열 반환
    UFUNCTION(BlueprintCallable, Category = "Baseball Game")
    static void CleanNumericInput(const FString& Input, FString& OutCleanedInput);

    // 유효한 입력인지 확인
    UFUNCTION(BlueprintCallable, Category = "Baseball Game")
    static bool IsValidInput(const FString& CleanedInput);

    // 스트라이크와 볼 계산
    UFUNCTION(BlueprintCallable, Category = "Baseball Game")
    static FString CalculateResult(const FString& Target, const FString& CleanedInput);
};

BaseballLibrary.cpp

#include "BaseballLibrary.h"

// 무작위 야구 숫자를 생성하는 라이브러리
FString UBaseballLibrary::GenerateRandomNumber()
{
    // 1부터 9까지 숫자를 담을 배열
    TArray<int32> Numbers;
    for (int32 i = 1; i <= 9; i++)
    {
        Numbers.Add(i);
    }

    // 배열을 무작위로 섞어 3개의 자리수를 만들 준비
    for (int32 i = 0; i < Numbers.Num(); i++)
    {
        int32 RandomIndex = FMath::RandRange(i, Numbers.Num() - 1);
        if (i != RandomIndex)
        {
            Numbers.Swap(i, RandomIndex); // 현재 인덱스와 무작위 인덱스를 교환
        }
    }

    // 섞인 배열에서 앞의 3자리만 문자열로 생성
    FString Result;
    for (int32 i = 0; i < 3; i++)
    {
        Result.AppendInt(Numbers[i]); // 숫자를 문자열로 추가
    }

    return Result;
}

// 입력에서 숫자만 추출하여 정제된 문자열 반환
void UBaseballLibrary::CleanNumericInput(const FString& Input, FString& OutCleanedInput)
{
    OutCleanedInput.Empty();
    for (int32 i = 0; i < Input.Len(); i++)
    {
        if (Input[i] >= '0' && Input[i] <= '9')
        {
            OutCleanedInput.AppendChar(Input[i]);
        }
    }
    UE_LOG(LogTemp, Warning, TEXT("⚡CleanNumericInput: 정제된 입력값: %s"), *OutCleanedInput);
}

// 사용자가 입력한 문자열이 조건(길이, 범위, 중복)에 맞는지 검증
bool UBaseballLibrary::IsValidInput(const FString& CleanedInput)
{
    if (CleanedInput.Len() != 3)
    {
        UE_LOG(LogTemp, Warning, TEXT("🔴IsValidInput: 길이가 3이 아님 (길이: %d)"), CleanedInput.Len());
        return false;
    }

    for (int32 i = 0; i < 3; i++)
    {
        if (CleanedInput[i] < '1' || CleanedInput[i] > '9')
        {
            UE_LOG(LogTemp, Warning, TEXT("🔴IsValidInput: %d번째 문자 범위 오류 (값: %c, ASCII: %d)"),
                i, CleanedInput[i], (int32)CleanedInput[i]);
            return false;
        }
    }

    for (int32 i = 0; i < 3; i++)
    {
        for (int32 j = i + 1; j < 3; j++)
        {
            if (CleanedInput[i] == CleanedInput[j])
            {
                UE_LOG(LogTemp, Warning, TEXT("🔴IsValidInput: 중복 발견"));
                return false;
            }
        }
    }

    UE_LOG(LogTemp, Log, TEXT("IsValidInput: 유효한 입력 확인: %s"), *CleanedInput);
    return true;
}

// 스트라이크와 볼을 계산해 반환
FString UBaseballLibrary::CalculateResult(const FString& Target, const FString& CleanedInput)
{
    int32 Strikes = 0;
    int32 Balls = 0;

    UE_LOG(LogTemp, Warning, TEXT("➡️CalculateResult: 타겟: %s, 입력: %s"), *Target, *CleanedInput);

    for (int32 i = 0; i < 3; i++)
    {
        if (CleanedInput[i] == Target[i])
        {
            Strikes++;
            UE_LOG(LogTemp, Warning, TEXT("➡️CalculateResult: %d번째 자리 스트라이크 (숫자: %c)"), i, CleanedInput[i]);
        }
        else
        {
            for (int32 j = 0; j < 3; j++)
            {
                if (CleanedInput[i] == Target[j] && i != j)
                {
                    Balls++;
                    UE_LOG(LogTemp, Warning, TEXT("➡️CalculateResult: %d번째 자리 볼 (숫자: %c, 타겟 위치: %d)"), i, CleanedInput[i], j);
                }
            }
        }
    }

    // 스트라이크와 볼이 모두 0이면 OUT
    if (Strikes == 0 && Balls == 0)
    {
        return TEXT("OUT");
    }

    // 그렇지 않으면 "xS yB" 형태의 문자열 반환
    return FString::Printf(TEXT("%dS%dB"), Strikes, Balls);
}

2.2.  레벨 블루프린트

LvChat

  1. 시작할 때, WidgetChatWindow가 ViewPort에 할당되어 보여진다.
  2. IsServer가 맞는지 아닌지 체크한다.
    • 1) 서버일 경우, BP_Controller가 확인되면 OnLoginWithID 이벤트 실행 ➡️ ‘Host’라는 ID를 PlayerController에게 전달한다.
    • 2) 서버가 아닐 경우, BP_Controller가 확인되면 OnLoginWithID 이벤트 실행 ➡️ ‘Guest’라는 ID를 PlayerController에게 전달한다.

2.3.  BP_Controller (PlayerController)

BP_Controller
OnSendMsgToServer (서버에서 실행되는 커스텀 이벤트)

1. Remote Role이 None이 아니라면 모든 Widget Class를 가져와서 Widget_ChatWindow Class로 변환한다.
2. 변환이 성공적이라면,

  • Widget_ChatWindow에서 이벤트(OnSendMsgToServer)가 발생하면 Set Message to User Controller가 실행되도록 이벤트를 바인딩한다.
    • 메시지가 서버에 보내지면(채팅창 위젯에서 사용자가 메시지를 보내면), OnSendMsgToServer 이벤트 발생.
    • OnSendMsgToServer( 서버에서 실행되는 커스텀 이벤트 )가 Set Message to User Controller에 바인딩 되어 있으므로, 이벤트 발생 시 Set Message to User Controller가 실행된다!
      • OnSendMsgToServer 이벤트는 게임모드를 가져와서(Get GameMode) BP_ChatMode로 캐스팅(Cast To BP_ChatMode)한다.
      • 그리고 BP_ChatMode 내에 있는 Got Message from Client 함수를 호출한다.
      • 이 함수는 "RELIABLE" 속성을 가지며, 서버로 복제되는 것(Run On Server)을 알 수 있다.
⚡Remote Role
➡️ Remote Role이 None이 아니라면 : 멀티플레이에서 서버/클라이언트 간 네트워크 동기화가 이루어지고 있다는 의미. 즉, 리슨 서버라는 거다. 리슨 서버는 Remote Role === None 체크 해야됨!

➡️ 온라인 게임을 친구들과 같이 한다고 생각해보자. 그 중 한 명이 호스트가 되어서 게임을 열고, 다른 친구들이 그 게임에 접속해서 같이 플레이하는 경우가 있는데, 이걸 Listen Server라고 한다. 이 때 호스트로 게임을 연 사람은 자기 컴퓨터에서 게임을 실행하면서 동시에 다른 친구들의 접속을 관리하는 역할도 해야 한다.

- Local Role (내 역할): 호스트 컴퓨터에서 실행되는 게임은 호스트 플레이어(자기 자신)를 직접 조작하고, 게임의 기본적인 규칙과 로직을 처리한다.
- Remote Role (다른 사람 역할): 호스트 컴퓨터에서 실행되는 게임은 다른 친구들이 접속했을 때, 그 친구들의 캐릭터 움직임이나 행동을 받아서 게임에 반영해준다.

그래서 Remote Role은 내 컴퓨터가 다른 컴퓨터와 연결되어서 그 연결된 컴퓨터에 있는 플레이어의 역할을 대신 처리해 주는 것을 말한다.

➕ 액터들은 네트워크 게임에서 어떤 역할을 하는지에 따라 종류가 나뉜다.

1. None (없음): 해당 액터가 지금 이 컴퓨터에는 존재하지 않는다는 뜻. 그냥 없는 거다.
2. SimulatedProxy (모의 프록시): Authority 역할을 가진 액터의 복사본인데, 주로 다른 플레이어의 캐릭터나 움직이는 물건들을 표현할 때 쓰인다. 이 역할은 그냥 서버에서 보내주는 정보대로 화면에 보여주기만 하고, 스스로 어떤 게임 로직을 실행하지는 않는다. 마치 다른 사람이 조종하는 인형을 내가 보기만 하는 것과 같다.
- AutonomousProxy (자율 프록시): Authority 역할을 가진 액터의 복사본인데, 특히 내 캐릭터를 표현할 때 많이 쓰인다. 내가 조작하는 대로 움직이는 것처럼 보이지만, 실제로는 서버에 있는 Authority에게 내 행동을 알려주고 확인받는 과정을 거친다. 마치 내가 조종하는 로봇인데, 진짜 로봇은 멀리 있는 사람이 관리하는 것과 같다.
- Authority (권한자): 게임의 규칙을 정하고, 중요한 결정을 내리는 액터. 주로 게임 서버에 있는 액터들이 이런 역할을 맡아서 게임 로직을 실행한다. 마치 심판과 같다.

2.4.  BP_ChatMode (GameMode)

BeginPlay에서 게임에 필요한 값을 초기화 한다 ➡️ 난수 생성, (turn 과 count 초기화, 타이머 초기화)

  • turn, count, 타이머 부분은 미구현 상태이다.

전체 모습, GotMessageFromClient ➡️ Run On Server
InitGame 함수 ➡️ 초기화(테스트용)
IsValidInput
CaculateResult, 승패 판 부분 미구현

 


2.5.  Widget_ChatWindow (UI)

Widget_ChatWindow

1) 입력창에 '/'와 숫자를 입력하고 enter(커밋 : OnTextCommitted + SwitchOnETextCommit(OnEnter))를 누르게 되면, UI의 SetMessagetoUserController가 call(호출)되는데 이 이벤트 디스패처는 PlayerController에 OnSendMsgtoServer와 바인딩 되어있다 ⬇️ : 이렇게 구현함으로써 입력창에 메시지를 써서 엔터를 치면 서버로 메시지가 보내지는 것이다.

바인딩 되어 있는 SetMessagetoUserController

 

2) Rule에서 "/"가 있어야 응답 패턴으로 간주하므로... 채팅을 입력할 때:
a. `Left` 노드를 통해 첫 글자를 가져온다 
b.  `/` 와 동일하다면?

  • True : CallSetMessage 이벤트 실행.
  • False : 올바른 입력으로 간주하지 않고, 오류 메시지 실행.

c. `/`을 제외한 오른쪽 텍스트를 반환해줘야 하기 때문에 `RightChop` 노드 사용.


🟣오늘의 옵시디언 현황

'Dev. > UE 언리얼 엔진' 카테고리의 다른 글

[UE/Tip] 유용한 팁 : ⚙️언리얼 엔진 플러그인 만들기 +  (0) 2025.03.27
🎮언리얼 엔진 주요 클래스 가이드(복습)  (0) 2025.03.26
[TIL_250306] 언리얼 엔진 시네마틱(엔딩 씬)  (0) 2025.03.06
[TIL_250305] 언리얼 엔진 자막 달기  (0) 2025.03.05
[TIL_250304] 언리얼 엔진 미니맵 제작 과정 가이드  (0) 2025.03.04
'Dev./UE 언리얼 엔진' 카테고리의 다른 글
  • [UE/Tip] 유용한 팁 : ⚙️언리얼 엔진 플러그인 만들기 +
  • 🎮언리얼 엔진 주요 클래스 가이드(복습)
  • [TIL_250306] 언리얼 엔진 시네마틱(엔딩 씬)
  • [TIL_250305] 언리얼 엔진 자막 달기
raindrovvv
raindrovvv
raindrovvv 님의 블로그 입니다.
  • raindrovvv
    raindrovvv 님의 블로그
    raindrovvv
  • 전체
    오늘
    어제
    • 분류 전체보기 (89) N
      • Dev. (82) N
        • UE 언리얼 엔진 (45) N
        • Unity 유니티 (0)
        • Wwise 와이즈 (3)
        • 게임 네트워크 (8)
        • 그래픽스 Graphics (18)
        • 프로젝트 (4)
        • 기타 개발 관련 (4)
      • Computer Science (0)
        • 하드웨어 HW (0)
        • 소프트웨어 SW (0)
        • 통신 (0)
        • 데이터 (0)
      • 블로그 (3)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    Git
    네트워크
    언리얼
    프로젝트
    게임
    오디오미들웨어
    TA
    셰이더
    언리얼엔진
    포스트프로세스
    고라니
    UE
    그래픽스
    머티리얼
    깃
    게임네트워크
    Unreal
    unrealengine
    AI
    게임개발
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
raindrovvv
[TIL_250325] UE5로 숫자 야구 게임 만들기🔄️
상단으로

티스토리툴바