Araina’s Blog

Manage scene flow and data: Implement data persistence between sessions 본문

Unity Learn 번역/Pathway: Junior Programmer

Manage scene flow and data: Implement data persistence between sessions

Araina 2022. 1. 5. 18:47

출처

 

Implement data persistence between sessions - Unity Learn

In this tutorial, you’ll write code to save and load the color that the user selects so that it persists between sessions of the application. By the end of this tutorial, you will be able to: Call the appropriate start-up methods in the initialization se

learn.unity.com


 

 

1. 서언

현재 유니티 에디터에서 작업 중이신 애플리케이션을 테스트하실 때, 여러분이 선택한 창고의 지게차 색상이 다음번에 애플리케이션을 테스트할 때는 적용되지 않습니다. 만약 여러분이 이 앱의 빌드를 생성하시면, 사용자가 실행할 때마다 매번 독립적인 실행 환경이 될 것입니다.

이는 사용자가 선택한 마지막 색상을 저장할 수 있도록 해서 애플리케이션을 시작했을 때, 자동으로 색상을 선택할 수 있도록 만들어줄 것입니다. 이번 튜토리얼에서는, 씬 사이의 데이터 지속성을 구현하기 위해 이전에 작업하셨던 것을 기반으로 세션 사이의 데이터 지속성을 구현해보겠습니다.

작업이 완료되면, 아래 영상과 같이 동작하게 될 것입니다:


(영상: 링크 참조)

 

Implement data persistence between sessions - Unity Learn

In this tutorial, you’ll write code to save and load the color that the user selects so that it persists between sessions of the application. By the end of this tutorial, you will be able to: Call the appropriate start-up methods in the initialization se

learn.unity.com


팁: 만약 이전 튜토리얼에서 이번 튜토리얼로 넘어가기 전에 공백기가 있으셨다면, 계속 진행하시기에 앞서 introduction to data persistence 문서를 다시 살펴보시는 것이 도움이 되실 겁니다.

 


 

2. 할 일 검토

이번 튜토리얼에서는 사용자가 선택한 색상을 파일에 쓸 것입니다. 그런 다음, 애플리케이션이 시작될 때 파일이 존재하는 지를 검사하도록 MainManager를 구성할 것입니다. 만약 파일이 존재한다면, MainManager가 파일에 저장된 색상을 읽고, 색상 선택기가 해당 색상을 선택할 것입니다.

시작하기에 앞서, 잠시 시간을 내어 프로젝트의 할 일에 대해 살펴봅시다. 여러분의 힘으로 이 작업을 직접 수행한다고 상상해보세요. 아래의 질문에 대한 답을 적어봅시다:

● 무엇을 구현해야 하고, 어디서 구현해야 하나요? 지금 생각하시는 걸 적어보고, 저희가 취한 접근 방식과 한 번 비교해보세요.

● Create with Code 또는 다른 학습 과정에서 이와 비슷한 기능을 경험해보신 적이 있으신가요? 여러분이 코드에 접근할 수는 없더라도, 여러분들에게 익숙한 세션들 사이의 데이터 지속성의 예제에 대해 한 번 생각해보세요.

● Unity Learn 외에, 이번 작업에 도움이 될만한 자료들은 무엇이 있을까요? 유니티 생태계 내부와 외부의 옵션들에 대해 생각해보세요.

작업을 시작하기에 앞서, 잠시 시간을 내어 여러분이 이미 알고 있는 것이 무엇인지 점검하는 것은, 특히 해야 할 작업이 어렵다고 느껴질 때 매우 유용한 경험이 될 수 있습니다. 만약 여러분이 Unity Essentials 과정을 완료하셨다면, 크리에이터들이 유니티를 배우기 위해 갖춰야 하는 확립된 마인드셋(mindset)에 대해 배우셨을 것입니다. 이번 시간은 그때 배우신 접근법에 대한 기억을 상기시킬 수 있는 좋은 기회가 될 것입니다.

