본문 바로가기
개발일지/게임개발

Unity 게임 개발 - 세이브 기능

by 라이티아 2025. 5. 5.

현재 코기 엔진의 세이브 기능을 활용해서 세이브 창을 만들고 있다

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


[System.Serializable]
public struct SerializableVector3
{
    public float x, y, z;

    public SerializableVector3(float x, float y, float z)
    {
        this.x = x;
        this.y = y;
        this.z = z;
    }

    public SerializableVector3(Vector3 vector)
    {
        this.x = vector.x;
        this.y = vector.y;
        this.z = vector.z;
    }

    public Vector3 ToVector3()
    {
        return new Vector3(x, y, z);
    }
}

[System.Serializable]
public class PlayerSaveData
{
    public SerializableVector3 savePosition; // Vector3 → SerializableVector3 변경

    public PlayerSaveData()
    {
        savePosition = new SerializableVector3(0, 0, 0);
    }

    public PlayerSaveData(Transform pos)
    {
        savePosition = new SerializableVector3(pos.position); // Vector3을 변환하여 저장
    }

    public Vector3 GetPosition()
    {
        return savePosition.ToVector3(); // 다시 Vector3로 변환
    }
}


public class saveManager : MonoBehaviour
{
    public Transform playerTransform;

    private const string SaveFileName = "PlayerPosition.save";
    private const string SaveFolder = "PlayerData";
    private static PlayerSaveData loadedData;

    private void Awake()
    {
        SceneManager.sceneLoaded += OnSceneLoaded;
    }

    private void OnDestroy()
    {
        // 씬이 변경될 때마다 중복 호출 방지를 위해 이벤트 제거
        SceneManager.sceneLoaded -= OnSceneLoaded;
    }

    // 게임 저장
    public void SaveGame()
    {
        playerTransform = GameObject.FindWithTag("Player").transform;
        if (playerTransform == null)
        {
            Debug.LogError("플레이어 Transform이 연결되지 않았습니다!");
            return;
        }

        PlayerSaveData saveData = new PlayerSaveData(playerTransform);
        MMSaveLoadManager.Save(saveData, SaveFileName, SaveFolder);
        Debug.Log("게임 저장 완료! 위치: " + saveData.savePosition.x + ", " + saveData.savePosition.y + ", " + saveData.savePosition.z);
    }

    // 씬을 다시 로드한 후, 저장된 위치로 플레이어 이동
    public void LoadGame()
    {
        object loadedObject = MMSaveLoadManager.Load(typeof(PlayerSaveData), SaveFileName, SaveFolder);

        if (loadedObject == null)
        {
            Debug.LogWarning("저장된 데이터가 없습니다!");
            return;
        }

        loadedData = (PlayerSaveData)loadedObject;
        Debug.Log("저장된 데이터 로드 완료. 씬을 다시 로드합니다...");

        // 씬 다시 로드
        SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex);
    }

    private void OnSceneLoaded(Scene scene, LoadSceneMode mode)
    {
        StartCoroutine(WaitAndSetPosition());
    }

    private IEnumerator WaitAndSetPosition()
    {
        yield return new WaitForSeconds(0.5f);

        if (loadedData != null)
        {
            GameObject player = GameObject.FindWithTag("Player");

            if (player != null)
            {
                player.transform.position = loadedData.GetPosition();
                Debug.Log("플레이어 위치 복원 완료! 위치: " + player.transform.position);
            }
            else
            {
                Debug.LogError("씬에서 플레이어를 찾을 수 없습니다! 플레이어가 생성되었는지 확인하세요.");
            }

            loadedData = null;
        }
    }

    public void DeleteSave()
    {
        MMSaveLoadManager.DeleteSave(SaveFileName, SaveFolder);
        Debug.Log("저장된 데이터 삭제 완료!");
    }
}

세이브 기능을 관리하는 매니저 이며

using MoreMountains.CorgiEngine;
using UnityEngine;
using UnityEditor;

public class saveArea : MonoBehaviour
{
    
