icon
9장 : 게임플레이 프레임워크

체크포인트와 세이브 설계


지금까지 게임의 핵심 로직과 네트워크 동기화에 대해 알아보았습니다. 이제 플레이어가 게임을 중단했다가 나중에 다시 시작할 수 있도록 게임의 진행 상황을 저장하고 불러오는 시스템을 설계하는 방법에 대해 다룰 차례입니다. 이는 플레이어 경험에 필수적인 요소이자, 게임 디자인 단계에서부터 신중하게 고려해야 할 부분입니다.

이번 절에서는 언리얼 엔진에서 체크포인트(Checkpoint)세이브/로드(Save/Load) 시스템을 구현하는 기본 개념과 설계 고려 사항에 대해 알아보겠습니다.


체크포인트와 세이브 시스템이란 무엇인가?

  • 체크포인트(Checkpoint)

    • 게임플레이 중 특정 지점에 도달했을 때, 플레이어의 현재 상태(위치, 체력, 인벤토리 등)를 자동으로 저장하는 지점입니다.
    • 주로 플레이어가 사망했을 때 마지막 체크포인트에서 다시 시작하도록 하여, 플레이어가 좌절하지 않고 게임을 계속 진행할 수 있도록 돕습니다.
    • 체크포인트는 일반적으로 플레이어의 명시적인 행동 없이 자동으로 이루어집니다.
  • 세이브/로드(Save/Load) 시스템

    • 플레이어가 원할 때 게임의 진행 상황을 파일로 저장하고, 나중에 이 파일을 불러와 게임을 재개할 수 있도록 하는 시스템입니다.
    • 게임의 모든 중요한 상태(전역 변수, 레벨 상태, 퀘스트 진행도 등)를 저장하고 불러올 수 있어야 합니다.

왜 세이브/로드 시스템이 필요한가?

  • 플레이어 편의성: 플레이어가 원하는 시간에 게임을 중단하고 다시 시작할 수 있도록 합니다.
  • 진행 상황 유지: 플레이어가 공들여 쌓은 게임 진행 상황과 성과를 잃지 않도록 보장합니다.
  • 게임 경험 개선: 플레이어가 실패했을 때 너무 많은 진행 상황을 잃지 않도록 하여 재시도를 독려하고 게임 진입 장벽을 낮춥니다.
  • 디버깅 및 개발: 개발 과정에서도 특정 지점부터 테스트하거나 재현하는 데 유용합니다.

언리얼 엔진의 세이브 시스템

언리얼 엔진은 게임 데이터를 저장하고 불러오는 데 SaveGame 클래스를 사용합니다. 이 클래스는 마치 일반적인 블루프린트처럼 변수를 선언하고 값을 저장할 수 있는 데이터 컨테이너 역할을 합니다.

SaveGame 클래스의 특징

  • 비-액터(Non-Actor) 클래스: SaveGame 클래스는 월드에 존재하지 않는 순수 데이터 클래스입니다. 액터가 아니므로 틱(Tick)이 없고, 메시나 콜리전 같은 컴포넌트도 없습니다.
  • 직렬화(Serialization): SaveGame 오브젝트에 저장된 변수들은 자동으로 바이너리 파일로 직렬화되어 디스크에 저장됩니다.
  • 데이터 컨테이너: 게임의 중요한 데이터를 담는 용도로만 사용됩니다.

세이브/로드 시스템 설계 및 구현 단계

저장할 데이터 정의

먼저 게임에서 어떤 데이터들을 저장해야 할지 결정하고, 이를 담을 SaveGame 블루프린트를 만듭니다.

새 블루프린트 클래스 생성: 콘텐츠 브라우저에서 마우스 오른쪽 버튼 클릭 > 블루프린트 클래스(Blueprint Class) > All Classes에서 SaveGame 을 검색하여 선택합니다.

  • 이름을 지정합니다. (예: BP_MySaveGame)

저장할 변수 추가: BP_MySaveGame 블루프린트를 열고, 게임의 상태를 나타내는 필요한 변수들을 추가합니다.

  • 플레이어 관련: PlayerLocation (Vector), PlayerHealth (Float), PlayerInventory (Array of Structs), CurrentWeapon (Enum/String)
  • 게임 진행 관련: CurrentLevelName (Name), QuestProgress (Map/Struct), UnlockedAbilities (Array of Enum)
  • 월드 상태 관련: DestroyedEnemies (Array of UniqueIDs), CollectedItems (Array of UniqueIDs)
  • 중요: SaveGame 클래스에 저장할 변수들은 SaveGame 클래스 자체가 지원하는 변수 타입이어야 합니다. 일반적으로 기본 변수 타입(Int, Float, Bool, String, Vector, Rotator, Transform 등), Enum, Struct, 그리고 이들의 배열 타입은 지원됩니다. 액터 레퍼런스(Actor Reference)나 컴포넌트 레퍼런스는 직접 저장할 수 없으며, 대신 해당 액터의 Unique IDTag, 또는 액터를 식별할 수 있는 다른 방식으로 정보를 저장하고 로드 시 다시 액터를 찾아 설정해야 합니다.