여러분이 자유롭게 다룰 수 있는 도구들을 식별하고, 이전에 배운 내용을 다시 살펴보는 것은 개요에 적합한 다양한 접근 방식들을 생각해볼 수 있는 매우 유용한 기회를 제공할 수 있습니다. 이번 패스웨이를 훨씬 넘어서서 여러분의 학습 여정을 계속하시는 동안 이러한 접근을 취하실 수 있습니다. - 어떠한 일을 하는 방법이 하나뿐인 경우는 매우 드뭅니다!

 


 

3. 어떻게 세션 간에 데이터가 지속되나요?

세션 간에 데이터를 지속하도록 만들기 위해서는 다른 곳에 데이터를 저장해둬야 합니다. 여러분의 경우, 사용자가 선택한 색상 데이터를 저장할 수 있는 포맷으로 변환하고, 애플리케이션이 다시 실행되었을 때, 이를 읽어드릴 수 있도록 만들어야 합니다.

복잡한 데이터를 저장할 수 있는 포맷으로 변환하는 과정을 직렬화(Serialization)라고 부릅니다. 데이터에 다시 접근할 준비가 되었을 때, 저장된 데이터를 다시 변환하는 과정을 역질렬화(deserialization)라고 부릅니다.

저장하려는 데이터가 어떤 데이터인지, 그리고 여러분이 그 데이터를 가지고 어떤 작업을 하고 싶으신지에 따라 데이터 저장에 사용할 수 있는 다양한 포맷들이 존재합니다. 여기서는 JSON 포맷을 사용할 것입니다.

 


 

4. JSON이란?

JSON이란, 데이터를 저장하고 플랫폼들 간에 저장한 데이터를 교환하는 데 사용하는 텍스트 포맷입니다. JSON은 원래 웹에서 사용하고자 처음 개발되었으며, JSON은 JavaScript Object Notation의 약자입니다. JSON은 JavaScript을 기반으로 하고 있으나, 언어 독립형(Language Independent) 포맷입니다. - 여러분이 C#으로 코딩을 하시든, 다른 프로그래밍 언어로 코딩을 하시든 상관없이 JSON을 사용하실 수 있습니다.

JSON은 키(key):값(value)의 쌍으로 구성된 형식으로 데이터를 저장합니다. 키는 문자열을 사용하며, 값에는 다음과 같은 요소들이 올 수 있습니다:

● 숫자

● 문자열(string)

● 불리언(Boolean) (true/false)

● 값의 배열

● 다른 JSON 오브젝트

JSON 포맷은 어떻게 동작하나요?

아래의 JSON 문자열은 사람에 대한 기본적인 정보를 저장하는 객체를 인코딩합니다.

{
  "name": "John",
  "age": 27,
  "address": {
    "streetAddress": "21 2nd Street",
    "city": "New York"
  }
  "pet": [“dog”, “cat”]
}

위의 예제 코드를 자세히 살펴봅시다:

● 각각의 객체(object)는 중괄호로 묶입니다. ( { } )

● 각각의 항목(entry)은 키:값 쌍의 형태를 지니며, 쉼표로 구분됩니다.

● "pet" 항목은 문자열 형태의 배열입니다. - 배열 값들은 대괄호 ( [ ] ) 안에 나열됩니다.

● "address" 항목과 연관된 값 또한 중괄호 안에 위치합니다. 이는 또 다른 JSON 객체이기 때문입니다.

 


 

5. JSON이 여러분의 작업에 적합한 이유는 무엇일까요?

JSON은 매우 다른 시스템들 사이에서 데이터를 교환할 수 있도록 개발되었기 때문에, 여러분의 애플리케이션에서 데이터를 저장하기에 적합합니다. 유니티에는 JsonUtility라는 이름의 도우미 클래스(Helper Class)가 존재합니다. 이 클래스는 직렬화 클래스(Serializable Class)를 JSON 형식으로 변환할 수 있도록 해줍니다. 이제 데이터 직렬화 및 역직렬화 작업이 어떻게 이루어지는 지에 대해 살펴보겠습니다.

데이터 직렬화

다음과 같이 PlayerData 클래스가 존재한다고 가정합시다:

[Serializable]
public class PlayerData
{
    public int level;
    public Vector3 position;
    public string playerName;
}

아래와 같은 값들과 함께 이 클래스를 JsonUtility로 넘겨주고 싶다고 가정해봅시다:

