게임에서 저장 및 불러오기 기능은 유저들이 게임을 중단하고 나중에 다시 이어서 플레이할 수 있도록 해줍니다. 이는 게임 플레이 경험을 끊김없이 유지할 수 있으며, 유저들이 게임을 더욱 쉽게 접근할 수 있도록 도와줍니다. 또한 게임을 재미있게 플레이하는 유저들의 만족도를 높이는 데에도 중요한 역할을 합니다. 게임 저장 시스템을 구현하고 이를 정리하였습니다.
📺 미리보기
💬 서론
- 본 기능은 게임을 저장하고 불러오는 기능으로 저장할 데이터들은 게임의 스타일 및 제작자의 의도에 따라 다를 수 있습니다.
- 본 글에서는 게임 저장 시스템을 어떤 의도로 구현하였는지 요약하여 정리하였습니다.
- 모든 내용이 들어가있지 않으며, 게임 저장 및 불러오기에대한 대략적인 아이디어를 정리하였습니다.
📖 구현 내용
- 게임을 저장하고 불러옵니다.
- 플레이어의 위치, 바라보는 방향, 체력, 스탯 등 플레이 진척 사항을 데이터로 저장합니다.
- 저장한 데이터를 불러오고 저장 직전의 상태와 동일하게 불러옵니다.
- 필수적인 요소가 아닌 외적 요소 또한 유동적으로 저장 기능에 추가하거나 제거할 수 있습니다.
✅ 구현
- 이번 글에서는 저장 및 불러오기 기능을 제공할 슬롯에 대한 내용을 정리하였습니다.
· Script
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using TMPro;
using UnityEngine.UI;
using UnityEngine.SceneManagement;
using System.IO;
using DialogBox;
namespace GameSave
{
public class GameDataSaveLoadSlot : MonoBehaviour
{
[field: Header("이 슬롯의 고유한 번호")]
[field: SerializeField] public int SlotId { private set; get; } = -1;
[Space(30)]
[Header("슬롯의 버튼 (저장, 불러오기, 삭제)")]
[SerializeField] private Button mSaveButton;
[SerializeField] private Button mLoadButton;
[SerializeField] private Button mRemoveButton;
[Space(30)]
[Header("슬롯의 메인, 서브 라벨")]
[SerializeField] private TextMeshProUGUI mMainLabel;
[SerializeField] private TextMeshProUGUI mSubLabel;
private void Awake()
{
UpdateSlot();
}
private void OnEnable()
{
UpdateSlot();
// 현재 세이브로드 슬롯을 사용하는 씬이 타이틀이라면?
if (SceneManager.GetActiveScene().name == "Main Title")
mSaveButton.interactable = false;// 세이브 버튼 비활성화
}
public void UpdateSlot()
{
// 슬롯 번호에 해당하는 세이브 파일이 있으면?
if (GameDataSaveLoadManager.Instance.FileExists(SlotId))
{
// 코어 파일 읽기
{
string fromJson = File.ReadAllText(GameDataSaveLoadManager._FILE_PATH + SlotId);
GameDataCore dataCore = JsonUtility.FromJson<GameDataCore>(fromJson);
mMainLabel.text = dataCore.gameBaseInfo.dateTime;
mSubLabel.text = $"위치:{dataCore.gameBaseInfo.sceneName} / 레벨:{dataCore.playerGameData.statData.level}";
}
// 로드, 제거 버튼 활성화
mLoadButton.interactable = true;
mRemoveButton.interactable = true;
}
else
{
// 로드, 제거 버튼 비활성화
mLoadButton.interactable = false;
mRemoveButton.interactable = false;
mMainLabel.text = "저장된 파일이 없습니다.";
mSubLabel.text = "";
}
}
public void BTN_Save()
{
if (GameDataSaveLoadManager.IsSaveButtonLocked)
{
DialogBoxController dialogBox = DialogBoxGenerator.Instance.CreateSimpleDialogBox("알림", $"지금은 게임을 저장할 수 없습니다.", "확인", DialogBoxController.RESERVED_EVENT_CLOSE, null, 220);
dialogBox.ModifyBottomLayoutPadding(70, 70);
return;
}
// 이미 파일이 있으면? > 덮어쓰기 위험성 알리기
if (GameDataSaveLoadManager.Instance.FileExists(SlotId))
{
DialogBoxController dialogBox = DialogBoxGenerator.Instance.CreateSimpleDialogBox(
"알림", $"{SlotId + 1}번째 슬롯에 게임을 저장합니까?", "예", "YES",
(controller, eventArg) =>
{
switch (eventArg)
{
case "YES":
{
GameDataSaveLoadManager.Instance.SaveGameData(SlotId);
UpdateSlot();
break;
}
case "NO":
{
break;
}
}
controller.DestroyBox();
},
220, 130, 30, 30);
dialogBox.AddButton(null, true, "아니오", "NO");
dialogBox.AddBorder(null, true, 10, true);
dialogBox.AddText(null, true, "기존의 파일을 덮어쓰기합니다.", 20, TextAlignmentOptions.Center);
dialogBox.ModifyBottomLayoutPadding(32, 32, 5, 5, 15);
}
else
{
GameDataSaveLoadManager.Instance.SaveGameData(SlotId);
UpdateSlot();
}
}
public void BTN_Load()
{
DialogBoxController dialogBox = DialogBoxGenerator.Instance.CreateSimpleDialogBox(
"게임 불러오기", $"{SlotId + 1}번째 게임을 불러옵니까?", "예", "YES",
(controller, eventArg) =>
{
switch (eventArg)
{
case "YES":
{
string fromJson = File.ReadAllText(GameDataSaveLoadManager._FILE_PATH + SlotId);
GameDataCore dataCore = JsonUtility.FromJson<GameDataCore>(fromJson);
LoadingSceneController.Instance.LoadScene(dataCore.gameBaseInfo.sceneName, () =>
{
GameDataSaveLoadManager.Instance.LoadGameData(SlotId);
});
break;
}
case "NO":
{
break;
}
}
controller.DestroyBox();
},
220, 130, 30, 30);
dialogBox.AddButton(null, true, "아니오", "NO");
dialogBox.ModifyBottomLayoutPadding(32, 32, 5, 5, 15);
}
public void BTN_Remove()
{
DialogBoxController dialogBox = DialogBoxGenerator.Instance.CreateSimpleDialogBox(
"알림", $"{SlotId + 1}번째 슬롯의 데이터를 삭제합니까?", "예", "YES",
(controller, eventArg) =>
{
switch (eventArg)
{
case "YES":
{
GameDataSaveLoadManager.Instance.RemoveGameData(SlotId);
UpdateSlot();
break;
}
case "NO":
{
break;
}
}
controller.DestroyBox();
},
220, 130, 30, 30);
dialogBox.AddButton(null, true, "아니오", "NO");
dialogBox.ModifyBottomLayoutPadding(32, 32, 5, 5, 15);
}
}
}
[field: Header("이 슬롯의 고유한 번호")]
[field: SerializeField] public int SlotId { private set; get; } = -1;
- 이 슬롯이 몇번째인지 확인하기위한 변수입니다.
- 슬롯 번호에 따라 게임을 저장하고, 파일을 구분하기 위해 사용합니다.
[Space(30)]
[Header("슬롯의 버튼 (저장, 불러오기, 삭제)")]
[SerializeField] private Button mSaveButton;
[SerializeField] private Button mLoadButton;
[SerializeField] private Button mRemoveButton;
- 버튼들을 가져옵니다.
- 조건에 맞게 특정 버튼의 사용 가능 여부를 유연하게 설정하기위해 가져옵니다.
[Space(30)]
[Header("슬롯의 메인, 서브 라벨")]
[SerializeField] private TextMeshProUGUI mMainLabel;
[SerializeField] private TextMeshProUGUI mSubLabel;
- 세이브 슬롯의 현재 정보를 표시하기위한 라벨들입니다.
private void OnEnable()
{
UpdateSlot();
// 현재 세이브로드 슬롯을 사용하는 씬이 타이틀이라면?
if (SceneManager.GetActiveScene().name == "Main Title")
mSaveButton.interactable = false;// 세이브 버튼 비활성화
}
- 슬롯이 활성화 될 때 호출되며 갱신합니다.
- 만약 현재 씬이 메인 타이틀이라면, 타이틀은 게임을 하는 씬이 아니기에 저장기능을 비활성화합니다.
public void UpdateSlot()
{
// 슬롯 번호에 해당하는 세이브 파일이 있으면?
if (GameDataSaveLoadManager.Instance.FileExists(SlotId))
{
// 코어 파일 읽기
{
string fromJson = File.ReadAllText(GameDataSaveLoadManager._FILE_PATH + SlotId);
GameDataCore dataCore = JsonUtility.FromJson<GameDataCore>(fromJson);
mMainLabel.text = dataCore.gameBaseInfo.dateTime;
mSubLabel.text = $"위치:{dataCore.gameBaseInfo.sceneName} / 레벨:{dataCore.playerGameData.statData.level}";
}
// 로드, 제거 버튼 활성화
mLoadButton.interactable = true;
mRemoveButton.interactable = true;
}
else
{
// 로드, 제거 버튼 비활성화
mLoadButton.interactable = false;
mRemoveButton.interactable = false;
mMainLabel.text = "저장된 파일이 없습니다.";
mSubLabel.text = "";
}
}
- 슬롯의 현 상태를 업데이트합니다.
- 만약 슬롯 번호에 해당하는 세이브파일이 있으면 로드가 가능하기에 파일을 읽어 요약 정보를 라벨에 표시해줍니다.
- 만약에 세이브 파일이 없으면, 삭제 및 불러오기가 불가능하기에 해당 버튼들을 비활성화하고 라벨 또한 세이브 파일이 없다고 표시해줍니다.
public void BTN_Save() { ... }
- 해당 슬롯에 게임을 저장합니다.
- 만약에 특정 조건에 의해 세이브가 막혀있다면, 세이브가 불가능하다는 다이얼로그 창을 띄웁니다.
- 막혀있지 않다면 저장을 시도합니다.
- 만약 이미 파일이 있다면, 덮어쓰기 경고 다이얼로그 창을 띄워줍니다.
- GameDataSaveLoadManager를 호출하여 현재 씬의 정보를 읽어 텍스트파일로 저장합니다.
public void BTN_Load() { ... }
- 해당 슬롯의 게임 데이터를 불러옵니다.
- 게임 데이터에 맞는 씬을 읽어 해당 씬을 로드하며 로드 후 데이터를 적용하도록 합니다.
public void BTN_Remove() { ... }
- 해당 슬롯의 파일을 제거합니다.
· UI
- 전체적인 디자인 구성입니다.
- 각 슬롯에서 사용하는 구성요소입니다.
'unity game modules' 카테고리의 다른 글
[유니티] 게임 저장 시스템(3) - 매니저 (0) | 2023.04.02 |
---|---|
[유니티] 게임 저장 시스템(2) - 저장 및 불러오기 예시 (0) | 2023.04.02 |
[유니티] 상자 시스템(2) - 상자 다이얼로그 (0) | 2023.04.02 |
[유니티] 상자 시스템(1) - 상자 데이터 (0) | 2023.04.02 |
[유니티] 제작 시스템(4) - 매니저 (0) | 2023.04.02 |