1. CPU 스케줄링의 기초

정의 및 중요성

  • 운영체제가 여러 프로세스에게 공정하고 합리적으로 CPU 자원을 배분하는 과정이다.
  • 스케줄링이 비효율적이면 자원 배분 불균형, 처리량 감소, 무질서 상태가 발생할 수 있다.

프로세스 우선순위

  • 프로세스는 성격에 따라 입출력 집중 프로세스(I/O bound)와 CPU 집중 프로세스(CPU bound)로 나뉜다.
  • 시스템 효율을 높이기 위해 입출력 장치와 CPU를 동시에 활용할 수 있도록 입출력 집중 프로세스에 더 높은 우선순위를 부여한다.
  • 운영체제는 프로세스의 PCB(Process Control Block)에 기록된 우선순위를 바탕으로 스케줄링을 진행한다.

스케줄링 큐

  • 준비 큐(Ready Queue)는 CPU를 할당받기 위해 대기하는 프로세스들이 모인 곳이다.
  • 대기 큐(Waiting Queue)는 입출력 작업 등이 끝나기를 기다리는 프로세스들이 모인 곳이다.

선점형과 비선점형 스케줄링

  • 선점형(Preemptive)은 프로세스가 CPU를 사용 중이더라도 운영체제가 강제로 빼앗을 수 있는 방식이며, 문맥 교환 오버헤드가 크지만 반응성이 좋다.
  • 비선점형(Non-preemptive)은 프로세스가 스스로 CPU를 반납할 때까지 기다리는 방식이며, 오버헤드가 적지만 긴 작업이 CPU를 독점하면 무한정 기다리는 비효율이 발생한다.

2. 스케줄링 알고리즘

FCFS (선입 선처리)

  • 준비 큐에 먼저 도착한 순서대로 처리하는 비선점형 스케줄링이다.
  • 실행 시간이 긴 프로세스가 먼저 도착하면 뒤의 짧은 프로세스들이 무작정 기다려야 하는 호위 효과(Convoy Effect)가 발생한다.

SJF (최단 작업 우선)

  • CPU 이용 시간이 가장 짧은 프로세스부터 먼저 실행하는 방식이다.
  • 평균 대기 시간을 최소화할 수 있으나 현실에서는 프로세스의 실행 시간을 정확히 예측하기 어렵다는 한계가 있다.

RR (라운드 로빈)

  • 선입 선처리 방식에 타임 슬라이스(Time Slice)라는 시간제한 개념을 더한 선점형 방식이다.
  • 정해진 시간이 지나면 CPU를 빼앗기고 큐의 맨 뒤로 이동하며, 타임 슬라이스의 크기에 따라 성능이 크게 좌우된다.

SRT (최소 잔여 시간 우선)

  • 최단 작업 우선(SJF)과 라운드 로빈(RR)의 장점을 결합한 선점형 스케줄링이다.
  • 정해진 시간만큼 CPU를 사용한 후, 남아있는 작업 시간이 가장 짧은 프로세스에게 CPU를 넘겨준다.

우선순위 스케줄링

  • 부여된 우선순위가 가장 높은 프로세스부터 순서대로 실행한다.
  • 우선순위가 낮은 프로세스는 영원히 실행되지 못하는 기아(Starvation) 현상이 발생할 수 있다.
  • 이를 방지하기 위해 대기 시간에 비례해 우선순위를 높여주는 에이징(Aging) 기법을 활용한다.

다단계 큐 (Multilevel Queue)

  • 우선순위별로 여러 개의 큐를 두어 관리하며, 각 큐마다 서로 다른 스케줄링 알고리즘을 적용할 수 있다.
  • 단, 프로세스가 한 번 특정 큐에 배정되면 다른 큐로 이동할 수 없다는 한계가 있다.

