체크포인트와 세이브 설계
SaveGame에 진행 상태를 정의하고 체크포인트 저장·슬롯 로드·실패 복구 흐름을 설계해 재개 기능을 구현합니다.
지금까지는 게임 핵심 로직과 네트워크 동기화를 다뤘습니다.
이제 플레이어가 중단 후 재개할 수 있도록 진행 상황 저장/불러오기 시스템을 설계해야 합니다.
이 기능은 플레이어 경험에 직접 영향을 주므로 게임 디자인 단계부터 신중히 고려해야 합니다.
이번 절에서는 체크포인트(Checkpoint)와 세이브/로드(Save/Load) 구현의 기본 개념과 설계 고려 사항을 정리합니다.
체크포인트와 세이브 시스템이란 무엇인가?
-
체크포인트(Checkpoint)
- 게임플레이 중 특정 지점에 도달했을 때, 플레이어의 현재 상태(위치, 체력, 인벤토리 등)를 자동으로 저장하는 지점입니다.
- 주로 플레이어가 사망했을 때 마지막 체크포인트에서 다시 시작하도록 하여, 플레이어가 좌절하지 않고 게임을 계속 진행할 수 있도록 돕습니다.
- 체크포인트 저장은 일반적으로 플레이어의 명시적인 행동 없이 자동으로 이루어집니다.
-
세이브/로드(Save/Load) 시스템
- 플레이어가 원할 때 게임의 진행 상황을 파일로 저장하고, 나중에 이 파일을 불러와 게임을 재개할 수 있도록 하는 시스템입니다.
- 게임의 모든 중요한 상태(전역 변수, 레벨 상태, 퀘스트 진행도 등)를 저장하고 불러올 수 있어야 합니다.
왜 세이브/로드 시스템이 필요한가?
- 플레이어 편의성: 플레이어가 원하는 시간에 게임을 중단하고 다시 시작할 수 있도록 합니다.
- 진행 상황 유지: 플레이어가 공들여 쌓은 게임 진행 상황과 성과를 잃지 않도록 보장합니다.
- 게임 경험 개선: 플레이어가 실패했을 때 너무 많은 진행 상황을 잃지 않도록 하여 재시도를 독려하고 게임 진입 장벽을 낮춥니다.
- 디버깅 및 개발: 개발 과정에서도 특정 지점부터 테스트하거나 재현하는 데 유용합니다.
언리얼 엔진의 세이브 시스템
언리얼 엔진은 게임 데이터를 저장하고 불러오는 데 SaveGame 클래스를 사용합니다.
이 클래스는 마치 일반적인 블루프린트처럼 변수를 선언하고 값을 저장할 수 있는 데이터 컨테이너 역할을 합니다.
SaveGame 클래스의 특징
- 비-액터(Non-Actor) 클래스:
SaveGame클래스는 월드에 존재하지 않는 순수 데이터 클래스입니다. 액터가 아니므로 틱(Tick)이 없고, 메시나 콜리전 같은 컴포넌트도 없습니다. - 직렬화(Serialization): 데이터를 채운 뒤
Save Game To Slot또는 비동기 저장 노드를 호출해야 파일이 기록됩니다. 메모리의 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 GUIDs),CollectedItems(Array of GUIDs) - 중요:
SaveGame클래스에 저장할 변수들은SaveGame클래스 자체가 지원하는 변수 타입이어야 합니다. 일반적으로 기본 변수 타입(Int, Float, Bool, String, Vector, Rotator, Transform 등), Enum, Struct, 그리고 이들의 배열 타입은 지원됩니다. 오브젝트 참조의 직렬화는 액터와 모든 컴포넌트의 상태를 통째로 저장·재생성하는 기능이 아닙니다. 저장용 GUID나 명시적 ID와 복원할 데이터를 따로 저장하고, 로드 시 ID로 대상을 찾거나 생성합니다. 런타임GetUniqueID는 재실행 후에도 같은 대상을 가리키는 영구 ID가 아닙니다.
체크포인트 시스템 구현
체크포인트 액터를 만들고, 플레이어가 체크포인트에 도달하면 자동으로 게임 상태를 저장하도록 합니다. BP_Checkpoint에 Boolean 변수 bIsActivated와 비동기 저장을 사용할 때의 bIsSaving을 만들고 둘 다 false로 시작합니다. 성공·실패 결과를 체크포인트에 돌려주는 경로도 연결합니다. 저장 요청을 보낸 사실만으로 활성화하지 않습니다.
BP_Checkpoint 액터 생성- 새로운 액터 블루프린트(예:
BP_Checkpoint)를 만들고,Box Collision컴포넌트와Static Mesh(시각적 표시용)를 추가합니다. - 양쪽 컴포넌트의 Query/Overlap 응답과
Generate Overlap Events를 설정한 뒤,Box Collision에On Component Begin Overlap이벤트를 생성합니다.
On Component Begin Overlap이벤트에서Other Actor를Cast To BP_PlayerCharacter로 캐스팅합니다.- 캐스팅 성공 시,
Get Game Mode노드를 호출하고 여러분의 게임 모드(GM_MyGame)로 캐스팅합니다. GM_MyGame에서Save Game커스텀 이벤트를 호출하도록 연결합니다. (또는GM_MyGame에서 해당 로직을 직접 구현)- 중복 저장 방지:
bIsActivated를 검사하고 저장 성공을 확인한 뒤에만True로 바꿉니다. 비동기 저장이면bIsSaving으로 진행 중 요청을 막고, 완료 시 이를 해제합니다. 실패했다면 다시 시도할 수 있어야 합니다.
게임 저장 및 불러오기 로직
게임 저장/불러오기 로직은 보통 GameMode (싱글 플레이어 게임) 또는 PlayerController (멀티플레이어 게임의 경우 클라이언트 측 저장)에서 구현됩니다.
여기서는 로컬 플레이어 한 명인 싱글플레이 GameMode를 기준으로 설명합니다. 원격 클라이언트에는 GameMode가 없으므로 멀티플레이 클라이언트의 로컬 저장에 그대로 적용하지 않습니다.
다른 레벨 로드 예제를 따라 하기 전에 프로젝트의 GameInstance 파생 Blueprint를 만들고 Maps & Modes의 Game Instance Class로 지정합니다. 여기에 BP_MySaveGame 오브젝트 참조형 PendingSave를 만들고 기본값은 비워 둡니다. 아래의 ApplyPendingSave는 직접 만들 복원 함수입니다. 새 GameMode의 플레이어 생성 완료 경로(예: 기본 RestartPlayer 흐름을 마친 OnRestartPlayer 처리)에서 현재 Pawn 유효성을 확인해 호출하도록 연결합니다. 임의 BeginPlay의 실행 순서에 기대지 않습니다. Get Health/Set Health와 인벤토리 복원 역시 이 프로젝트의 플레이어 데이터 함수이며 엔진이 이 이름의 모든 노드를 자동 생성하는 것은 아닙니다.
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 Value를Cast To BP_MySaveGame으로 캐스팅합니다.- 유효한 오브젝트와 캐스트 성공을 확인한 뒤 현재 상태를 업데이트합니다. 파일이 있어도 로드가 실패할 수 있으므로 실패 시 저장을 중단하고 오류를 알립니다. 기존 파일을 조용히 빈 데이터로 덮어쓰지 않습니다.
-
False(기존 세이브 파일 없음)Create SaveGame Object노드를 호출하여 새로운BP_MySaveGame오브젝트를 생성합니다.SaveGame Class핀에BP_MySaveGame을 선택합니다.Return Value를Cast To BP_MySaveGame으로 캐스팅합니다.
-
공통 로직 (새로 생성했든 로드했든)
- 유효한 SaveGame과
Get Player Pawn(0)의 플레이어 참조를 확인합니다. 현재 위치를Get Actor Location으로 읽어PlayerLocation에 저장합니다. - 플레이어의 현재 체력:
Get Player Pawn> (Cast to PlayerCharacter) >Get Health→BP_MySaveGame의PlayerHealth변수에 저장. - 저장할 다른 모든 데이터도 유사하게
BP_MySaveGame오브젝트의 해당 변수에 업데이트합니다. 현재 맵은Get Current Level Name으로 읽고 필요한 String→Name 변환 뒤CurrentLevelName에 기록합니다. PIE 접두사 제거 설정도 확인하여 다음 실행에 열 수 있는 맵 이름을 저장합니다. 이름 변수만 선언하고 값을 채우지 않은 상태로 다른 맵 로드를 시도하지 않습니다. - 최종적으로
Save Game To Slot노드를 호출합니다.SaveGame Object핀에 업데이트된BP_MySaveGame오브젝트를 연결합니다.Slot Name핀에Save Slot Name변수를 연결합니다.User Index는 보통0으로 설정합니다. (단일 유저 세이브)
- 저장 노드의 불리언 결과가 참일 때만 게임 저장 완료!를 출력합니다. 거짓이면 실패를 표시하며 체크포인트도 활성화하지 않습니다. 모든 저장·조회·로드 노드에서 슬롯명과 User Index를 같게 사용합니다.
- 유효한 SaveGame과
LoadGame)LoadGame이라는 커스텀 이벤트를 생성합니다. (UI 버튼 클릭 시 호출되도록)Does Save Game Exist노드를 사용하여 해당 슬롯에 세이브 파일이 있는지 확인합니다.False일 경우,Print String으로 저장된 게임이 없습니다! 메시지를 출력하고 함수 종료.
-
True일 경우Load Game From Slot노드를 호출하여BP_MySaveGame오브젝트를 로드합니다.Return Value를Cast To BP_MySaveGame으로 캐스팅합니다.- 로드에 실패하거나 캐스트가 실패하면 오류를 표시하고 종료합니다. 파일 존재는 데이터의 유효성을 보장하지 않습니다.
- 로드·캐스트가 성공한 SaveGame 참조를 먼저 프로젝트의 GameInstance
PendingSave에 보관합니다.ApplyPendingSave는 이 참조를 읽는 함수로 만듭니다. 같은 레벨과 다른 레벨 모두 이 대입을 마친 뒤에 진행합니다. - 저장된
CurrentLevelName이 다르면Open Level (by Name)을 호출합니다. 이전 GameMode는 레벨과 함께 교체되므로 그 뒤 실행선에서 새 플레이어를 복원하려 하지 않습니다. - 새 레벨의 GameMode가 Pawn 스폰을 마친 뒤 명시적인
ApplyPendingSave함수를 호출하도록 연결합니다. 같은 레벨이면 현재 Pawn이 준비됐는지 확인하고 이 복원 함수를 바로 호출합니다. - 복원 함수는 GameInstance의
PendingSave가 유효한지 확인하고 그 데이터를 읽습니다. 유효한 플레이어의Set Actor Location(PlayerLocation), 캐스트 후Set Health(PlayerHealth)를 적용하고, 저장용 ID로 월드의 수집 아이템·적 상태를 복원합니다. - 적용 완료 후 GameInstance의
PendingSave를 비우고 게임 불러오기 완료!를 표시합니다.Open Level전체에 자동 연결되는 범용On Level Loaded이벤트가 있다고 가정하지 않습니다.
레벨 이동 동안 유지할 저장 데이터
로드/캐스트 성공→GameInstance PendingSave 보관→다른 레벨 열기→새 Pawn 준비→적용→참조 비움의 구체적 상태이다.
LOCAL SAVE · WORLD TRANSITION
레벨을 열기 전에 저장 데이터를 GameInstance에 보관합니다
이 흐름은 로컬 싱글플레이에서 다른 레벨로 이동하는 경우입니다. 이전 GameMode는 사라지므로 새 GameMode가 Pawn 준비를 확인하고 보관한 데이터를 명시적으로 적용합니다.
실선은 실행 순서, 점선은 레벨 이동 동안 보관한 데이터의 전달입니다.
같은 레벨이면 이동을 생략합니다
같은 레벨도 로드·캐스트 성공 뒤 GameInstance의 PendingSave에 보관하는 단계는 공통입니다. Open Level만 생략하고 현재 Pawn과 적용 대상의 준비를 확인한 뒤 ApplyPendingSave가 보관한 데이터를 읽도록 호출합니다. 적용 완료 후 PendingSave를 비웁니다. 로드 실패·캐스트 실패 때는 적용하거나 기존 저장 파일을 덮어쓰지 않습니다.
새 월드에서 적용 완료를 확인합니다
체력·인벤토리·Transform을 적용한 뒤 PendingSave 참조를 비웁니다. Open Level 다음 실행 핀에 이전 월드의 적용 로직을 이어 붙이지 않습니다.
이 예의 조건
- 본문과 원 도표에 제시된 사례의 입력·조건을 사용합니다. 설명용 수치나 독립 비교 경로는 각 단계에 표시합니다.
- 현재 상태·값과 전환 근거의 위치는 모든 단계에서 같습니다. 조건이 맞지 않거나 실행되지 않은 경로를 성공한 동작으로 해석하지 않습니다.
관계도의 보충 설명
| 범위 | 원문 |
|---|---|
| 도표 제목 | 레벨 이동 동안 보관한 데이터와 실행 |
| 관계·조건 설명 | 실선은 로드 확인→GI 보관→OpenLevel→새 Pawn 준비→적용 순서다. 오른쪽 점선은 GI에 보관한 참조가 적용 함수에 전달되는 데이터 관계다. |
| 관찰·상황 | 값·보충 정보 | 변화·이유 |
|---|---|---|
| 로드 보관 Load와 Cast에 성공한 데이터를 GameInstance에 둔다. |
|
|
| 레벨·Pawn 준비 새 레벨을 열고 적용할 Pawn을 기다린다. |
|
|
| 적용·참조 정리 준비된 Pawn에 적용하고 PendingSave를 비운다. |
|
|
레벨 이동 동안 유지할 저장 데이터
로드/캐스트 성공→GameInstance PendingSave 보관→다른 레벨 열기→새 Pawn 준비→적용→참조 비움의 구체적 상태이다.
LOCAL SAVE · WORLD TRANSITION
레벨을 열기 전에 저장 데이터를 GameInstance에 보관합니다
이 흐름은 로컬 싱글플레이에서 다른 레벨로 이동하는 경우입니다. 이전 GameMode는 사라지므로 새 GameMode가 Pawn 준비를 확인하고 보관한 데이터를 명시적으로 적용합니다.
실선은 실행 순서, 점선은 레벨 이동 동안 보관한 데이터의 전달입니다.
같은 레벨이면 이동을 생략합니다
같은 레벨도 로드·캐스트 성공 뒤 GameInstance의 PendingSave에 보관하는 단계는 공통입니다. Open Level만 생략하고 현재 Pawn과 적용 대상의 준비를 확인한 뒤 ApplyPendingSave가 보관한 데이터를 읽도록 호출합니다. 적용 완료 후 PendingSave를 비웁니다. 로드 실패·캐스트 실패 때는 적용하거나 기존 저장 파일을 덮어쓰지 않습니다.
새 월드에서 적용 완료를 확인합니다
체력·인벤토리·Transform을 적용한 뒤 PendingSave 참조를 비웁니다. Open Level 다음 실행 핀에 이전 월드의 적용 로직을 이어 붙이지 않습니다.
이 예의 조건
- 본문과 원 도표에 제시된 사례의 입력·조건을 사용합니다. 설명용 수치나 독립 비교 경로는 각 단계에 표시합니다.
- 현재 상태·값과 전환 근거의 위치는 모든 단계에서 같습니다. 조건이 맞지 않거나 실행되지 않은 경로를 성공한 동작으로 해석하지 않습니다.
관계도의 보충 설명
| 범위 | 원문 |
|---|---|
| 도표 제목 | 레벨 이동 동안 보관한 데이터와 실행 |
| 관계·조건 설명 | 실선은 로드 확인→GI 보관→OpenLevel→새 Pawn 준비→적용 순서다. 오른쪽 점선은 GI에 보관한 참조가 적용 함수에 전달되는 데이터 관계다. |
| 관찰·상황 | 값·보충 정보 | 변화·이유 |
|---|---|---|
| 로드 보관 Load와 Cast에 성공한 데이터를 GameInstance에 둔다. |
|
|
| 레벨·Pawn 준비 새 레벨을 열고 적용할 Pawn을 기다린다. |
|
|
| 적용·참조 정리 준비된 Pawn에 적용하고 PendingSave를 비운다. |
|
|
UI 버튼에 연결 (선택 사항)
메인 메뉴나 인게임 메뉴의 세이브/로드 버튼에 위에서 구현한 SaveGame과 LoadGame 함수를 연결합니다.
- 버튼
On Clicked이벤트 →Get Game Mode→ 성공 분기를 확인한Cast To GM_MyGame→SaveGame또는LoadGame함수 호출.
세이브 시스템 설계 시 주요 고려사항
- 저장할 데이터의 범위: 모든 것을 저장할 필요는 없습니다. 다시 생성하거나 쉽게 유추할 수 있는 데이터는 저장하지 않아도 됩니다. (예: 일회성 이펙트, 단기 계산 변수)
- 복잡한 액터 저장: 저장용 ID, 클래스, Transform, 필요한 상태 값을 보관하고 복원합니다. 배치 액터에도 재실행·맵 수정 정책에 맞는 ID를 부여합니다. 표시 이름이나 런타임 고유 번호만으로 영구 식별을 가정하지 않습니다.
- 로드 시점: 월드와 Pawn이 준비된 뒤 적용해야 합니다. 여러 액터의 BeginPlay 순서가 우연히 맞기를 기대하지 말고, 스폰 완료 지점에서 복원 함수를 호출합니다.
- 성능: 너무 많은 데이터를 저장하거나, 너무 자주 저장하면 성능에 영향을 미칠 수 있습니다. 필요한 최소한의 데이터만 저장하고, 저장 빈도를 조절합니다.
- 데이터 버전 관리: 게임 업데이트 시 세이브 파일의 구조가 변경될 수 있습니다.
SaveGame클래스에SaveGameVersion(Int) 변수를 추가하여, 로드 시 버전 불일치를 감지하고 이전 버전 데이터를 변환하는 로직을 구현할 수 있습니다. - 보안: 일반 해시만으로 악의적 변조를 막을 수는 없습니다. 인증된 무결성 검증과 신뢰할 수 있는 키·서버 권한을 별도로 설계하며, 클라이언트의 로컬 파일을 완전히 신뢰하지 않습니다.
- UI 피드백: 저장/로드 진행 중임을 플레이어에게 알려주는 UI(예: 저장 중..., 로딩 바)를 제공하여 사용자 경험을 개선합니다.
체크포인트와 세이브 시스템은 플레이어가 게임을 지속적으로 즐길 수 있도록 하는 필수적인 기반 시스템입니다.
잘 설계된 세이브 시스템은 플레이어의 몰입도를 높이고 게임의 만족도를 향상시킵니다.
세이브/로드 실패 증상과 복구
-
증상: 저장 버튼을 눌러도 다음 실행에서 데이터가 사라짐
- 원인:
Save Slot Name불일치 또는Save Game To Slot미호출 - 복구: 저장/로드가 동일한 슬롯명을 쓰는지 확인하고, 저장 결과 불리언과 오류 경로를 기록해 성공 여부를 구분합니다.
- 원인:
-
증상: 로드 직후 플레이어 위치가 원점으로 이동
- 원인:
Open Level이후 복원 로직이 재실행되지 않음 - 복구: 레벨 전환이 필요한 경우, 새 레벨의 Pawn 생성 완료 지점에서
PendingSave의 위치를 적용합니다.
- 원인:
-
증상: 수집 아이템 상태가 복원되지 않음
- 원인: 액터 참조 자체를 저장하려고 함
- 복구: 액터 레퍼런스 대신 고유 ID 배열을 저장하고, 로드 시 ID 매칭으로 월드 상태를 재적용합니다.
이번 절에서는 언리얼 엔진에서 체크포인트와 세이브/로드 시스템을 설계하고 구현하는 기본 개념과 SaveGame 클래스 활용법에 대해 알아보았습니다.
다른 레벨 로드와 실패 경로 따라가기
Level A의 슬롯에는 Level B, 위치 (300, 200, 0) cm, 체력 60이 기록돼 있습니다. Load Game From Slot과 캐스트는 성공했습니다. 아직 Level B의 Pawn은 없습니다. 어느 객체에 데이터를 보관하고 언제 위치·체력을 적용할까요? 별개의 시도에서 슬롯 파일은 존재하지만 캐스트가 실패했다면 기본값으로 새 세이브를 만들어 같은 슬롯을 덮어써도 될까요?
데이터 보관·월드 준비·유효성 순서로 풀기
Level A가 교체되기 전에 지정한 GameInstance의 PendingSave에 로드 결과를 보관합니다. Level B의 GameMode가 새 Pawn 준비를 확인한 뒤 ApplyPendingSave를 호출해 위치 (300, 200, 0) cm와 체력 60을 적용하고, 성공한 적용 뒤 PendingSave를 비웁니다. 이전 GameMode의 Open Level 다음 실행선에서 새 Pawn을 이미 얻었다고 가정하지 않습니다. 캐스트 실패 시에는 적용·덮어쓰기를 중단하고 오류를 알립니다. 파일 존재, 로드 결과 유효성, 클래스 호환성은 각각 확인합니다.
게임의 진행 상황을 안정적으로 저장하고 불러오는 것은 플레이어에게 매우 중요한 기능입니다.