PlayerData myData = new PlayerData();
myData.level = 1;
myData.position = new Vector3(3.0f, 4.4f, 2.3f);
myData.playerName = "John";

string json = JsonUtility.ToJson(mydata); 로 호출하면, 아래와 같은 JSON 문자열이 생성됩니다:

{
    “level”: 1,
    “position”: {
        “x” : 3.0,
        “y” : 4.4,
        “z” : 2.3 },
    “playerName”: “John”
}

position이 vector3이기 때문에 3가지 종류의 키(x, y, z)를 가진 새로운 JSON 오브젝트로 인코딩 되었음을 기억해두세요. 이는 (매우 단순화된) Vector3 클래스가 다음과 같은 구조이기 때문입니다:

[Serializable]
public class Vector3
{
    public float x;
    public float y;
    public float z;
}

데이터 역직렬화

JsonUtility는 직렬화 과정을 거꾸로 수행할 수 있는 FromJson<T> 메서드도 가지고 있습니다. 이 메서드는 일부 JSON 데이터가 포함된 문자열을 사용하며, 필드에 작성된 데이터 값을 지닌 오브젝트 인스턴스를 생성합니다. 이 메서드는 템플릿 인수(Template Argument)를 사용합니다. PlayerData JSON 문자열을 살펴보면 눈치챌 수 있듯이, 데이터의 기존 타입은 JSON 파일에 저장되지 않습니다. 유니티가 우측 필드에 기입된 데이터를 올바르게 읽어드리기 위해서는 템플릿 인수가 필요합니다.

예제를 확장하기 위해, PlayerData myData = JsonUtility.FromJson<PlayerData>(json);을 호출하면 JSON 문자열의 값들로 myData가 채워질 것입니다.

JsonUtility의 한계

유니티의 JsonUtility 클래스는 성능과 단순함에 초점을 맞춰 설계되었기 때문에 몇 가지 한계점들을 지니고 있습니다. JsonUtility는 원시 자료형(Primitive Types), 배열(Array), 리스트(List) 또는 딕셔너리(Dictionary)와 함께 사용할 수 없습니다.

여기서 짐작하실 수 있듯이, JsonUtility는 직렬화 타입(Serializeable type) - MonoBehaviour 또는 [Serializable] 속성을 추가할 수 있는 다른 클래스/구조체에서만 동작합니다. 만약 여러분이 여러 데이터 조각들이 포함된 클래스를 저장하려고 했으며, 그중 하나가 저장되지 않았다면, 이는 직렬화가 불가능하기 때문일 것입니다. 예를 들어, 여러분이 중간에 딕셔너리를 포함한 클래스를 JSON으로 변환을 시도하면, 딕셔너리는 직렬화가 불가능하므로 저장되지 않을 것입니다. 클래스에서 어떤 데이터가 제대로 저장되지 않은 것처럼 보이신다면, 해당 데이터 타입이 직렬화가 가능한 타입인지 확인해주세요!

더 많은 정보는 JSON Serialization에 관한 유니티 API 문서를 참고해주세요.

 


 

6. SaveData 클래스 추가하기

사용자가 마지막에 선택한 색상을 저장 및 불러올 수 있도록 만들기 위해서는, MainManager 클래스에 아래의 3가지 요소들이 필요합니다:

1. 색상을 저장하는 SaveData 클래스.

2. 클래스를 JSON 포맷으로 변환하고 파일로 생성하는 Save 메서드.

3. 데이터를 JSON 파일에서 SaveData 클래스로 변환하는 Load 메서드.

SavaData 클래스부터 시작해봅시다:

1. MainManager.cs 파일을 여러분의 IDE에서 열어주세요.

2. 아래의 코드를 MainManager 클래스 하단(중괄호 영역 안)에 추가해주세요:

[System.Serializable]
class SaveData
{
    public Color TeamColor;
}

3. IDE를 보시면 마지막 줄에서 에러가 발생했을 것입니다. 이는 스크립트에 새로운 네임스페이스(Namespace)를 추가해야 하기 때문입니다. 스크립트 상단에 다음과 같이 코드를 추가해주세요:

