FPS서바이벌 디펜스

完.세이브 & 로드

ruripanda 2025. 6. 7. 12:02

본 강좌는 케이디님의 유튜브강좌 영상을 보고 유니티6로 구현한 것 입니다 이렇게 멋진 강좌를 만드신 케이디님을 존경합니다

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

public class Title : MonoBehaviour
{
    public string sceneName = "SampleScene";

    public static Title instance;

    private SaveAndLoad theSaveNLoad;

    private void Awake()
    {
        if(instance == null)
        {
            instance = this;
            DontDestroyOnLoad(gameObject);
        }
        else
        {
            Destroy(this.gameObject);
        }
    }

    public void ClickStart()
    {
        Debug.Log("로딩");
        this.gameObject.SetActive(false);
        SceneManager.LoadScene(sceneName);
    }

    public void ClickLoad()
    {
        Debug.Log("로드");
        StartCoroutine(LoadCoroutine());
    }

    IEnumerator LoadCoroutine()
    {
        AsyncOperation operation = SceneManager.LoadSceneAsync(sceneName);

        while (!operation.isDone)
        {
            yield return null;
        }

        theSaveNLoad = FindAnyObjectByType<SaveAndLoad>();
        theSaveNLoad.LoadData();
        this.gameObject.SetActive(false);
    }

    public void ClickExit()
    {
        Debug.Log("종료");
        Application.Quit();
    }
}
using UnityEngine;
using System.IO;
using System.Collections.Generic;

[System.Serializable]
public class SaveData
{
    public Vector3 playerPos;
    public Vector3 playerRot;

    public List<int> invenArrayNumber = new List<int>();
    public List<string> invenItemName = new List<string>();
    public List<int> invenItemNumber = new List<int>();
}
public class SaveAndLoad : MonoBehaviour
{
    private SaveData saveData = new SaveData();

    private string SAVE_DATA_DIRECTORY;
    private string SAVE_FILENAME = "/SaveFile.txt";

    private PlayerController thePlayer;
    private Inventory theInven;

    // Start is called once before the first execution of Update after the MonoBehaviour is created
    void Start()
    {
        SAVE_DATA_DIRECTORY = Application.dataPath + "/Saves";

        if(!Directory.Exists(SAVE_DATA_DIRECTORY))
            Directory.CreateDirectory(SAVE_DATA_DIRECTORY);
    }

    public void SaveData()
    {
        thePlayer = FindAnyObjectByType<PlayerController>();
        theInven = FindAnyObjectByType<Inventory>();

        saveData.playerPos = thePlayer.transform.position;//플레이어 위치
        saveData.playerRot = thePlayer.transform.eulerAngles;//플레이어 벡터 방향

        Slot[] slots = theInven.GetSlots();

        for(int i = 0; i < slots.Length; i++)
        {
            if( slots[i].item != null)
            {
                saveData.invenArrayNumber.Add(i);
                saveData.invenItemName.Add(slots[i].item.itemName);
                saveData.invenItemNumber.Add(slots[i].itemCount);
            }
        }

        string json = JsonUtility.ToJson(saveData);

        File.WriteAllText(SAVE_DATA_DIRECTORY +  SAVE_FILENAME, json);

        Debug.Log("저장 완료");
        Debug.Log(json);
    }

    public void LoadData()
    {
        if (File.Exists(SAVE_DATA_DIRECTORY + SAVE_FILENAME))
        {
            string loadJson = File.ReadAllText(SAVE_DATA_DIRECTORY + SAVE_FILENAME);
            saveData = JsonUtility.FromJson<SaveData>(loadJson);

            thePlayer = FindAnyObjectByType<PlayerController>();
            theInven = FindAnyObjectByType<Inventory>();

            thePlayer.transform.position = saveData.playerPos;
            thePlayer.transform.eulerAngles = saveData.playerRot;

            for(int i = 0; i < saveData.invenItemName.Count; i++)
            {
                theInven.LoadToInven(saveData.invenArrayNumber[i], saveData.invenItemName[i], saveData.invenItemNumber[i]);
            }

            Debug.Log("로드 완료");
        }
        else
            Debug.Log("세이브 파일이 없습니다");

    }
}
using UnityEngine;

