아이템의 갯수는 PlayerPrefs를 사용하면 되지만 씬에 배치된 오브젝트는 저장할 수 없습니다.  예제에서는 JSON이라는 데이터 형식으로 배치된 데이터를 저장합니다.

JSON이란 텍스트 형식으로 쓰인 데이터 형식이빈다. 단순한 구조로 앱이나 인터넷에서 데이터 저장, 전달에 널리 사용됩니다.

JSON의 구체적인예

{
"hp":100,
"name":"유니",
"speed":10.3,
"isMoving":true
}

다룰수 있는 데이터형은 ,숫자,문자열,논리형, null, 배열 등입니다. {}로 감싼 데이터 자체를 값으로 가질 수도 있습니다.

유니티는 JsonUtility라는 클래스가 있어 쉽게JSON 형식을 사용할 수 있습니다.

배치 데이터를 저장하려면 JsonUtility 클래스를 사용 JSON형식(텍스트)으로 변환하고 PlayerPrefs 클래스를 활용 텍스트로 저장합니다, JSON자체는 텍스트이므로 PlayerPrefs클래스의 SetString메서드로 읽고 쓸수있습니다.

예제에서는 열린문, 열린상자, 획득한 아이템, 쓰러트린적 의 정보를 저장합시다. 저장하는 정보는 게임오브젝트를 식별할 수 있는 번호와 종류입니다. 번호는 9장까지 만들었던 Door, ItemBox, ItemData, EnemyController클래스의 arrnageID변수를 사용합니다. 종류는 각 게임오브젝트의 태그를 이용합니다.

 

배치된 정보를 기록하는 스크립트 만들기

JSON을 기록하려면 먼저 저장할 JSON과 같은 구조를 가진 클래스를 정의해야 합니다. SaveData라는 이름의 스크립트를 RoomManager폴더에 만들고 다음과 같이 작성합니다.

 

 

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[System.Serializable]
public class SaveData
{
    public int arrangeId = 0;       //배치 ID
    public string objTag = "";      //배치된 오브젝트의 태그
}

[System.Serializable]
public class SaveDataList
{
    public SaveData[] saveDatas;    //SaveData 배열
}

[System.Serializable]는 이 클래스는 저장할 대상입니다를 의미합니다. JSON으로 만들기 위해 필요한 것이라고 합시다.

SaveData 클래스는 배치된 오브젝트 하나를 나타내고. SaveDataList는 이들을 여러개 배열로 저장하는 데이터입니다.

두 클래스는 JSON형식(텍스트) 데이터를 클래스로 변환했을 때의 형을 정의합니다.

 

다음은 JSON데이터를 읽거나 써서 배치된 데이터를 관리하는 스크립트를 만들어보겠습니다.

 SaveDataManager  스크립트를 RoomManager폴더에 만들고 RoomManager의 프리팹에 어태치합니다. 

 

 

변수

SaveDataList클래스의 객체를 public static으로 선언합니다.

public static SaveDataList arrangeDataList;    //베치 데이터

Start메서드

 

 void Start() {
    //SaveDataList 초기화 
    arrangeDataList = new SaveDataList();
    arrangeDataList.saveDatas = new SaveData[] { };
    //씬 이름 불러오기
    string stageName = PlayerPrefs.GetString("LastScene");
    //씬 이름을 키로하여 저장 데이터 읽어오기
    string data = PlayerPrefs.GetString(stageName);
    if (data != "")   {
        //--- 저장 된 데이터가 존재할 경우  ---
        //JSON에서 SaveDataList로 변환하기
        arrangeDataList = JsonUtility.FromJson<SaveDataList>(data);
        for (int i = 0; i < arrangeDataList.saveDatas.Length; i++)  {
            SaveData savedata = arrangeDataList.saveDatas[i]; //배열에서 가져오기
            //태그로 게임 오브젝트 찾기
            string objTag = savedata.objTag;
            GameObject[] objects = GameObject.FindGameObjectsWithTag(objTag);
            for (int ii = 0; ii < objects.Length; ii++) {
                GameObject obj = objects[ii]; //배열에서 GameObject 가져오기
                //GameObject의 태그 확인하기
                if (objTag == "Door") {      //문 
                    Door door = obj.GetComponent<Door>();
                    if (door.arrangeId == savedata.arrangeId) {
                        Destroy(obj);  //arrangeId가 같으면 제거
                    }
                } else if (objTag == "ItemBox") {  //보물 상자
                    ItemBox box = obj.GetComponent<ItemBox>();
                    if (box.arrangeId == savedata.arrangeId) {
                        box.isClosed = false;   //arrangeIdd가 같으면 열기
                        box.GetComponent<SpriteRenderer>().sprite = box.openImage;
                    }
                }  else if (objTag == "Item") {     //아이템
                    ItemData item = obj.GetComponent<ItemData>();
                    if (item.arrangeId == savedata.arrangeId) {
                        Destroy(obj);   //arrangeId가 같으면 제거
                    }
                }
                else if (objTag == "Enemy") {      //적
                    EnemyController enemy = obj.GetComponent<EnemyController>();
                    if (enemy.arrangeId == savedata.arrangeId)  {
                        Destroy(obj);   //arrangeId가 같으면 제거
                    }
                }
            }
        }
    }
}