    private bool playerInArea = false; // 플레이어가 영역 안에 있는지 확인
    private saveManager saveManager;
    private void Awake() {
        saveManager = GameObject.Find("saveManager").GetComponent<saveManager>();
    }
    [MenuItem("Custom Menu/customLoad")]
    private static void MyCustomMethod()
    {
        LoadCustom();
    }

    private void Update() {
        if (playerInArea && Input.GetKeyDown(KeyCode.L))
        {
            Debug.Log("플레이어 저장");
            saveManager.SaveGame();
        }
    }

    private void OnTriggerEnter2D(Collider2D other)
    {
        if (other.CompareTag("Player"))
        {
            playerInArea = true;
            Debug.Log("플레이어가 저장 영역에 들어왔습니다!");
        }
    }
    
    static private void LoadCustom()
    {
        saveManager instance = GameObject.FindObjectOfType<saveManager>();
        if (instance == null)
        {
            Debug.LogError("saveManager 인스턴스를 찾을 수 없습니다!");
            return;
        }

        instance.LoadGame();
    }


    private void OnTriggerExit2D(Collider2D other)
    {
        if (other.CompareTag("Player"))
        {
            playerInArea = false;
            Debug.Log("플레이어가 저장 영역을 벗어났습니다.");
        }
    }

    public bool IsPlayerInSaveArea()
    {
        return playerInArea;
    }
}

세이브 구역을 관리하는 스크립트이다

 

현재 구조는 플레이어 캐릭터가 세이브 구역에 들어가면 인식 후, 특정키 = 현재는 L키를 누르면 세이브 기능이 사용되며, 로드는 커스텀 메뉴에서 사용되는 경우이다

 

현재 구조로는

휴식하는 공간 진입 => 세이브 기능 or 정비 기능 버튼이 나오고, 세이브 기능 버튼을 누르면 세이브 버튼이 나오고, 버튼을 누르면 세이브가 되는 형식이다

 

현재 UI와는 연결되어 있지 않다

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

public class RestUIControl : MonoBehaviour
{
    [SerializeField]
    private saveManager saveManager;
    [SerializeField]
    private GameObject RestLayerUI;
    [SerializeField]
    private GameObject SaveLayerUI;
    
    public void GotoSaveLayerBtn()
    {
        RestLayerUI.SetActive(false);
        SaveLayerUI.SetActive(true);
    }

    public void BacktoRestLayerBtn()
    {
        RestLayerUI.SetActive(true);
        SaveLayerUI.SetActive(false);
    }

    public void SaveBtnA()
    {
        saveManager.SaveGame();
    }
}

UI를 관리하는 스크립트이며, 여기에 saveManager를 객체로 가져와서 사용한다

 

    public void SaveBtnA()
    {
        saveManager.SaveGame();
        BacktoRestLayerBtn();
        gameObject.SetActive(false);
    }

이 함수를 실행해서 저장을 하도록 한다

 

이제 L키를 눌렀을때 RestUI가 나오도록 해야 한다

 

    private void Update() {
        if (playerInArea && Input.GetKeyDown(KeyCode.L))
        {
            Debug.Log("플레이어 저장");
            saveManager.SaveGame();
        }
    }

현재 이런 구조를 가지고 있는데, 이를 조금 수정한다

[SerializeField]
private GameObject RestUI;
RestUI.SetActive(true);

이런 줄을 추가해서 관리한다

 

나중에 GameObject를 자동으로 할당하도록 수정해야 한다

 

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

public class RestUIControl : MonoBehaviour
{
    [SerializeField]
    private GameObject RestLayerUI;
    [SerializeField]
    private GameObject SaveLayerUI;
    
    public void GotoSaveLayerBtn()
    {
        RestLayerUI.SetActive(false);
        SaveLayerUI.SetActive(true);
    }

    public void BacktoRestLayerBtn()
    {
        RestLayerUI.SetActive(true);
        SaveLayerUI.SetActive(false);
    }

    public void SaveBtnA()
    {
        saveManager.Instance.SaveGame();
        BacktoRestLayerBtn();
        gameObject.SetActive(false);
    }
}