public class StopMenu : MonoBehaviour
{
    [SerializeField] private GameObject go_BaseUi;
    [SerializeField] private SaveAndLoad theSaveNLoad;

    // Update is called once per frame
    void Update()
    {
        if (Input.GetKeyDown(KeyCode.P))
        {
            if (!GameManager.isPause)
                CallMenu();
            else
            {
                CloseMenu();
            }
        }
    }

    private void CallMenu()
    {
        GameManager.isPause = true;
        go_BaseUi.SetActive(true);
        Time.timeScale = 0f;
    }

    private void CloseMenu()
    {
        GameManager.isPause = false;
        go_BaseUi.SetActive(false);
        Time.timeScale = 1f;
    }

    public void ClickSave()
    {
        Debug.Log("세이브");
        theSaveNLoad.SaveData();
    }
    public void ClickLoad()
    {
        Debug.Log("로드");
        theSaveNLoad.LoadData();
    }
    public void ClickExit()
    {
        Debug.Log("종료");
        Application.Quit();
    }
}
using UnityEngine;

public class Inventory : MonoBehaviour
{
    public static bool inventoryActivated = false;

    //필요한 컴포넌트
    [SerializeField]
    private GameObject go_InventoryBase;
    [SerializeField]
    private GameObject go_SlotsParent;

    private Slot[] slots;

    public Slot[] GetSlots() { return slots; }

    [SerializeField] private Item[] items;

    public void LoadToInven(int _arrayNum, string _itemName, int _itenNum)
    {
        for (int i = 0; i < items.Length; i++) 
        {
            if (items[i].itemName == _itemName)
                slots[_arrayNum].AddItem(items[i], _itenNum);
        }
    }

    // Start is called once before the first execution of Update after the MonoBehaviour is created
    void Start()
    {
        slots = go_SlotsParent.GetComponentsInChildren<Slot>();    
    }

    // Update is called once per frame
    void Update()
    {
        TryOpenInventory();
    }

    private void TryOpenInventory()
    {
        if (Input.GetKeyDown(KeyCode.I))
        {
            inventoryActivated = !inventoryActivated;

            if (inventoryActivated)
                OpenInventory();
            else
                CloseInventory();
        }
    }

    private void OpenInventory()
    {
        GameManager.isOpenInventory = true;
        go_InventoryBase.SetActive(true);
    }

    private void CloseInventory()
    {
        GameManager.isOpenInventory = false;
        go_InventoryBase.SetActive(false);
    }

    public void AcquireItem(Item _item, int _count = 1)
    {
        if (Item.ItemType.Equipment != _item.itemType)
        {
            for (int i = 0; i < slots.Length; i++)
            {
                if (slots[i].item != null)
                {
                    if (slots[i].item.itemName == _item.itemName)
                    {
                        slots[i].SetSlotCount(_count);
                        return;
                    }
                }
            }
        }

        for (int i = 0; i < slots.Length; i++)
        {
            if (slots[i].item == null)
            {
                slots[i].AddItem(_item, _count);
                return;
            }
        }
    }
}

이번 강좌에서는 마지막 강좌 세이브 & 로드를 구현하겠다

플레이어의 위치, 플레이어의 방향, 아이템 인벤토리 내역을 저장하고 로드한다

 

일단 Title 스크립트부터 열어주자

 

10번은 Title씬을 인스턴스화 해준다

12번은 세이브엔로드 스크립트를 장착해준다

14번 줄의 void Awake함수는 타이틀 씬이 다른 씬으로 이동해도 파괴되는 것을 방지해준다

이유는 씬을 이동하면서 세이브 파일 데이터를 로드하기 전까지는 필요하기 때문이다

 

ClickStart()함수를 보면