SetArrangeId()

public static void SetArrangeId(int arrangeId, string objTag)
{
    if (arrangeId == 0 || objTag == "")
    {
        //기록하지 않음
        return;
    }
    //추가 하기 위해 하나 많이 SaveData 배열 만들기
    SaveData[] newSavedatas = new SaveData[arrangeDataList.saveDatas.Length + 1];
    //데이터 복사
    for (int i = 0; i < arrangeDataList.saveDatas.Length; i++)
    {
        newSavedatas[i] = arrangeDataList.saveDatas[i];
    }
    //SaveData 만들기
    SaveData savedata = new SaveData();
    savedata.arrangeId = arrangeId; //Id를 기록
    savedata.objTag = objTag;       //태그 기록
    //SaveData 추가
    newSavedatas[arrangeDataList.saveDatas.Length] = savedata;
    arrangeDataList.saveDatas = newSavedatas;
}

SaveArrangeData()

public static void SaveArrangeData(string stageName)
{
    if (arrangeDataList.saveDatas != null && stageName != "")
    {
        //SaveDataList를 JSON 데이터로 변환
        string saveJson = JsonUtility.ToJson(arrangeDataList);
        //씬 이름을 키로하여 저장
        PlayerPrefs.SetString(stageName, saveJson);
    }
}

열린 보물 상자 저장

ItemBox.cs
0.00MB

보물 상자가 열리고아이템이 생성됐을때 호출되는 ItemBox 클래스의 OnCollisionEnter2D 메서드에 보물 상자의 열린 상태를 저장하는 처리를 추가합니다. 인수로 자기 자신(보물 상자)의 arrangeID와 태그를 전달합니다.

ItemBox스크립트를 열어 다음을 추가합니다.

public class ItemBox : MonoBehaviour
{
~ 생략
    //접촉 (물리)
    private void OnCollisionEnter2D(Collision2D collision)   {
        if (isClosed && collision.gameObject.tag == "Player")  {
            //상자가 닫혀 있는 상태에서 플레이어와 접촛
                //프리펩으로 아이템 만들기
            //배치 Id 기록
            SaveDataManager.SetArrangeId(arrangeId, gameObject.tag);
        }
    }
}

획득한 아이템 저장

ItemData.cs
0.00MB

아이템을 얻었을때 호출되는 ItemData클래스의 OnCollisionEnter2D()에 획득한 아이템을 저장하는 처리를 추가합니다. 인수로 arrangeID와 태그를 전달합니다.

