취미로 게임을 하나 만들고 있다.
세이브 파일에 대한 경로를 관리해야 하는데, 읽기 쓰기가 가능한 Application.persistentDataPath를 사용하였다.
처음 프롤로그 씬을 불러올 때 세이브파일의 존재 유무에 따른 조건이 필요해서 씬을 불러오는 역할을 하는 함수에서 세이브파일 경로를 직접 지정해주었었다.
하지만 파일 경로와 같은 것들은 게임의 전반적인 세팅이나 상태를 관리하는 GameManager만 알고 있어야 한다고 생각해서 GameManager 내부에서 세이브파일 경로를 프로퍼티로 관리하고 필요한 경우 외부에서 접근 가능하도록 하면 어떨까 했다.
이 과정에서 알게된 Application.persistentDataPath의 특성에 대해 살펴보고자 한다.
GamaManager에서 프로퍼티 변수로 다음과 같이 세이브 파일 경로를 정의했다.
pubilc static string SaveFilePath = Application.persistentDataPath + "/savefile.dat";
그러고나서 게임을 실행해보자 다음과 같은 오류를 만났다.
UnityException: get_persistentDataPath is not allowed to be called from a MonoBehaviour constructor (or instance field initializer), call it in Awake or Start instead. Called from MonoBehaviour 'GameManager' on game object 'GameManager'. See "Script Serialization" page in the Unity Manual for further details. GameManager..cctor () (at Assets/Scripts/GameManager.cs:10) Rethrow as TypeInitializationException: The type initializer for 'GameManager' threw an exception.
필드 변수에서 초기화할 때 Application.persistentDataPath를 호출하도록 허용되지 않았다는 것이 오류의 골자다.
대신에 Awake나 Start 함수 내부에서 초기화하라는 문구가 있다.
왜 이런 일이 발생했을까?
공식 문서를 살펴보자
https://docs.unity3d.com/ScriptReference/Application-persistentDataPath.html
Unity - Scripting API: Application.persistentDataPath
This value is a directory path (notice the difference in slash directions on different operating systems according to Path.DirectorySeparatorChar), where you can store data that you want to be kept between runs. When you publish on iOS and Android, persist
docs.unity3d.com
persistentDataPath의 값이 플랫폼에 따라 다르며, 일부 플랫폼에서는 런타임에 결정된다고 한다.
특히 iOS와 Android 같은 모바일 플랫폼에서 persistentDataPath는 앱의 샌드박스 환경 내에 위치하며, 이는 앱이 실행될 때 결정된다.
따라서 이런 사실로부터 persistentDataPath는 런타임에 결정된다는 사실을 유추할 수 있다.
Application.persistentDataPath와 같은 API는 실행 플랫폼에 따라 다른 값을 리턴한다.
이 값은 컴파일 시점에 알 수 없는 것이 당연하므로 런타임에 결정되는 것이 당연해보인다.
또한 Unity의 생명주기를 살펴보면 Awake나 Start 함수는 초기화 단계에 속한다.
다시 말해 확실하게 런타임 환경이라고 할 만한 지점에서 호출을 허용하도록 강제하고 있는 것이라고 느껴졌다.
다음과 같이 개선하여 문제를 해결하였다.
public class GameManager : MonoBehaviour
{
private string _saveFilePath;
public string SaveFilePath
{
get
{
if (string.IsNullOrEmpty(_saveFilePath))
{
_saveFilePath = Path.Combine(Application.persistentDataPath, "savefile.dat");
}
return _saveFilePath;
}
}
}
기본적으로는 private 지시자로 GameManager 내부에서만 관리하다가 필요한 경우 SaveFilePath를 통해 외부에서 접근이 가능하도록 하였다.
또한 SaveFilePath 프로퍼티는 처음 접근할 때만 경로를 계산하고, 이후에는 저장된 값을 반환하도록 하여 성능상 이점을 챙겼다.
경로가 한 곳에서 관리되므로, 변경이 필요할 때 쉽게 수정할 수 있는 것도 장점이다.
이로써 처음 목적했던 바를 이루었다.