30번줄에 타이틀 씬을 비활성화해주는 것을 달아놨다

이게 있어야 다른 씬으로 넘어가고 타이틀 씬을 비 활성화해 줄 수 있다

 

ClickLoad는 코루틴으로 된 Load를 불러와준다

 

 

LoadCoroutine()함수이다

42번 줄은 스테이지 씬을 로드해주는 것이다

44번의 while문은 위의 스테이지 로드가 끝날때까지 대기해주는 것이다 실제 구현되면 노란색으로 스크린이 됬다가 넘어간다

나머지 내용은 주석을 참고하자

 

이제 StopMenu 스크립트를 열어주자

 

6번줄에 추가한 내용이 있다

세이브엔로드를 장착해준다

 

한눈에 알아보기 쉽게 작성했다 참고해주자

 

이제 Inventory 스크립트를 열어주자

 

15번 줄에 Slot[] GetSlots()로 린턴해주어서 외부로 아이템 정보를 보내준다

19번줄이 실제 아이템을 저장하는데 정보를 저장해주는 함수이다 주석으로 설명해놓았다

 

이제 SaveData 스크립트를 작성해주자

 

2번 줄을 보면 System.IO 를 작성하여 세이브파일을 생성할 수 있게 using를 가져왔다

 

이제 커스텀 변수를 보자

커스텀 클레스 이므로 5번줄의 내용을 작성해줬다

8번과 9번은 각 캐릭터의 위치와 방향이다 Json은 이 두가지를 한꺼번에 저장하지 못하므로 따로 기록한다

11~13번은 아이템 저장 데이터다

 

이제 SaveAndLoad 스크립트의 기본 변수를 보자

17번은 새로운 세이브 파일을 작성하게 해준ㄷ나

나머지는 주석 그대로의 내용이다

 

void Stat()함수의 내용이다

주석을 참고해주자

 

SaveData() 함수의 내용이다

36번과 37번은 플레이어 컨트롤과 인벤토리 스크립트를 장착해준 것이다

 

39와 40번은 플레이어의 위치와 방향을 저장해준다

42번은 아이템의 슬롯 데이터를 받아와서...

for문을 아이템 개수만큼 돌려주면서

if문으로 아이템 슬롯이 비어있지 않다면

48~50번이 각 인벤토리 슬롯 위치, 아이템 이름, 아이템 개수이다

 

54번줄은 Json파일을 생성하여 준다

그리고 그 파일을 물리적으로 저장해서 윈도우의 디렉토리에 파일로 남긴다

58번과 59번은 콘솔로 저장완료를 출력한 후 Json 파일의 내용을 콘솔로 출력한다

 

LoadData() 함수이다

if문으로 조건을 걸고 파일 디렉토리에 세이브 파일이 있을대 실행하게 한 다음

66번줄에 string문으로 Json파일을 읽어온다

67번줄에 Json파일을 풀어주는 작업을 하고

69와 70번줄은 플레이어 컨트롤과 인벤토리 스크립트를 로드해준 것이다

 

72와73번줄은 플레이어의 위치와 방향을 저장해준 것이다

 

75번은 아이템을 불러오는데

인벤토리 슬롯 넘버, 인벤토리 아이템 이름, 인벤토리 아이템 개수를 불러와준다

그뒤 콜솔에 로드 완료를 출력해준다

 

82번은 세이브 파일이 없으면 없다고 출력해주는 것이다

 

이렇게 해서 세이브 로드를 구현했다 테스트를 해보면...

 

구현이 끝났다 ㅜㅜ

 

드디어 다 공부했다!!

'FPS서바이벌 디펜스' 카테고리의 다른 글

33.타이틀 메뉴  (2) 2025.06.06
32.일시정지 메뉴  (0) 2025.06.06
31.수영과 호흡  (0) 2025.06.06
30.물구현과 수영 시스템  (0) 2025.06.03
29.낮과 밤 구현하기  (0) 2025.06.03