
유니티에서 소리 켜기/끄기나 최고 점수처럼 작은 값을 기기에 저장할 때는 PlayerPrefs 를 씁니다. SetInt · SetFloat · SetString 으로 저장하고, GetInt · GetFloat · GetString 으로 불러오며, 저장된 값이 없을 때 쓸 기본값을 두 번째 값으로 넘깁니다.
PlayerPrefs.SetInt("best_score", 120); // 저장
int best = PlayerPrefs.GetInt("best_score", 0); // 불러오기(없으면 0)
게임의 설정 화면에서 소리를 끄고 감도를 바꿨는데, 다음에 켰을 때 모두 처음 값으로 돌아가 있다면 사용자는 매번 다시 맞춰야 합니다. 이런 설정 값은 서버까지 갈 필요 없이 기기 안에 남겨 두면 됩니다. Unity 에서는 PlayerPrefs 가 이 일을 맡는데, 키(이름)와 값을 짝으로 저장하고 int · float · string 세 가지 타입만 받습니다. 이번 글에서는 소리(bool), 감도(float), 언어(string)를 저장했다가 다음 실행 때 불러오고, 초기화 버튼으로 지우는 설정 화면을 만들겠습니다. 처음 실행인지 알아보는 HasKey 와, 키 이름을 바꿀 때 옛 값을 옮기는 방법도 함께 보겠습니다.
C# (UNITY 3D)
처음 실행하면 저장된 키가 하나도 없어서 GetXxx 에 넘긴 기본값(켜짐 · 1 · 한국어)으로 시작합니다. 소리를 끄고 감도를 1.5, 언어를 English 로 바꾼 뒤 다시 실행하면 바꾼 값 그대로 시작하고, 초기화를 누르면 키가 지워져 기본값으로 돌아갑니다.
1. 준비 — 토글 · 슬라이더 · 드롭다운 · 버튼
Hierarchy 의 UI (Canvas) 메뉴에서 Toggle, Slider, Dropdown - TextMeshPro, Button - TextMeshPro, Text - TextMeshPro 를 하나씩 만듭니다. SettingsSave 는 설정 창 오브젝트에 붙이고 각 칸에 끌어다 넣습니다. 감도 범위와 언어 목록은 코드가 정하므로 인스펙터 값은 그대로 두어도 됩니다. TMP 기본 글꼴에는 한글 · 일본어가 없어서 「한국어」 「日本語」 가 □ 로 보이면, 한글 글꼴로 TMP 글꼴 에셋(Font Asset)을 만들어 드롭다운과 글자에 넣습니다.
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using TMPro;
List 를 쓰기 위해 System.Collections.Generic 을, Toggle · Slider · Button 을 쓰기 위해 UnityEngine.UI 를, TMP_Dropdown · TMP_Text 를 쓰기 위해 TMPro 를 사용합니다. PlayerPrefs 는 UnityEngine 에 들어 있습니다.
2. 설정 저장 코드
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using TMPro;
//CodeFinder 코드파인더
//From https://codefinder.janndk.com/
public class SettingsSave : MonoBehaviour
{
public Toggle toggle_sound; // 소리 켜기/끄기 → int (1/0)
public Slider slider_sensitivity; // 감도 0.5~2 → float
public TMP_Dropdown dropdown_language; // 언어 → string ("ko", "en", "ja")
public Button button_reset; // 설정 초기화
public TMP_Text text_saved; // 지금 저장된 값 보기
const string KEY_SOUND = "settings.sound";
const string KEY_SENSITIVITY = "settings.sensitivity";
const string KEY_LANGUAGE = "settings.language";
readonly string[] language_codes = { "ko", "en", "ja" };
void Start()
{
if (toggle_sound == null || slider_sensitivity == null || dropdown_language == null)
return;
Init_UI();
Load_Settings();
toggle_sound.onValueChanged.AddListener(Change_Sound);
slider_sensitivity.onValueChanged.AddListener(Change_Sensitivity);
dropdown_language.onValueChanged.AddListener(Change_Language);
if (button_reset != null)
button_reset.onClick.AddListener(Reset_Settings);
}
void Init_UI()
{
slider_sensitivity.minValue = 0.5f;
slider_sensitivity.maxValue = 2f;
dropdown_language.ClearOptions();
dropdown_language.AddOptions(new List<string> { "한국어", "English", "日本語" });
}
void Load_Settings()
{
bool sound = PlayerPrefs.GetInt(KEY_SOUND, 1) == 1; // 저장된 게 없으면 켜짐
float sensitivity = PlayerPrefs.GetFloat(KEY_SENSITIVITY, 1f); // 없으면 1
string language = PlayerPrefs.GetString(KEY_LANGUAGE, "ko"); // 없으면 한국어
toggle_sound.SetIsOnWithoutNotify(sound); // 불러올 때는 이벤트 없이
slider_sensitivity.SetValueWithoutNotify(sensitivity);
dropdown_language.SetValueWithoutNotify(Mathf.Max(0, System.Array.IndexOf(language_codes, language)));
Show_Saved();
}
void Change_Sound(bool is_on)
{
PlayerPrefs.SetInt(KEY_SOUND, is_on ? 1 : 0); // bool 은 1/0 으로
Save_Now();
}
void Change_Sensitivity(float value)
{
PlayerPrefs.SetFloat(KEY_SENSITIVITY, value); // 드래그하는 동안에는 Save 를 부르지 않음
Show_Saved();
}
void Change_Language(int index)
{
PlayerPrefs.SetString(KEY_LANGUAGE, language_codes[index]);
Save_Now();
}
void Reset_Settings()
{
PlayerPrefs.DeleteKey(KEY_SOUND);
PlayerPrefs.DeleteKey(KEY_SENSITIVITY);
PlayerPrefs.DeleteKey(KEY_LANGUAGE);
Save_Now();
Load_Settings(); // 기본값으로 돌아감
}
void Save_Now()
{
PlayerPrefs.Save(); // 디스크에 바로 씀
Show_Saved();
}
void OnDisable()
{
PlayerPrefs.Save(); // 설정 창을 닫을 때 감도까지
}
void Show_Saved()
{
if (text_saved == null)
return;
text_saved.text =
"sound = " + (PlayerPrefs.HasKey(KEY_SOUND) ? PlayerPrefs.GetInt(KEY_SOUND).ToString() : "(없음)") + "\n" +
"sensitivity = " + (PlayerPrefs.HasKey(KEY_SENSITIVITY) ? PlayerPrefs.GetFloat(KEY_SENSITIVITY).ToString("0.00") : "(없음)") + "\n" +
"language = " + (PlayerPrefs.HasKey(KEY_LANGUAGE) ? PlayerPrefs.GetString(KEY_LANGUAGE) : "(없음)");
}
}
PlayerPrefs 에는 bool 이 없어서 소리는 켜짐 1, 꺼짐 0 으로 저장하고 불러올 때 == 1 로 되돌립니다. 언어는 드롭다운 번호 대신 "ko" 같은 코드로 저장해 두면, 나중에 목록 순서가 바뀌어도 저장값이 엉뚱한 언어를 가리키지 않습니다. 감도 슬라이더는 드래그하는 동안 값이 계속 바뀌므로 SetFloat 만 하고, Save 는 설정 창이 닫힐 때(OnDisable) 한 번 부릅니다.
이 스크립트는 Unity 6000.6.3f1 에서 컴파일 오류 없이 들어갔고, 배치 모드에서 토글 · 슬라이더 · 드롭다운을 만들어 붙여 보니 처음에는 세 키가 모두 없음, 소리를 끄고 감도 1.5 · English 를 고른 뒤 새로 만든 설정 창은 꺼짐 · 1.5 · English 로 시작했습니다. 초기화 뒤에는 세 키가 지워지고 기본값으로 돌아갔습니다. Game 창에서 손으로 눌러 본 것은 아닙니다.
변수(variable)
toggle_sound(Toggle) : 소리 켜기/끄기
slider_sensitivity(Slider) : 감도 0.5~2
dropdown_language(TMP_Dropdown) : 언어 고르기
button_reset(Button) : 설정 초기화
text_saved(TMP_Text) : 지금 저장된 값을 보여 줄 글자
KEY_SOUND · KEY_SENSITIVITY · KEY_LANGUAGE(string) : PlayerPrefs 에 저장할 이름
language_codes(string[]) : 드롭다운 번호에 맞는 언어 코드
함수(function)
Init_UI : 감도 범위와 언어 목록을 정합니다.
Load_Settings : 저장된 값(없으면 기본값)을 불러와 UI 에 이벤트 없이 넣습니다.
Change_Sound · Change_Sensitivity · Change_Language : 값이 바뀔 때 불려 PlayerPrefs 에 저장합니다.
Reset_Settings : 세 키를 지우고 기본값으로 다시 불러옵니다.
Save_Now : 디스크에 바로 쓰고 표시를 고칩니다.
Show_Saved : 키가 있으면 값을, 없으면 (없음)을 보여 줍니다.
3. 처음 실행인지 알아보기 — HasKey
if (!PlayerPrefs.HasKey("first_run_done"))
{
Debug.Log("처음 실행 — 안내 화면 보여 주기");
PlayerPrefs.SetInt("first_run_done", 1);
PlayerPrefs.Save();
}
HasKey 는 그 이름으로 저장된 값이 있으면 true 를 돌려줍니다. 처음 실행할 때만 안내 화면을 띄우거나 기본 목록을 넣을 때 이렇게 표시용 키를 하나 저장해 둡니다. 첫 번째에는 true(처음), 두 번째에는 false 가 나옵니다.
4. 키 이름을 바꿀 때 — 옛 값 옮기기
if (!PlayerPrefs.HasKey("settings.sound") && PlayerPrefs.HasKey("sound"))
{
PlayerPrefs.SetInt("settings.sound", PlayerPrefs.GetInt("sound"));
PlayerPrefs.DeleteKey("sound"); // 옛 키는 지움
PlayerPrefs.Save();
}
업데이트하면서 키 이름을 "sound" 에서 "settings.sound" 로 바꾸면, 이미 게임을 하던 사람은 새 키가 없어서 설정이 처음 값으로 돌아갑니다. 새 키가 아직 없고 옛 키만 있을 때 값을 옮기고 옛 키를 지우면 됩니다. 새 키가 생긴 뒤에는 조건이 false 가 되어 다시 옮기지 않습니다.
5. 저장했는데 값이 안 나올 때 — 자주 하는 실수
PlayerPrefs.SetFloat("speed", 1.5f);
int speed_wrong = PlayerPrefs.GetInt("speed", 7); // 1 이 아니라 기본값 7