체크포인트 시스템 구현

체크포인트 액터를 만들고, 플레이어가 체크포인트에 도달하면 자동으로 게임 상태를 저장하도록 합니다.

BP_Checkpoint 액터 생성

  • 새로운 액터 블루프린트(예: BP_Checkpoint)를 만들고, Box Collision 컴포넌트와 Static Mesh (시각적 표시용)를 추가합니다.
  • Box Collision 컴포넌트에서 On Component Begin Overlap 이벤트를 생성합니다.

저장 로직 구현 (플레이어가 겹쳤을 때)

  • On Component Begin Overlap 이벤트에서 Other ActorCast To BP_PlayerCharacter로 캐스팅합니다.
  • 캐스팅 성공 시, Get Game Mode 노드를 호출하고 여러분의 게임 모드(GM_MyGame)로 캐스팅합니다.
  • GM_MyGame에서 Save Game 커스텀 이벤트를 호출하도록 연결합니다. (또는 GM_MyGame에서 해당 로직을 직접 구현)
  • 중복 저장 방지: 한 번 저장된 체크포인트는 다시 저장되지 않도록, BP_Checkpoint 내부에 bIsActivated와 같은 불리언 변수를 추가하고, 저장 후 True로 설정한 뒤 다음 오버랩 시 이 변수를 확인하는 로직을 추가합니다.

게임 저장 및 불러오기 로직

게임 저장/불러오기 로직은 보통 GameMode (싱글 플레이어 게임) 또는 PlayerController (멀티플레이어 게임의 경우 클라이언트 측 저장)에서 구현됩니다. 여기서는 GameMode를 기준으로 설명합니다.

GM_MyGame 블루프린트 열기

세이브 슬롯 이름 정의: Save Slot Name 변수(String)를 생성하고 MyGameSaveSlot 등으로 이름을 지정합니다. (여러 개의 세이브 파일을 관리할 경우 슬롯 번호 등을 추가)

저장 함수 (SaveGame)

  • SaveGame이라는 커스텀 이벤트를 생성합니다.
  • Does Save Game Exist 노드를 사용하여 해당 슬롯에 기존 세이브 파일이 있는지 확인합니다.
    • Slot Name 핀에 미리 정의한 Save Slot Name 변수를 연결합니다.
  • True (기존 세이브 파일 있음)
    • Load Game From Slot 노드를 호출하여 기존 SaveGame 오브젝트를 로드합니다.
    • Return ValueCast To BP_MySaveGame으로 캐스팅합니다.
    • 로드된 BP_MySaveGame 오브젝트에 현재 게임 상태 데이터를 업데이트합니다. (예: Set PlayerLocation, Set PlayerHealth 등)
  • False (기존 세이브 파일 없음)
    • Create SaveGame Object 노드를 호출하여 새로운 BP_MySaveGame 오브젝트를 생성합니다.
    • SaveGame Class 핀에 BP_MySaveGame을 선택합니다.
    • Return ValueCast To BP_MySaveGame으로 캐스팅합니다.
  • 공통 로직 (새로 생성했든 로드했든)
    • 플레이어의 현재 위치: Get Player Pawn > Get Actor LocationBP_MySaveGamePlayerLocation 변수에 저장.
    • 플레이어의 현재 체력: Get Player Pawn > (Cast to PlayerCharacter) > Get HealthBP_MySaveGamePlayerHealth 변수에 저장.
    • 다른 모든 저장할 데이터들도 유사하게 BP_MySaveGame 오브젝트의 해당 변수에 업데이트합니다.
    • 최종적으로 Save Game To Slot 노드를 호출합니다.
      • SaveGame Object 핀에 업데이트된 BP_MySaveGame 오브젝트를 연결합니다.
      • Slot Name 핀에 Save Slot Name 변수를 연결합니다.
      • User Index는 보통 0으로 설정합니다. (단일 유저 세이브)
    • Print String 등으로 "게임 저장 완료!" 메시지를 출력합니다.