using System.IO;

새로운 코드를 다시 살펴봅시다.

SaveData는 사용자가 선택한 색상을 저장하는 기능만 하는 단순한 클래스입니다. [System.Serializable] 속성을 상단에 추가해주었음을 기억해주세요. 해당 줄은 JsonUtility를 사용하는데 필요합니다. 방금 배우셨던 것처럼, JsonUtility에서는 직렬화(Serializable) 태그가 지정된 경우에만 JSON으로 변환이 가능합니다.

그렇다면 왜 클래스를 생성하고, MainManager 인스턴스를 곧장 JsonUtility로 전달하지 않는 걸까요? 음, 이는 대부분의 경우, 여러분의 클래스 안에 모든 것들을 저장할 수는 없습니다. 여러분이 저장하고 싶으신 특정 데이터만 포함하는 작은 클래스를 사용하는 것이 더 효과적이고 좋은 방법입니다.

 


 

7. SaveColor 메서드 추가하기

다음으로, SaveColor 메서드를 추가해봅시다:

1. SaveData 클래스 밑에 한 줄을 띄워주세요.

2. 아래의 코드를 작성해주세요:

public void SaveColor()
{
    SaveData data = new SaveData();
    data.TeamColor = TeamColor;

    string json = JsonUtility.ToJson(data);
  
    File.WriteAllText(Application.persistentDataPath + "/savefile.json", json);
}

3. IDE를 보시면 마지막 줄에서 에러가 발생했을 것입니다. 이는 스크립트에 새로운 네임스페이스(Namespace)를 추가해야 하기 때문입니다. 스크립트 상단에 다음과 같이 코드를 추가해주세요:

using System.IO;

새로운 코드를 다시 살펴봅시다:

시퀀스(Sequence) 안의 새로운 메서드를 하나씩 살펴봅시다. 먼저, SaveData 타입의 새로운 인스턴스를 생성했고, TeamColor 클래스 멤버에 MainManager 클래스에서 저장된 TeamColor 타입의 변수를 할당했습니다:

SaveData data = new SaveData();
data.TeamColor = TeamColor;

다음은, 인스턴스를 JsonUtility.ToJson을 사용해 JSON으로 변환했습니다.

string json = JsonUtility.ToJson(data);

마지막으로, 파일에 문자열을 저장하기 위해 File.WriteAllText라는 특별한 메서드를 사용했습니다:

File.WriteAllText(Application.persistentDataPath + "/savefile.json", json);

첫 번째 매개변수는 파일의 경로입니다. Application.persistentDataPath라는 이름의 유니티 메서드를 사용하여 애플리케이션이 재설치되거나 업데이트되는 사이에도 유지되는 데이터를 저장할 수 있는 폴더를 제공하고, 파일 이름 뒤에 savefile.json을 추가합니다.

참고: 유니티 스크립팅 API 문서를 참고하시면, 플랫폼별 실제 경로 리스트를 보실 수 있습니다.

두 번째 매개변수는 여러분이 파일에 작성하고 싶은 텍스트로, 위의 코드에서는 여러분의 JSON이 파일에 기록됩니다!

 


 

8. LoadColor 메서드 추가하기

이제 LoadColor 메서드를 추가해봅시다:

1. SaveColor 메서드 아래에 한 줄을 띄워주세요.

2. 아래의 코드를 추가해주세요:

public void LoadColor()
{
    string path = Application.persistentDataPath + "/savefile.json";
    if (File.Exists(path))
    {
        string json = File.ReadAllText(path);
        SaveData data = JsonUtility.FromJson<SaveData>(json);

        TeamColor = data.TeamColor;
    }
}

새로운 코드를 다시 살펴봅시다.

이 메서드는 SaveColor 메서드의 반대입니다:

string path = Application.persistentDataPath + "/savefile.json";

이 코드는 File.Exists 메서드를 사용해 .json 파일이 존재하는 지를 검사합니다. 만약 존재하지 않는다면, 아무것도 저장되지 않기에 다른 추가 작업이 불필요합니다. 만약 파일이 존재한다면, 메서드는 File.ReadAllText:에 포함된 내용을 읽어드릴 것입니다:

