Post

2026-04-22 TIL (42일차)

2026-04-22 TIL (42일차)

Unreal 게임 플로우 설계와 데이터 관리 아키텍처

게임 플로우(Game Flow)란 플레이어가 게임을 시작하고, 진행하며, 클리어(또는 게임 오버)하기까지의 모든 규칙과 흐름을 의미합니다. 언리얼 엔진에서는 이 흐름을 어디에 구현하느냐가 프로젝트의 안정성과 직결됩니다.


1. 게임 흐름을 어디에 구현할까? (아키텍처 설계)

작성하신 대로 GameModeBaseGameStateBase와, GameModeGameState와 짝을 맞추어 사용하는 것이 기본 원칙입니다. 게임 흐름은 프로젝트 규모와 멀티플레이 여부에 따라 구현 위치가 달라집니다.

🔹 1) GameMode에 구현하는 경우 (서버 / 규칙 관리자)

  • 역할: 게임의 ‘심판’ 역할입니다. 승리 조건, 스폰 규칙, 데미지 공식 등을 처리합니다.
  • 멀티플레이 특징: 멀티플레이 시 GameMode는 오직 ‘서버(호스트)’에만 존재합니다. 클라이언트(접속자)의 컴퓨터에는 아예 존재하지 않습니다. 따라서 클라이언트가 해킹으로 규칙을 조작하는 것을 원천 차단할 수 있습니다.

🔹 2) GameState에 구현하는 경우 (전역 상황판)

  • 역할: 심판(GameMode)이 내린 결정의 ‘결과물’을 들고 있는 전광판입니다. 현재 스코어, 남은 시간, 퀘스트 진행도 등을 가집니다.
  • 멀티플레이 특징: 서버에 있는 GameState가 모든 클라이언트에게 복제(Replication)되어 뿌려집니다. 즉, 클라이언트가 “우리 팀 지금 몇 점이지?”를 알고 싶다면 GameState를 열어봐야 합니다.

🔹 3) 기타 구현 위치 (프로그래머 스타일에 따라 다름)

  • PlayerController: 플레이어 개인의 흐름(UI 열기, 개인 미션 진행, 인벤토리)을 관리할 때 사용합니다.
  • LevelScriptActor (레벨 블루프린트): 특정 맵(레벨)에서만 발생하는 고유한 기믹이나 시네마틱 연출 흐름을 짤 때 사용합니다.

멀티플레이 정석 흐름: 클라이언트가 몬스터를 때림 -> 서버의 GameMode가 죽었다고 판정하고 점수 10점 올림 -> 점수 결과를 GameState에 기록 -> GameState가 모든 클라이언트의 화면(UI)에 10점 올랐다고 동기화해 줌.


2. 스폰(Spawn) 함수가 AActor*를 반환해야 하는 이유

기존에 단순히 화면에 아이템을 띄우기만 할 때는 void 반환형으로 충분했습니다. 하지만 GameState에서 게임 흐름을 제어하려면 AActor* (액터 포인터)를 반환받아야 합니다.

  • void 반환의 한계 (Fire and Forget): 엔진에게 “아이템 하나 생성해!”라고 명령만 하고 끝입니다. 방금 태어난 아이템이 메모리 어디에 있는지 알 길이 없습니다.
  • AActor* 반환의 장점 (추적과 관리): 아이템을 생성함과 동시에 그 아이템의 ‘메모리 주소’를 돌려받습니다.
    • 왜 필요할까?: GameState가 “맵에 코인이 10개 스폰됐고, 다 먹으면 게임 클리어”라는 규칙을 관리한다고 칩시다. GameState는 방금 태어난 코인들의 주소(AActor*)를 배열(TArray)에 담아두고 추적해야 합니다. 그래야 코인이 파괴될 때 남은 개수를 카운팅하고, 0개가 되면 다음 스테이지로 넘기는 흐름을 만들 수 있습니다.


3. 언리얼 IsA 함수의 정확한 기능

IsA 함수는 객체가 특정 클래스인지, 혹은 그 클래스를 상속받은 자식 클래스인지 안전하게 확인할 때 사용하는 매우 유용한 함수입니다. (메모하신 ‘하위 클래스까지 인지해준다’는 말이 정확합니다. = 다형성)

  • 동작 원리: if (MyItem->IsA(ACoinItem::StaticClass()))
    • 만약 MyItemACoinItem 이라면? 👉 True
    • 만약 MyItemACoinItem을 상속받아 만든 ABigCoinItem이나 ASmallCoinItem이라면? 👉 부모가 코인이므로 True
    • 만약 MyItem이 힐링 포션이라면? 👉 False
  • Cast와의 차이: Cast<ACoinItem>(MyItem)은 진짜로 그 타입으로 변환해서 기능을 쓰고자 할 때(무거움) 쓰고, IsA는 단순히 “너 코인 계열 맞지?”라고 족보(타입)만 검사할 때(가벼움) 사용합니다.


4. 레벨 전환 초기화와 GameInstance (싱글톤)

GameState는 치명적인 약점이 하나 있습니다. 바로 현재 레벨(World)에 종속된 액터라는 점입니다.

  • 초기화 문제: 레벨 1에서 레벨 2로 넘어갈 때(Level Transition), 이전 레벨에 있던 모든 액터(GameState 포함)는 메모리에서 파괴되고 새로운 레벨의 GameState가 BeginPlay를 호출하며 0부터 다시 태어납니다. 누적된 스코어가 다 날아가 버립니다.
  • 해결책: GameInstance 사용
    • GameInstance는 레벨이 아니라 ‘게임 프로그램(애플리케이션)’ 자체에 종속된 객체입니다. 게임이 켜질 때 단 1개만 생성되고, 게임을 완전히 종료할 때까지 절대 파괴되지 않습니다. (디자인 패턴의 싱글톤 객체와 유사합니다.)
    • 따라서 여러 레벨에 걸쳐서 유지해야 하는 정보(총 누적 점수, 플레이어의 인벤토리, 환경 설정값 등)는 반드시 GameInstance에 저장해 두고 필요할 때마다 꺼내 써야 합니다.




Unreal UI 및 데이터 연동

마스터 반 과제

개인과제 로직 수정 및 진행

This post is licensed under CC BY 4.0 by the author.