불러오기 함수 (LoadGame)

  • LoadGame이라는 커스텀 이벤트를 생성합니다. (UI 버튼 클릭 시 호출되도록)
  • Does Save Game Exist 노드를 사용하여 해당 슬롯에 세이브 파일이 있는지 확인합니다.
    • False일 경우, Print String으로 "저장된 게임이 없습니다!" 메시지를 출력하고 함수 종료.
  • True일 경우
    • Load Game From Slot 노드를 호출하여 BP_MySaveGame 오브젝트를 로드합니다.
    • Return ValueCast To BP_MySaveGame으로 캐스팅합니다.
    • 로드된 BP_MySaveGame 오브젝트의 변수 값을 읽어서 게임 상태를 복원합니다.
      • 플레이어 위치: Get Player Pawn > Set Actor Location을 호출하고 PlayerLocation 변수 값을 연결합니다.
      • 플레이어 체력: Get Player Pawn > (Cast to PlayerCharacter) > Set Health를 호출하고 PlayerHealth 변수 값을 연결합니다.
      • 레벨 로드: 만약 저장된 레벨 이름(CurrentLevelName)이 현재 레벨과 다르다면, Open Level (by Name) 노드를 호출하여 해당 레벨로 이동합니다. (이 경우 레벨 로드 후 On Level Loaded 이벤트에서 다시 플레이어 위치 등을 설정해야 할 수 있습니다.)
      • 월드 상태 복원: DestroyedEnemiesCollectedItems 같은 배열을 순회하며 해당 액터들을 월드에서 파괴하거나 숨기는 로직을 구현합니다. (이를 위해 액터들에게 고유한 ID를 부여하는 것이 중요합니다.)
    • Print String 등으로 "게임 불러오기 완료!" 메시지를 출력합니다.

UI 버튼에 연결 (선택 사항)

메인 메뉴나 인게임 메뉴의 세이브/로드 버튼에 위에서 구현한 SaveGameLoadGame 함수를 연결합니다.

  • 버튼 On Clicked 이벤트 → Get Game Mode (또는 Get Player Controller) → Cast To GM_MyGameSaveGame 또는 LoadGame 함수 호출.

세이브 시스템 설계 시 주요 고려사항

  • 저장할 데이터의 범위: 모든 것을 저장할 필요는 없습니다. 다시 생성하거나 쉽게 유추할 수 있는 데이터는 저장하지 않아도 됩니다. (예: 임시 이펙트, 단기적인 변수)
  • 복잡한 액터 저장: 액터 자체를 저장하는 것은 불가능합니다. 액터의 Unique ID (또는 Tag), Class, Transform 등의 핵심 정보를 저장하고, 로드 시 해당 정보를 바탕으로 액터를 다시 스폰하거나 찾아 데이터를 복원해야 합니다.
    • 특히 월드에 이미 배치되어 있는 액터(Placed Actor)의 경우, 그 액터의 고유한 이름을 저장하고, 로드 시 Get All Actors Of Class 등으로 해당 액터를 찾아 데이터를 복원하는 방식이 흔히 사용됩니다.
  • 로드 시점: 게임을 로드할 때 어떤 레벨에서 로드할 것인지, 그리고 로드된 레벨에서 캐릭터가 어디에 스폰될지 등을 고려해야 합니다.
    • 일반적으로 저장된 레벨로 이동 후, 해당 레벨의 BeginPlay 또는 특정 로드 이벤트에서 PlayerControllerGameMode가 로드된 데이터를 바탕으로 플레이어와 월드 상태를 복원합니다.
  • 성능: 너무 많은 데이터를 저장하거나, 너무 자주 저장하면 성능에 영향을 미칠 수 있습니다. 필요한 최소한의 데이터만 저장하고, 저장 빈도를 조절합니다.
  • 데이터 버전 관리: 게임 업데이트 시 세이브 파일의 구조가 변경될 수 있습니다. SaveGame 클래스에 SaveGameVersion (Int) 변수를 추가하여, 로드 시 버전 불일치를 감지하고 이전 버전 데이터를 변환하는 로직을 구현할 수 있습니다.
  • 보안: 중요한 세이브 데이터(치트 방지)의 경우 암호화나 해시를 적용하여 변조를 방지할 수 있습니다.
  • UI 피드백: 저장/로드 진행 중임을 플레이어에게 알려주는 UI(예: "저장 중...", 로딩 바)를 제공하여 사용자 경험을 개선합니다.

체크포인트와 세이브 시스템은 플레이어가 게임을 지속적으로 즐길 수 있도록 하는 필수적인 백본 시스템입니다. 잘 설계된 세이브 시스템은 플레이어의 몰입도를 높이고 게임의 만족도를 향상시킵니다.


이번 절에서는 언리얼 엔진에서 체크포인트와 세이브/로드 시스템을 설계하고 구현하는 기본 개념과 SaveGame 클래스 활용법에 대해 알아보았습니다. 게임의 진행 상황을 안정적으로 저장하고 불러오는 것은 플레이어에게 매우 중요한 기능입니다.