재시작과 최고 점수: PlayerPrefs로 로컬 기록 저장하기
16강. 재시작과 최고 점수: PlayerPrefs로 로컬 기록 저장하기
1. 이번 강의에서 해결할 문제
게임 오버 뒤 새 Run을 시작하면 이전 택배·점수·구독이 남거나 최고 점수가 사라집니다. 민감하지 않은 최고 점수만 로컬에 저장하고 Scene 상태를 초기화합니다.
2. 학습 목표
3. 핵심 개념
PlayerPrefs는 간단한 로컬 설정·기록을 저장하지만 암호화된 보안 저장소가 아닙니다. 최고 점수와 음량에는 사용할 수 있지만 비밀번호, 토큰, 결제·개인정보는 절대 저장하지 않습니다. 잦은 Save()는 플레이 중 끊김을 만들 수 있어 결과가 확정된 시점에 호출합니다.
먼저 알아야 할 저장·재시작 용어
| 용어 | 의미 | 주의할 점 |
|---|---|---|
| PlayerPrefs | 문자열 Key로 int·float·string을 저장하는 플랫폼별 로컬 저장소 | 사용자가 수정하거나 지울 수 있으므로 보안·권위 데이터에 쓰지 않습니다. |
| Key | 저장 값을 다시 찾는 고유 문자열 | 이름을 바꾸면 이전 값이 자동 이전되지 않습니다. |
SetInt | 메모리의 PlayerPrefs 값에 정수를 기록 | 영구 반영 시점은 플랫폼과 종료 상황을 고려해 Save 정책을 정합니다. |
Save | 변경된 PlayerPrefs를 저장소에 기록하도록 요청 | 매 프레임 호출하지 않고 결과 확정 시점에 호출합니다. |
| Scene reload | 현재 Scene 에셋을 다시 로드해 Scene 소유 객체를 새로 만드는 재시작 | DontDestroyOnLoad 객체와 정적 값은 자동 초기화되지 않습니다. |
PlayerPrefs는 브라우저·클라우드 동기화나 서버 계정 저장이 아닙니다. 저장 위치는 운영체제·플랫폼마다 다르며 앱 삭제, 사용자 초기화, 저장 손상으로 사라질 수 있습니다. 따라서 최고 점수처럼 잃어도 복구 가능한 작은 편의 기록에만 사용합니다.
Scene을 다시 로드하면 Scene에 속한 GameObject는 파괴되고 새 인스턴스가 만들어집니다. 하지만 DontDestroyOnLoad, static 필드, 외부 파일과 PlayerPrefs는 남을 수 있습니다. “재로드하면 모든 상태가 무조건 초기화된다”고 가정하지 말고 각 상태의 소유 위치를 확인합니다.
4. 단계별 실습
C# · 파일 경로: Assets/_Project/Scripts/Core/HighScoreStore.cs
using UnityEngine;
namespace DeliverySorter.Core
{
public sealed class HighScoreStore : MonoBehaviour
{
private const string Key = "delivery-sorter-high-score-v1";
public int Value { get; private set; }
private void Awake() => Value = PlayerPrefs.GetInt(Key, 0);
public bool SaveIfHigher(int score)
{
if (score <= Value) return false;
Value = score;
PlayerPrefs.SetInt(Key, Value);
PlayerPrefs.Save();
return true;
}
}
}
HighScoreStore 코드 한 줄씩 이해하기
const string Key는 저장·조회에 같은 철자를 쓰게 합니다.v1은 향후 값 의미가 바뀔 때 새 Key로 구분할 여지를 줍니다.Value는 현재 실행에서 읽은 최고 점수 복사본이며 외부에서는 읽기만 가능합니다.Awake의GetInt(Key, 0)은 저장 값이 있으면 읽고, 처음 실행이면 기본값 0을 사용합니다.- 새 점수가
Value보다 작거나 같으면 저장하지 않고false를 반환합니다. - 더 높으면 메모리 값 → PlayerPrefs 값 → 저장소 반영 순으로 갱신하고
true를 반환합니다. - bool 반환값을 이용하면 “신기록!” UI와 사운드는 실제 최고 점수가 바뀐 경우에만 표시할 수 있습니다.
C# · GameFlowController.cs에 추가
using UnityEngine.SceneManagement;
[SerializeField] private HighScoreStore highScoreStore;
private void GameOver()
{
highScoreStore.SaveIfHigher(session.Score);
input.SetInputEnabled(false);
spawner.Stop();
Time.timeScale = 1f;
SetState(GameState.GameOver);
}
public void RestartRun()
{
Time.timeScale = 1f;
SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex);
}
GameOver와 재시작 흐름
RunEnded
→ 현재 점수와 최고 점수 비교
→ 신기록일 때만 PlayerPrefs 저장
→ 입력·Spawner 중지
→ Time Scale을 1로 복원
→ GameOver 화면 표시
→ Restart 버튼
→ 현재 Scene 다시 로드
→ HighScoreStore.Awake에서 저장값 다시 읽기
SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex)를 사용하려면 현재 Scene이 Build Settings 또는 사용 중인 Unity 버전의 Build Profiles Scene 목록에 포함돼 유효한 buildIndex를 가져야 합니다. Editor에서 열려 있기만 하고 빌드 목록에 없으면 재로드가 실패할 수 있습니다.
Unity Editor에서 연결하고 가장 작은 저장 확인
- Hierarchy에
HighScoreStoreGameObject를 만들고 HighScoreStore Component를 붙입니다. - GameFlowController의 High Score Store 슬롯에 연결합니다.
- GameOverPanel의 Restart Button OnClick에
GameFlowController.RestartRun을 연결합니다. - 최고 점수를 표시하는 TMP_Text는 UI Presenter가 Store의 Value를 읽어 표시하게 합니다. TMP_Text가 직접 PlayerPrefs Key를 알게 하지 않습니다.
- 현재 Game Scene이 빌드 Scene 목록에 포함됐는지 확인합니다.
- 100점으로 종료한 뒤 Restart하고, HighScoreStore의 Value 또는 UI가 100으로 다시 로드되는지 확인합니다.
테스트용 값을 초기화할 때는 개발 중에만 PlayerPrefs.DeleteKey("delivery-sorter-high-score-v1")를 한 번 실행하고 즉시 제거합니다. DeleteAll()은 음량 등 다른 설정도 지울 수 있으므로 이 실습에 사용하지 않습니다.
5. 코드가 동작하는 이유
결과가 기존 값보다 높을 때만 쓰고 Scene을 다시 로드해 Scene 소유 런타임 상태와 이벤트 구독을 새로 만듭니다. 저장 키에 버전을 넣어 포맷 변경 시 구분할 수 있습니다.
6. 자주 하는 실수와 해결법
7. 직접 실습
실습 목표
낮은 점수는 기존 최고 기록을 덮지 않고, 높은 점수만 저장되며, Scene 재로드 뒤 기록과 런타임 상태가 각각 올바르게 복원·초기화되는지 확인합니다.
시작 전 상태
테스트 Key 하나만 삭제해 최고 점수를 0으로 만들고 Game Scene을 다시 시작합니다. 점수·목숨·생성 중인 택배도 초기 상태인지 기록합니다.
1단계: 따라 하기
100점으로 GameOver를 만들고 Restart합니다. 재로드 뒤 최고 점수는 100, 현재 Run 점수는 0이어야 합니다. 저장되는 값과 초기화되는 값의 소유자가 다름을 확인합니다.
2단계: 값 바꿔 보기
50점으로 종료하면 SaveIfHigher가 false이고 최고 점수는 100을 유지해야 합니다. 다음 Run에서 200점으로 종료하면 true가 되고 재시작 뒤 200이 보여야 합니다.
| 종료 점수 | 저장 전 최고 | 반환값 | 저장 후 최고 |
|---|---|---|---|
| 100 | 0 | true | 100 |
| 50 | 100 | false | 100 |
| 200 | 100 | true | 200 |
3단계: 직접 적용
SaveIfHigher의 반환값을 받아 신기록일 때만 NEW HIGH SCORE 표시가 켜지도록 UI Presenter에 전달하세요. Store가 직접 TMP_Text나 Panel을 참조하지 않게 이벤트 또는 Flow의 결과 전달을 사용합니다.
4단계: 스스로 확인
막혔을 때
| 증상 | 원인 | 해결 |
|---|---|---|
| 재시작 뒤 최고 점수가 0 | Store가 Scene에 없거나 Key 철자가 다름 | Awake 로그로 Key와 읽은 Value를 확인합니다. |
| Restart 버튼에서 Scene 로드 오류 | 현재 Scene이 빌드 목록에 없음 | Build Settings 또는 Build Profiles에 Scene을 추가합니다. |
| 재시작 뒤 게임이 계속 멈춤 | Time Scale이 0인 채 재로드 | LoadScene 전에 Time.timeScale = 1f를 실행합니다. |
| 50점이 100점을 덮음 | 비교 조건이 반대이거나 항상 SetInt 호출 | score <= Value에서 즉시 false로 반환하는지 확인합니다. |
| 테스트 초기화로 음량도 사라짐 | DeleteAll 사용 | 학습용 최고 점수 Key만 DeleteKey로 지웁니다. |
8. 이해 점검 질문 3개
9. 핵심 요약
다음 강의 연결
다음 강의: Object Pool과 성능에서는 Scene 재시작뿐 아니라 한 Run 안에서 반복 생성·파괴되는 택배 비용도 줄이고 측정으로 개선 효과를 확인합니다.
재시작과 최고 점수: PlayerPrefs로 로컬 기록 저장하기 미니 퀴즈
선택 즉시 정답과 해설을 확인할 수 있습니다. 결과는 이 브라우저에만 저장됩니다.
학습을 마쳤나요?
직접 실습과 점검 질문까지 확인한 뒤 완료로 표시하세요.