반응형
유니티 제공 PlayerPrefs 한계
유니티에서 기본 제공하는 PlayerPrefs 클래스는 간단하게 사용이 가능하지만, 대체적으로 사용하는데 아래와 같은 불편함이 있습니다.
- 한정된 클래스 타입으로 string, int, float만 제공합니다.
- 보안에 취약합니다. 해킹 이슈에 노출되어있습니다.
- 파일 로드의 개념이지만 캐싱을 따로 하고 있지 않습니다.
위와 같은 문제를 해결하려면 다른 방법이 필요하기에 PlayerPrefs를 글로벌 클래스로 수정하여 사용하는 방식입니다. 근본적으로는 PlayerPrefs의 기능을 쓰지만, 다양한 타입에 사용이 가능하고, Serialize 및 base64 알고리즘를 통해서 보안을 좀 더 강화했습니다. 알고리즘을 추가하여 본인이 원하면 수정도 가능합니다. 또한, 내부적으로 Dictionary를 통해 관리를 하고 씬 전환 때마다 메모리를 정리하도록 하였습니다.
스크립트
PlayerPrefs.cs
using System;
using System.IO;
using System.Collections.Generic;
/// <summary>
/// 파일 저장 클래스
/// </summary>
public static class PlayerPrefs // UnityEngine.PlayerPrefs Overriding
{
/// <summary>
/// Dictionary 관리
/// </summary>
private static Dictionary<string, object> memory = new();
static PlayerPrefs()
{
// 씬이 전환될 때마다 메모리 정리 이벤트 발생
UnityEngine.SceneManagement.SceneManager.sceneLoaded += (scene, mode) => Release();
}
/// <summary>
/// 데이터 불러오기
/// </summary>
/// <typeparam name="T">데이터 타입</typeparam>
/// <param name="name">이름</param>
/// <returns>데이터 반환</returns>
public static T Load<T>(string name, T defaultValue = default)
{
if (!RegisterKey(name)) Save(name, defaultValue);
if (!memory.TryGetValue(name, out var data))
{
data = FileLoad<T>(name);
memory.Add(name, data);
}
return (T)data;
}
/// <summary>
/// 데이터 저장하기
/// </summary>
/// <typeparam name="T">데이터 타입</typeparam>
/// <param name="name">이름</param>
/// <param name="data">데이터</param>
public static void Save<T>(string name, T data)
{
if (!memory.ContainsKey(name)) memory.Add(name, null);
memory[name] = data;
FileSave(name, data);
}
/// <summary>
/// 메모리 정리
/// </summary>
public static void Release() => memory.Clear();
/// <summary>
/// 키 등록 여부
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
private static bool RegisterKey(string key) => HasKey(key);
#region ## File I/O ##
/// <summary>
/// 파일 데이터 불러오기
/// </summary>
/// <typeparam name="T">데이터 타입</typeparam>
/// <param name="name">이름</param>
/// <returns>데이터 반환</returns>
private static T FileLoad<T>(string name)
{
if (UnityEngine.PlayerPrefs.HasKey(name))
{
var loadData = UnityEngine.PlayerPrefs.GetString(name);
if (loadData != null)
{
byte[] bytes = Convert.FromBase64String(loadData);
using (var ms = new MemoryStream(bytes))
{
object obj = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter().Deserialize(ms);
return (T)obj;
}
}
}
return default;
}
/// <summary>
/// 파일 데이터 저장
/// </summary>
/// <typeparam name="T">데이터 타입</typeparam>
/// <param name="name">이름</param>
/// <param name="data">데이터</param>
private static void FileSave<T>(string name, T data)
{
if (data != null)
{
using (var ms = new MemoryStream())
{
new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter().Serialize(ms, data);
UnityEngine.PlayerPrefs.SetString(name, Convert.ToBase64String(ms.ToArray()));
}
}
else
{
UnityEngine.PlayerPrefs.SetString(name, null);
}
UnityEngine.PlayerPrefs.Save();
}
#endregion
#region ## PlayerPrefs original method ##
public static void SetInt(string key, int value) => Save(key, value);
public static int GetInt(string key, int defaultValue = 0) => Load(key, defaultValue);
public static void SetFloat(string key, float value) => Save(key, value);
public static float GetFloat(string key, float defaultValue = 0f) => Load(key, defaultValue);
public static void SetString(string key, string value) => Save(key, value);
public static string GetString(string key, string defaultValue = "") => Load(key, defaultValue);
public static bool HasKey(string key) => UnityEngine.PlayerPrefs.HasKey(key);
public static void DeleteKey(string key)
{
UnityEngine.PlayerPrefs.DeleteKey(key);
memory.Remove(key);
}
public static void DeleteAll()
{
UnityEngine.PlayerPrefs.DeleteAll();
memory.Clear();
}
public static void Save() => UnityEngine.PlayerPrefs.Save();
#endregion
}
글로벌 클래스로 제작되었기 때문에 UnityEngine.PlayerPrefs는 가려집니다.
예제
private void Start()
{
List<int> list = new List<int>();
// 저장
PlayerPrefs.Save("Numbers", list);
// 불러오기
List<int> newList = PlayerPrefs.Load<List<int>>("Numbers");
}
이렇게 한정된 string, int, float 타입 말고 다양한 형태의 저장이 가능합니다.
마무리
PlayerPrefs의 순수 기능만으로는 저장하여 사용하는 것에는 한계가 있어, 커스텀으로 수정했습니다. 다양한 클래스를 사용하게 되고 이에 따라 저장이 필요할 때 사용하면 좋습니다.
참고 : https://forum.unity.com/threads/c-serialization-playerprefs-mystery.72156/
반응형
'Utils' 카테고리의 다른 글
C#, 클래스 이름을 입력해서 클래스에 접근하기 (0) | 2023.08.05 |
---|---|
C#, 폴더 및 파일 전체 복사 (0) | 2023.07.04 |
Unity, 에셋 FileID 가져오기 (0) | 2023.05.14 |
Unity, 박스 콜라이더 기즈모 (0) | 2023.05.11 |
Unity, Transform 인스펙터 커스텀 (0) | 2023.05.09 |
댓글