public class ItemData : MonoBehaviour{
~생략
    //접촉 (물리)
    private void OnTriggerEnter2D(Collider2D collision)  {
        if (collision.gameObject.tag == "Player")     {
            if (type == ItemType.key)  {  //열쇠  }
            else if (type == ItemType.arrow)     {  //화살   }
            else if (type == ItemType.life)   {     }
            //++++ 아이템 획득 연출 ++++
            //충돌 판정 비활성
            //아이템의 Rigidbody2D가져오기
            //중력 젹용
            //위로 튀어오르는 연출
            //0.5초 뒤에 제거
            //배치 Id 기록
            SaveDataManager.SetArrangeId(arrangeId, gameObject.tag);
        }
    }
}

열린 문 저장

Door.cs
0.00MB

문이 열렸을때 호출되는 Door클래스의 OnCollisionEnter2D()에 문의 열린 상태를 저장하는 처리를 추가합니다. 인수로 arrangeID와 태그를 전달합니다.

public class Door : MonoBehaviour
{
~생략
    void OnCollisionEnter2D(Collision2D collision)  {
        if (collision.gameObject.tag == "Player")  {
            if (ItemKeeper.hasKeys > 0) {  //열쇠를 가지고있으면
                //열쇠를 하나 감소
                //문 열기 (제거하기)
                //배치 Id 기록
                SaveDataManager.SetArrangeId(arrangeId, gameObject.tag);
            }
        }
    }
}

쓰러트린 적 저장

EnemyController.cs
0.00MB

EmemyController의 OnCollisionEnter2D()에 쓰러트린 적을 저장하는 처리를 추가합니다. 인수로 arrangeID와 태그를 전달합니다.

private void OnCollisionEnter2D(Collision2D collision){
    if (collision.gameObject.tag == "Arrow") {
       
        hp--;  //데미지
        if (hp <= 0)  {    //사망!
           ~사망처리

            //배치 Id 기록
            SaveDataManager.SetArrangeId(arrangeId, gameObject.tag);
        }
    }
}

스테이지를 이동할 때 데이터 저장

RoomManager.cs
0.00MB

스테이지 이동은 RoomManager클래스의 ChangeScene메서드에서 실행합니다. 

public static void ChangeScene(string scnename, int doornum)
{
    doorNumber = doornum;   //문 번호를 static 변수에 저장

    string nowScene = PlayerPrefs.GetString("LastScene");
    if (nowScene != "")
    {
       SaveDataManager.SaveArrangeData(nowScene);      // 배치 데이터 저장
    }
    PlayerPrefs.SetString("LastScene", scnename);   // 씬 이름 저장
    PlayerPrefs.SetInt("LastDoor", doornum);        // 문 번호 저장
    ItemKeeper.SaveItem();                          // 아이템 저장

    SceneManager.LoadScene(scnename);   // 씬 이동
}

배치 데이터를 저장하기 위해 씬을 변경하기 전

SaveDataManager클래스의 SaveArrangeData메서드를 호출해 배치 데이터를 저장합니다.

 SaveArrangeData메서드에는 PlayerPrefs클래스의 GetString 메서드에 "LastScene"을 키로 전달하면 얻을 수 있는 "현재씬이름"을 사용합니다. 이름이 null이 아니면 SaveDataManager클래스의 SaveArrangeData메서드를 호출해 데이터를 저장합니다.

다음으로 PlayerPrefs클래스의 SetString메서드를 사용해 "LastScene"키로 앞으로 이동할 씬 이름을 저장합니다.

문번호는 PlayerPrefs클래스의 SetStringInt메서드를 사용해 "LastDoor"키로 문 번호를 저장합니다.

씬을 이동할 때 아이템을 저장하는 ItemKeeper클래스의 SaveItem메서드를 호출하면 그 방에서 획득한 아이템을 저장합니다.

 

게임이어하기 기능 만들기

TitleManager.cs
0.00MB

타이틀 화면의 [CONTINUE]버튼을 누르면 게임을 중간부터 시작할 수 있도록 합니다.

[GAME START]버튼을 누르면 이전 기록을 초기화 하는 처리도 추가합니다. TitleManager 스크립트를 다음과 같이 수정합니다.

변수

public GameObject startButton;      //스타트 버튼
public GameObject continueButton;   //이어하기 버튼
public string firstSceneName;       //게임 시작 씬 이름

Start()

PlayerPrefs "LastScene"키로 가져온 이름이 없다면 [CONTINUE]버튼을 비활성화 합니다. 마지막씬이 있다면 활성화합니다.

void Start()
{
    string sceneName = PlayerPrefs.GetString("LastScene");      //저장 된 씬
    if (sceneName == "")
    {
        continueButton.GetComponent<Button>().interactable = false; //비활성
    }
    else
    {
        continueButton.GetComponent<Button>().interactable = true; //활성
    }
    //타이틀 BGM 재생
    //SoundManager.soundManager.PlayBgm(BGMType.Title);
}

StartButtonClicked()

public void StartButtonClicked()
{
    //저장 데이터를 지움
    PlayerPrefs.DeleteAll();
    //HP 초기화 
    PlayerPrefs.SetInt("PlayerHP", 3);
    //저장된 스테이지 정보를 지움
    PlayerPrefs.SetString("LastScene", firstSceneName); //씬 이름 초기화
    RoomManager.doorNumber = 0;

    SceneManager.LoadScene(firstSceneName);
}

ContinueButtonClicked()

public void ContinueButtonClicked()
{
    string sceneName = PlayerPrefs.GetString("LastScene");      //저장된 씬
    RoomManager.doorNumber = PlayerPrefs.GetInt("LastDoor");    //문 번호
    SceneManager.LoadScene(sceneName);
}

게임 재시도 처리

UIManager.cs
0.00MB

UIManager 클래스의 Retry메서드도 UIManager스크립트에서 다음과 같이 수정합니다. Player.Prefs.SetInt로 저장된 플레이어의 HP를 3으로 되돌립니다.

 //재시도
public void Retry()
{
    //HP 되돌리기
    PlayerPrefs.SetInt("PlayerHP", 3);

    //게임 중으로 설정
    SceneManager.LoadScene(retrySceneName);   //씬 이동
}

 

저장할 수 있는 배치 데이터 만들기

이제 저장할 수 있는 배치 데이터를 만들어 봅시다. 배치할 수 있는 게임 오브젝트는 다음표와 같습니다. 각각 다른 태그가 설정됐으며. 각오브젝트에는 arrangeID라는 Int형 변수를 가지는 스크립트가 어태치됐습니다.

게임오브젝트 태그 arrangeID를 갖는 스크립트
Door Door
아이템 Item ItemData
보물상자 ItemBox ItemBox

예를 들어 보물 상자가 두개 배치되었으면 Itembox의 arrangeID에 각각 1이상이고 서로 중복되지 않는 숫자를 설정합니다. 이제 이상태에서 타이틀 화면은 [GAME START]버튼을 클릭해서게임을 시작합니다. 게임중 획득한 아이템이나 열린 문의 정보가 각 씬을 옮겨 다닐 때마다 저장되고 재현됩니다. 타이틀화면으로 돌아가 [CONTINUE] 버튼으로 게임을 시작하면 아이템의 수나 아이템의 배치 상태등 방의 모습이 재현된 상태로 게임이 시작됩니다.

 

게임 실행하기 

실행시 SoundManager관련 에러가 나면  주석처리 해줍니다. SoundManager는 맨 마지막에 만들겁니다.

+ Recent posts