저장할 때와 다른 타입으로 읽었다
SetFloat 로 저장한 키를 GetInt 로 읽으면 1 로 바뀌어 나오지 않고 기본값이 나옵니다. 1.5 를 저장한 키에서 GetInt("speed", 7) 은 7 이었습니다. 같은 키에 SetInt 뒤 SetString 을 하면 마지막에 넣은 문자열로 덮입니다. 키마다 타입을 하나로 정하고, 위 코드처럼 키 이름을 const 로 모아 두면 이런 실수가 줄어듭니다.
대소문자가 다른 키
"settings.Sound" 와 "settings.sound" 는 서로 다른 키입니다. 저장한 키와 읽는 키의 글자가 하나라도 다르면 기본값만 계속 나옵니다.
Save 를 너무 자주 부른다
Unity 는 앱이 정상 종료될 때(OnApplicationQuit) PlayerPrefs 를 저장하고, 문서는 디스크에 쓰는 동안 끊김이 생길 수 있으니 게임 도중에는 Save 를 부르지 말라고 권합니다. 설정 창은 게임이 멈춘 화면이라 2절은 토글 · 드롭다운처럼 가끔 바뀌는 값만 바로 Save 하고, 슬라이더는 창을 닫을 때 저장합니다. 휴대폰에서는 앱이 정상 종료되지 않고 꺼지는 일도 있어서, 앱이 뒤로 갈 때 저장하는 방법은 따로 다루겠습니다.
저장되는 곳과 보안
Windows 에서는 레지스트리 HKEY_CURRENT_USER\Software\회사 이름\제품 이름 아래에, 안드로이드에서는 /data/data/패키지 이름/shared_prefs/패키지 이름.v2.playerprefs.xml 에 저장됩니다. 암호화되지 않으므로 비밀번호나 결제 정보 같은 민감한 값은 넣지 않습니다.
함께 보기: 저장한 볼륨을 슬라이더에 이벤트 없이 불러오는 예제는 유니티 Slider onValueChanged 로 값 표시 · 정수 단계 · 저장값 불러오기 에 있습니다.
PlayerPrefs 동작은 Unity 공식 문서 PlayerPrefs · PlayerPrefs.Save · PlayerPrefs.GetInt 과 Unity 6000.6.3f1 에디터(Windows)에서 직접 넣어 본 값을 기준으로 적었습니다.
4절처럼 옛 키가 있을 때만 새 키로 옮기고 DeleteKey 로 지우는 방식은 제가 잰드케이로 낸 랜덤 결정 앱 RandomGO 에서 설정 저장 형식을 바꿀 때 실제로 쓰고 있습니다(스토어 개발사 표시는 Jakadela Games).