if (File.Exists(path))
{
    string json = File.ReadAllText(path);

그런 다음 결과 텍스트를 JsonUtility.FromJson으로 넘겨서 SaveData 인스턴스로 다시 변환합니다:

SaveData data = JsonUtility.FromJson<SaveData>(json);

마지막으로, TeamColor를 SaveData에 저장된 색상으로 설정합니다:

TeamColor = data.TeamColor;

여러분이 새로 작성하신 코드를 다시 한번 확인해주세요.

계속 진행하기에 앞서서, 여러분이 추가한 코드가 MainManager 클래스의 범위 내에 있는지 다시 한번 확인해주세요.

[System.Serializable]
class SaveData
{
    public Color TeamColor;
}

public void SaveColor()
{
    SaveData data = new SaveData();
    data.TeamColor = TeamColor;

    string json = JsonUtility.ToJson(data);
  
    File.WriteAllText(Application.persistentDataPath + "/savefile.json", json);
}

public void LoadColor()
{
    string path = Application.persistentDataPath + "/savefile.json";
    if (File.Exists(path))
    {
        string json = File.ReadAllText(path);
        SaveData data = JsonUtility.FromJson<SaveData>(json);

        TeamColor = data.TeamColor;
    }
}

 


 

9. 애플리케이션에서 색상 저장 및 불러오기

이제 거의 마무리되어 가고 있습니다만, 먼저 애플리케이션이 실행될 때 저장된 색상(만약 존재하는 경우)을 불러와야 하며, 애플리케이션 종료 시에는 색상을 저장해야 합니다. 이제 구현해봅시다:

1. MainManager.cs에서(아직 열려 있어야 함) Awake 메서드를 찾아주세요.

2. LoadColor 메서드를 Awake 메서드 하단에서 호출해주세요.

LoadColor();

3. 변경 사항을 저장하시고, IDE에서 MenuUIHandler.cs를 열어주세요.

4. Start 메서드 마지막 줄에 아래의 코드를 추가해주세요:

ColorPicker.SelectColor(MainManager.Instance.TeamColor);

이 코드는 메뉴 스크린을 띄울 때 MainManager에서 저장된 색상(이 있는 경우)을 미리 선택할 것입니다.

5. Exit 메서드가 시작되는 부근에 아래 코드를 추가해주세요:

MainManager.Instance.SaveColor();

이 코드는 애플리케이션이 종료될 때 사용자가 마지막으로 선택한 색상을 저장할 것입니다.

 


 

10. 테스트 기능 추가하기

마지막으로, 애플리케이션에서 바로 색상을 저장 및 로드할 수 있도록 MenuUIHandler에 몇 가지 빠른 테스트 기능을 추가해보겠습니다:

1. 아래 코드처럼 MenuUIHandler.cs에 메서드 2개를 추가해주세요:

public void SaveColorClicked()
{
    MainManager.Instance.SaveColor();
}

public void LoadColorClicked()
{
    MainManager.Instance.LoadColor();
    ColorPicker.SelectColor(MainManager.Instance.TeamColor);
}

2. 변경 사항을 저장해주세요.

3. 색상 로드 및 저장 버튼에 해당 메서드를 연결해주세요. 기억이 안 나신다면 Start 버튼을 어떻게 구성하셨는지 다시 살펴보셔도 됩니다.

에디터 상에서 테스트하기

애플리케이션 종료 시 데이터 저장 기능 및 애플리케이션 실행 시 데이터 로드 기능을 포함하여 애플리케이션에 대한 변경 사항들을 확실하게 테스트해주세요.

 


 

11. 다음 단계

이번 튜토리얼에서는 사용자가 애플리케이션을 다시 로드할 때, 사용자가 마지막에 선택한 색상이 미리 선택되어 있도록 여러분의 애플리케이션의 세션 간 데이터 지속성을 구현하였습니다. 이 기능은 아주 유용한 기능입니다!

다음 시간에는 새로운 유니티 프로젝트 상에서 기본적인 게임 개요를 충족하기 위해 여러분이 배운 내용을 적용하여 과제를 준비해보겠습니다.

 


 


수고하셨습니다!


Comments