다단계 피드백 큐 (Multilevel Feedback Queue)

  • 다단계 큐의 한계를 극복하기 위해 프로세스들이 큐 사이를 자유롭게 이동할 수 있도록 만든 방식이다.
  • CPU를 오래 사용하는 프로세스는 우선순위가 낮은 큐로 강등되고, 대기 시간이 길어지면 에이징 기법으로 우선순위를 다시 높인다.
  • 현대 운영체제에서 가장 보편적으로 사용하는 스케줄링 알고리즘이다.

'CS' 카테고리의 다른 글

CPU의 구조 및 클럭, 코어  (0) 2026.02.06
[CS] 컴파일이란?  (0) 2026.01.27

https://github.com/NMEii/Algorithms/tree/main/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4/1/140108.%E2%80%85%EB%AC%B8%EC%9E%90%EC%97%B4%E2%80%85%EB%82%98%EB%88%84%EA%B8%B0

 

Algorithms/프로그래머스/1/140108. 문자열 나누기 at main · NMEii/Algorithms

This is an auto push repository for Baekjoon Online Judge created with [BaekjoonHub](https://github.com/BaekjoonHub/BaekjoonHub). - NMEii/Algorithms

github.com

 

설계

첫번째 문자를 x로 선언하고 문자열을 순회 하면서 n번째 문자가 x와 같으면 sameCount++, 다르면 diffCount++ 을 진행하고 각 카운트의 크기가 같다면 문자열을 자르고 answer++ 진행 하고 그뒤를 진행한다.

 

구현

#include <string>
#include <vector>

using namespace std;

int solution(string s) {
    int answer = 0;

    vector<string> save;

    char x = s[0];
    int sameCount = 0;
    int diffCount = 0;

    for (int i = 0; i < s.length(); i++)
    {
        if (x == s[i])
        {
            sameCount++;
        }
        else
        {
            diffCount++;
        }
        
        if (sameCount == diffCount)
        {
            answer++;
            
            if(i+1 < s.length())
            {
                x = s[i+1];
            }
            
            sameCount = 0;
            diffCount = 0;
        }
    }

    if (sameCount != 0 || diffCount != 0)
    {
        answer++;
    }

    return answer;
}

 

리펙토링 코드

#include <string>

using namespace std;

int solution(string s) {
    int answer = 0; // 분해된 문자열의 총 개수를 저장할 변수

    // 기준 글자(x)와 각 횟수를 저장할 상태 변수들
    char x; 
    int sameCount = 0; // 기준 글자와 같은 글자가 나온 횟수
    int diffCount = 0; // 기준 글자와 다른 글자가 나온 횟수

    // [리팩토링 포인트 1] 범위 기반 for문 사용
    // 인덱스(i) 없이 문자열 s에서 글자를 하나씩 순서대로 꺼내 변수 c에 담습니다.
    for (char c : s) 
    {
        // [리팩토링 포인트 2] 새로운 덩어리의 시작점 잡기
        // 두 카운트가 모두 0이라는 것은 '새로운 문자열 조각을 읽기 시작했다'는 뜻입니다.
        // 따라서 이때 처음 꺼낸 글자 c를 새로운 기준 글자 x로 설정합니다.
        if (sameCount == 0 && diffCount == 0) 
        {
            x = c;
        }

        // 현재 읽은 글자(c)가 기준 글자(x)와 같은지 다른지 판별하여 카운트를 올립니다.
        if (x == c) 
        {
            sameCount++; // 기준 글자와 같으면 sameCount 1 증가
        } 
        else 
        {
            diffCount++; // 기준 글자와 다르면 diffCount 1 증가
        }
        
        // 두 카운트가 같아지는 바로 그 순간, 문자열을 하나로 분리합니다.
        if (sameCount == diffCount) 
        {
            answer++;       // 문자열 조각 개수 1 증가
            sameCount = 0;  // 다음 조각을 다시 처음부터 세기 위해 0으로 초기화
            diffCount = 0;  // 다음 조각을 다시 처음부터 세기 위해 0으로 초기화
            
            // [리팩토링 포인트 3] 다음 기준 글자(s[i+1])를 미리 찾을 필요가 없습니다.
            // 루프가 다음 바퀴를 돌 때, 위쪽의 (sameCount == 0 && diffCount == 0) 조건에 걸려 
            // 자연스럽게 다음 글자가 새로운 기준 글자(x)로 설정되기 때문입니다.
        }
    }

    // [리팩토링 포인트 4] 남은 짜투리 처리 로직 간소화
    // 반복문이 다 끝났는데 sameCount가 0보다 크다는 것은, 
    // 카운트가 같아지지 않아서 미처 분리되지 못한 찌꺼기 글자가 남아있다는 뜻입니다.
    if (sameCount > 0) 
    {
        answer++; // 이 남은 부분도 하나의 조각으로 인정하여 더해줍니다.
    }

    return answer; // 최종 조각 개수 반환
}

주요 리팩토링 포인트

  1. 불필요한 변수 제거: 사용하지 않는 vector<string> save;와 주석 처리된 코드를 지워 메모리 낭비를 줄였습니다.
  2. 범위 기반 for문 사용 (Modern C++): s[i]처럼 인덱스로 접근하는 대신 for (char c : s) 문법을 사용해 코드를 훨씬 직관적으로 바꿨습니다.
  3. 미래(i+1)를 내다보는 대신, 현재에 집중하기: 이전 코드에서는 잘라낸 직후에 다음 글자(s[i+1])를 미리 찾아 x에 넣었습니다. 하지만 **"카운트가 모두 0으로 초기화되었을 때 읽는 글자가 곧 새로운 기준 글자다"**라는 규칙을 이용하면, 굳이 범위를 벗어날 위험이 있는 i+1을 확인할 필요가 없습니다.

회고

필요할 것 같은 변수를 일단 생성해두고 삭제 안하는 습관이 정말 무서운 것 같다... 이점은 빠르게 고쳐야 할 것 같다.

'코드테스트' 카테고리의 다른 글

[level 1] 체육복 - 42862  (0) 2026.03.19
[level 1] 숫자 짝꿍 - 131128  (0) 2026.03.17
[level 1] 옹알이 (2) - 133499  (0) 2026.03.16
[level 1] 기사단원의 무기 - 136798  (0) 2026.03.13
덧칠하기 - 161989  (0) 2026.03.09

의견 종합


[창민님]
GAS활용 : 찬성
원하는 장르 : 협동해서 적을 막는 디펜스 게임
담당하고 싶은 역할 : 애니메이션을 제외한 기능
이전에 담당했던 역할 : 좀 많음
피하고 싶은 역할 : 애니메이션

[남태님]
GAS활용 : 찬성
원하는 장르 : RPG아니면 데바데같은 숨박꼭질 같은 장르
담당하고 싶은 역할 : 그리드 기반 배치 시스템 및 인벤토리, 몬스터 AI
이전에 담당했던 역할 : 애니메이션을 맡음, 캐릭터 움직임과 스킬 기능
피하고 시은 역할 : 애니메이션

[태우님]
GAS활용 : 찬성
원하는 장르 : 파티플레이가 가능한 협동 혹은 경쟁 게임 (장르는 크게 상관 없음)
담당하고 싶은 역할 : 플레이어 스킬 시스템
이전에 담당했던 역할 : 몬스터 AI
피하고 싶은 역할 : 몬스터 AI

[시환님]
GAS활용 : 찬성
원하는 장르 : 협동 공포 게임
담당하고 싶은 역할 : 몬스터 AI (사운드 학습하는 몬스터 AI)
이전에 담당했던 역할 : 몬스터 AI(추적, 공격, 애니메이션)
피하고 싶은 역할 : 애니메이션

[희원님]
GAS활용 : 찬성
원하는 장르 : 물리적인 요소들(상호작용 물체에 관해서 현실적으로 움직임을 반영하는 기능이 있으면 좋다)
담당하고 싶은 역할 : 캐릭터, 상호작용 물체들 로직 구현(아이템), NPC
이전에 담당했던 역할 : 몬스터 AI
피하고 싶은 역할 : 몬스터 AI

[둘내님]
GAS활용 : 구현이 편해지면 사용하는데 찬성함
원하는 장르 : 루나 섬 같은 아기자기한 RPG, 포코피아나 팰월드 같이 NPC한테 일 시키고, 플레이어는 파밍이나 전투하는 시스템
담당하고 싶은 역할 : 애니메이션만 아니면 된다.
이전에 담당했던 역할 : 인벤토리, UI, 아이템
피하고 싶은 역할 : 애니메이션


정리

장르총합을 해서 도시 건설 장르에 멀티플레이 PvP로 스타크래프트와 유사한 RTS 장르쪽으로 방향성을 잡았다.

1. 코어 루프 (Core Loop) 정의

게임의 뼈대를 이루는 3단계 순환 구조입니다. 플레이어는 이 사이클을 반복하며 스노우볼을 굴리게 됩니다.

  • 1단계: 기반 다지기 (자원 순환)
    • 흐름: 거점 <- 필드 파밍
    • 설명: 게임 시작 시 주어지는 기본 유닛(일꾼)을 이용해 맵에 배치된 자원을 채집하고 거점으로 가져옵니다. 초반 성장의 절대적인 기반이 됩니다.
  • 2단계: 인프라 및 전력 확장 (성장)
    • 흐름: 거점 -> 생산 건물 배치 -> [자원 생산 / 병력 생산 / 테크 연구]
    • 설명: 파밍한 자원을 소비하여 거점 주변에 건물을 짓습니다.
      • 필드 파밍의 한계를 극복하기 위한 '자원 생산' (예: 자동 채굴기, 농장)
      • 맵을 장악하기 위한 '병력 생산' (예: 보병, 기갑 유닛)
      • 더 강력한 유닛과 효율을 해금하는 '테크 연구' (예: 무기 업그레이드, 2티어 건물 해금)
  • 3단계: 맵 장악 및 승리 굳히기 (전투)
    • 흐름: 거점(에서 생산된 병력) -> 적 || 중립 땅따먹기
    • 설명: 생산된 병력을 진출시켜 중립 구역을 점령해 추가 자원 멀티(새로운 거점)를 확보하거나, 적의 거점을 파괴하여 승리합니다.

2. 핵심 시스템 기획안 (System Design)

플로우차트의 각 노드가 실제로 어떻게 작동할지 규칙을 정해야 합니다.

A. 자원 시스템 (Economy System)

  • 이원화된 자원 수급: * 필드 파밍: 초반에 빠르게 얻을 수 있지만, 맵 상에 한정되어 있거나 일꾼이 적에게 노출되는 위험(Risk)이 있습니다.
    • 자원 생산 건물: 짓는 데 비용과 시간이 들지만, 안전한 거점 안에서 지속적이고 안정적인 자원(Return)을 제공합니다.
  • 자원의 종류: 직관성을 위해 2~3개로 제한하는 것이 좋습니다. (예: 기본 건물용 '광물', 고급 테크/병력용 '가스' 또는 '전력')

B. 건물 및 테크 트리 시스템 (Tech Tree)

  • 건물 건설은 철저한 **선행 조건(Dependency)**을 가집니다.
  • 예: [막사]를 지어야 [보병] 생산 가능 $\rightarrow$ [막사]에서 '공학 연구'를 마쳐야 [군수공장] 배치 가능 $\rightarrow$ [군수공장]에서 [탱크] 생산.

C. 땅따먹기 시스템 (Territory Control)

  • RTS에서 '땅따먹기'의 재미를 극대화하려면 거점 점령의 메리트가 확실해야 합니다.
  • 중립 구역: 점령 시 시야(Vision)를 넓혀주거나, 정기적으로 특수 자원을 제공하는 '전략 거점(Control Point)' 형태로 디자인합니다.
  • 새로운 거점을 점령하면 그곳에서 다시 [거점 $\rightarrow$ 필드 파밍 $\rightarrow$ 건물 배치]의 새로운 루프가 시작되며 플레이어의 세력이 확장됩니다.

3. 게임 플레이 페이즈 (Game Flow)

기획한 뼈대가 시간 흐름에 따라 어떻게 작동하는지 시뮬레이션합니다.

  1. 초반 (Early Game): 정찰과 파밍
    • 목표: 필드 파밍 극대화 및 빠른 정찰.
    • 행동: 일꾼을 최대로 뽑아 필드 자원을 캐고, 최소한의 방어 병력만 생산한 뒤 1티어 테크 연구에 집중합니다.
  2. 중반 (Mid Game): 힘 싸움과 멀티 확보
    • 목표: 중립 땅따먹기 및 자원 생산 건물 최적화.
    • 행동: 주력 병력을 생산하여 맵 중앙의 중요 거점을 두고 적과 국지전을 벌입니다. 본진 자원이 고갈되기 전에 새로운 거점(멀티)을 개척합니다.
  3. 후반 (Late Game): 총력전
    • 목표: 적 거점 파괴.
    • 행동: 최종 테크 연구를 완료하고 강력한 고급 유닛을 조합하여 적의 본진으로 진격합니다.

https://school.programmers.co.kr/learn/courses/30/lessons/42862

 

프로그래머스

SW개발자를 위한 평가, 교육의 Total Solution을 제공하는 개발자 성장을 위한 베이스캠프

programmers.co.kr

 

설계

학생 전체 수의 +2 인 사이즈의 int 타입 벡터 변수 students를 선언한다. n+2사이즈인 이유는 나중에 여벌을 빌려주기위해 순회를 할 때 여벌을 가진 학생의 ±1 위치를 확인해야 하기 위함이다. 

 이제 students를 순회 하면서 0이 나온다면 앞자리와 뒷자리 학생을 조회하여 2이면 여벌학생을 1로, 도난당한 학생을 1로 변경한다. 여기서 앞에 사람을 먼저 조회 해야하는데 이유는 그래야 최대한 많은 인원들에게 빌려주는 로직이 되기 때문이다.

 

구현

#include <string>
#include <vector>

using namespace std;

int solution(int n, vector<int> lost, vector<int> reserve) {
    int answer = 0;
    vector<int> students(n+2, 1);
    
    for(int l : lost)
    {
        students[l]--;
    }
    
    for(int r : reserve)
    {
        students[r]++;
    }
    
    for (int i = 1; i <= n ; i++)
    {
      if(students[i] == 0)
      {
          if(students[i-1] == 2)
          {
              students[i-1]--;
              students[i]++;
          }
          else if(students[i+1] == 2)
          {
              students[i+1]--;
              students[i]++;
          }
      }
    }

    for(int i = 1; i <= n; i++)
    {
        if(students[i] > 0)
        {
            answer++;
        }
    }
    
    return answer;
}

 

리펙토링 코드

#include <vector>

using namespace std;

// 1. 매개변수 최적화: 배열 복사 비용을 없애기 위해 'const 참조자(&)'를 사용합니다.
int solution(int n, const vector<int>& lost, const vector<int>& reserve) {
    
    // 2. 발상의 전환: 일단 전체 학생(n)이 모두 수업을 들을 수 있다고 가정하고 시작합니다.
    int answer = n; 
    
    // 패딩을 포함한 학생 배열 초기화
    vector<int> students(n + 2, 1);
    
    // 잃어버린 학생과 여벌이 있는 학생의 상태를 기록합니다.
    for(int l : lost) {
        students[l]--;
    }
    for(int r : reserve) {
        students[r]++;
    }
    
    // 메인 로직: 1번부터 n번까지 순회하며 체육복을 빌립니다.
    for (int i = 1; i <= n ; i++) {
        
        // 내가 체육복이 없을 때
        if(students[i] == 0) {
            
            // 앞사람에게 빌리기 시도
            if(students[i - 1] == 2) {
                students[i - 1]--;
                students[i]++;
            }
            // 앞사람이 안 되면 뒷사람에게 빌리기 시도
            else if(students[i + 1] == 2) {
                students[i + 1]--;
                students[i]++;
            }
            // 앞뒤 모두에게 체육복을 빌리는 데 실패했다면?
            else {
                // 3. 최종 루프 생략: 이 학생은 확실히 수업을 못 듣게 되었으므로 정답에서 1을 뺍니다.
                answer--; 
            }
        }
    }
    
    // 별도의 카운팅 루프 없이 바로 정답 반환
    return answer;
}

코딩 테스트를 위한 추가 리팩토링 포인트

1. 매개변수 상수 참조(const vector<int>&)로 메모리 오버헤드 제거

  • 이유: 프로그래머스가 기본 제공하는 템플릿은 vector<int> lost처럼 '값 복사(Call by Value)' 형태로 되어 있습니다. 데이터가 작을 땐 상관없지만, 코딩 테스트에서 배열의 크기가 10만, 100만 개로 커지면 함수가 호출될 때마다 배열 전체를 통째로 복사하느라 엄청난 시간과 메모리를 낭비하게 됩니다.
  • 해결: 이를 const vector<int>& lost로 바꿔주면, 복사 없이 원본의 주소만 참조하여 속도를 극대화할 수 있습니다. (코딩 테스트의 기본 소양 중 하나입니다.)

2. 변수 추적으로 불필요한 루프(for) 제거

  • 이유: 현재 코드는 배열을 세팅하고, 빌려주는 루프를 돈 다음, 마지막에 정답을 세는 루프를 또 돌고 있습니다.
  • 해결: 발상을 조금 바꿔서 **"처음에는 N명 모두가 수업을 들을 수 있다"**고 가정(answer = n)해 봅니다. 그리고 체육복을 빌리는 메인 루프에서 끝내 체육복을 빌리지 못한 학생이 발생할 때만 answer에서 1을 빼면 마지막 카운팅 루프를 아예 삭제할 수 있습니다.

회고

오늘은 제한사항에 여벌을 가진 학생도 도난을 당할 수 있다는 부분을 확인하지 못하고 빙빙 돌고 말았다. 문제가 길다고 대충보고 제한사항을 간략히 보고 넘어갔는데 이제는 제한사항은 정독 할 필요가 있다고 생각했다.

'코드테스트' 카테고리의 다른 글

[level 1] 문자열 나누기 - 140108  (0) 2026.03.20
[level 1] 숫자 짝꿍 - 131128  (0) 2026.03.17
[level 1] 옹알이 (2) - 133499  (0) 2026.03.16
[level 1] 기사단원의 무기 - 136798  (0) 2026.03.13
덧칠하기 - 161989  (0) 2026.03.09

https://github.com/NMEii/NBC_Task9?tab=readme-ov-file#chatx

 

GitHub - NMEii/NBC_Task9

Contribute to NMEii/NBC_Task9 development by creating an account on GitHub.

github.com

 

ChatX

Unreal Engine 5.5 기반의 멀티플레이어 채팅 시스템 예제 프로젝트이다. C++ 및 UMG(Unreal Motion Graphics)를 활용하여 클라이언트와 서버 간의 텍스트 채팅 기능을 구현했다.

사용된 기술 (Tech Stack)

  • Engine: Unreal Engine 5.5
  • Language: C++ / Blueprints
  • UI: UMG (Slate, SlateCore)
  • Network: Server & Client RPC (Remote Procedure Call)

주요 기능 (Features)

  • 멀티플레이어 네트워크 동기화: ServerRPC와 ClientRPC를 사용하여 채팅 메시지를 서버에서 검증하고 모든 클라이언트로 전파하는 기본적인 채팅 시스템을 구현했다.
  • UMG UI 시스템: UEditableTextBox와 UUserWidget 베이스 클래스에 C++ 클래스를 바인딩하여 채팅 입력창(WBP_ChatInput) 및 알림창(WBP_NotificationText)을 구현했다.
  • C++ 클래스 및 블루프린트 연동:
    • CXPlayerController: 채팅 메시지의 RPC 통신 및 위젯(채팅창, 텍스트 알림) 생성/관리를 담당한다.
    • CXChatInput: UMG 위젯 이벤트 처리를 위한 C++ UI 클래스로, 텍스트 입력 커밋 시 컨트롤러로 이벤트를 전달한다.
    • 이외 게임 모드(CXGameModeBase), 게임 스테이트(CXGameStateBase), 폰(CXPawn) 등이 구현되어 있다.

프로젝트 구조 (Project Structure)

  • Source/ChatX/Game: 게임 모드 및 게임 스테이트 클래스
  • Source/ChatX/Player: 폰, 플레이어 컨트롤러, 플레이어 스테이트 클래스 (RPC 로직 포함)
  • Source/ChatX/UI: 채팅 위젯 및 알림 UI 베이스 C++ 클래스
  • Content/ChatX/Blueprint: UI 위젯, 게임 룰, 플레이어 캐릭터 등의 블루프린트 애셋 (WBP_ChatInput, WBP_NotificationText, BP_PlayerController 등)
  • Content/ChatX/Maps: 데모 맵 (L_Chatting)

실행 방법 (Getting Started)

  1. 프로젝트 루트의 ChatX.uproject를 우클릭하여 Generate Visual Studio project files를 실행한다.
  2. 생성된 솔루션 파일(ChatX.sln)을 열고 프로젝트를 빌드한다.
  3. Unreal Editor에서 프로젝트를 실행하고, 기본 맵(L_Chatting)을 연다.
  4. Net Mode를 Play As Listen Server 혹은 Play As Client로 설정하고 다수의 플레이어를 배치한 뒤 실행하여 채팅 시스템을 테스트할 수 있다.

1. PlayerState를 활용한 플레이어 정보 관리

멀티플레이어 환경에서 각 플레이어의 고유한 데이터(이름, 시도 횟수 등)를 관리하기 위해 PlayerState 클래스를 상속받아 ACXPlayerState를 구현함. GameModeBase의 OnPostLogin 함수를 통해 새로운 플레이어가 접속할 때마다 전체 컨트롤러 배열의 크기를 기반으로 "Player1", "Player2"와 같이 고유한 번호를 지정해 주었음. 또한, 게임 진행에 필요한 현재 시도 횟수(CurrentGuessCount)와 최대 시도 횟수(MaxGuessCount)를 이곳에서 안전하게 보관하도록 설계함.

2. 네트워크 동기화를 위한 프로퍼티 레플리케이션

서버에서 관리되는 플레이어의 정보와 UI 공지사항을 클라이언트와 일치시키기 위해 프로퍼티 레플리케이션을 적용함. 레플리케이션이 필요한 변수에 UPROPERTY(Replicated) 매크로를 선언하고, GetLifetimeReplicatedProps 함수 내에서 DOREPLIFETIME 매크로를 사용하여 서버의 값이 변경될 때마다 클라이언트에게 자동으로 전달되도록 설정함. 변하지 않는 값인 MaxGuessCount도 클라이언트가 게임 시작 시 초기화된 값을 정상적으로 넘겨받아 UI 등에 활용하려면 레플리케이션 등록이 필요하다는 것을 알게 됨.

3. 게임 승패 판정 로직 및 UI 연동

채팅으로 입력받은 숫자 문자열을 판별하여 3 스트라이크일 경우 승리로 처리하고, 모든 플레이어가 최대 시도 횟수를 소진할 경우 무승부로 처리하는 로직을 GameModeBase에 구현함. 결과 판정 후에는 PlayerController에 선언된 NotificationText 변수를 갱신함. 이 변수는 클라이언트의 UMG 위젯(WBP_NotificationText)과 바인딩되어 있어, 값이 변경되면 즉시 플레이어의 화면에 공지사항으로 출력됨. 게임이 종료된 후에는 ResetGame 함수를 호출하여 정답을 새로 생성하고 모든 플레이어의 시도 횟수를 0으로 초기화함.


1. 변수 동기화 (프로퍼티 레플리케이션)

멀티플레이어 게임에서 서버와 클라이언트 간의 데이터를 일치시키는 핵심 설정이다.

// CXPlayerState.cpp

#include "CXPlayerState.h"
#include "Net/UnrealNetwork.h"

ACXPlayerState::ACXPlayerState()
	: PlayerNameString(TEXT("None"))
	, CurrentGuessCount(0)
	, MaxGuessCount(3)
{
    // 이 액터가 네트워크를 통해 레플리케이트 되도록 활성화한다.
	bReplicates = true;
}

void ACXPlayerState::GetLifetimeReplicatedProps(TArray<class FLifetimeProperty>& OutLifetimeProps) const
{
	Super::GetLifetimeReplicatedProps(OutLifetimeProps);

    // 서버에서 값이 변경되면 클라이언트들에게 동기화되도록 변수들을 등록한다.
	DOREPLIFETIME(ThisClass, PlayerNameString);
	DOREPLIFETIME(ThisClass, CurrentGuessCount);
	DOREPLIFETIME(ThisClass, MaxGuessCount); 
}

 

2. 게임 승패 판정 및 초기화 로직

스트라이크 개수에 따라 승리를 판정하거나, 모든 플레이어의 턴이 끝났는지 확인하여 무승부를 판정하는 로직이다.

// CXGameModeBase.cpp

void ACXGameModeBase::JudgeGame(ACXPlayerController* InChattingPlayerController, int InStrikeCount)
{
    // 3 스트라이크를 달성한 경우 해당 플레이어의 승리로 판정한다.
	if (3 == InStrikeCount)
	{
		ACXPlayerState* CXPS = InChattingPlayerController->GetPlayerState<ACXPlayerState>();
		for (const auto& CXPlayerController : AllPlayerControllers)
		{
			if (IsValid(CXPS) == true)
			{
                // 승리 메시지를 만들어 모든 플레이어의 UI 텍스트 변수를 갱신한다.
				FString CombinedMessageString = CXPS->PlayerNameString + TEXT(" has won the game.");
				CXPlayerController->NotificationText = FText::FromString(CombinedMessageString);

                // 게임 데이터를 초기 상태로 되돌린다.
				ResetGame();
			}
		}
	}
	else
	{
        // 무승부 여부를 확인하기 위한 플래그 변수를 선언한다.
		bool bIsDraw = true;
		for (const auto& CXPlayerController : AllPlayerControllers)
		{
			ACXPlayerState* CXPS = CXPlayerController->GetPlayerState<ACXPlayerState>();
			if (IsValid(CXPS) == true)
			{
                // 단 한 명이라도 시도 횟수가 남아있다면 무승부가 아니므로 검사를 중단한다.
				if (CXPS->CurrentGuessCount < CXPS->MaxGuessCount)
				{
					bIsDraw = false;
					break;
				}
			}
		}

        // 모든 플레이어가 시도 횟수를 소진했다면 무승부 처리 후 게임을 초기화한다.
		if (true == bIsDraw)
		{
			for (const auto& CXPlayerController : AllPlayerControllers)
			{
				CXPlayerController->NotificationText = FText::FromString(TEXT("Draw..."));
				ResetGame();
			}
		}
	}
}

 


위젯 (WBP_NotificationText)

 

Text > Details 에 다음과 같이 설정

 

 

BP_PlayerController > Details

  • Notification Text Widget Class : WBP_NotificationText 설정

 

위와 같이 로직을 채우면 아래와 같이 화면에 출력되는 것을 볼 수 있다.

+ Recent posts