如今,几乎所有游戏都具备存档功能,这成为了游戏中不可或缺的重要特性。然而,存档功能的实现并不神秘,它是通过将游戏中的数据保存到硬盘上的文件中,以便在下次进入游戏时能够读取这些数据,从而实现玩家能够继续游戏的功能。
在游戏开发中,通常会选择一种数据格式(如JSON、XML或二进制)将数据写入文件中。本文将以JSON格式作为存储数据的示例,因为它易于阅读和编辑。
由于存档内容通常不会单一,而且可能存在多个存档文件,因此我们需要一种通用的方法来管理存档。通过接口,我们可以实现多态性,使得不同的类能够以统一的方式进行存档操作。
接下来,我们将介绍如何通过接口来实现存档功能。通过接口调用函数的形式,不同的类可以实现不同的存档逻辑。
public interface IArchive
{
void Save();
void Load();
}
然后通过存档管理器来统一管理
public class ArchiveSystem
{
protected static Dictionary<string, List<IArchive>> saves = new();
protected static Dictionary<IArchive, List<string>> saveIndex = new();
/// <summary>
/// 存档
/// </summary>
/// <param name="key">存档任务的索引</param>
/// <param name="name">发出存档指令的物体</param>
public static void SaveData(string key, string name = "")
{
if (!saves.ContainsKey(key))
{
Debug.LogWarning($"存档任务<{name}>使用错误索引<{key}>");
return;
}
foreach (var item in saves[key])
{
try
{
item.Save();
}
catch(System.Exception e)
{
Debug.LogError($"存档任务<{name}>存档失败:{e.Message}");
}
}
}
/// <summary>
/// 读档
/// </summary>
/// <param name="key">读档任务的索引</param>
/// <param name="name">发出读档任务的物体</param>
public static void LoadData(string key, string name = "")
{
if (!saves.ContainsKey(key))
{
Debug.LogWarning($"读档任务<{name}>使用错误索<{key}>");
return;
}
foreach (var item in saves[key])
{
try
{
item.Load();
}
catch (System.Exception e)
{
Debug.LogError($"读档任务<{name}>读档失败:{e.Message}");
}
}
}
/// <summary>
/// 增加任务
/// </summary>
/// <param name="key">任务索引</param>
/// <param name="archive">存档接口</param>
/// <param name="name">发出任务的物体</param>
/// <param name="isAllowRepeat">是否允许重复在不同任务添加</param>
/// <param name="isCheck">是否检查在同一任务重复添加</param>
public static void AddTask(string key, IArchive archive, string name = "", bool isAllowRepeat = false, bool isCheck = false)
{
if (!isAllowRepeat && saveIndex.ContainsKey(archive) && saveIndex[archive].Count > 1)
{
string message = string.Join(",", saveIndex[archive]);
Debug.LogError($"对于<{name}>存档接口<{archive}>已存在于任务<{message}>中");
return;
}
if (isCheck && saveIndex[archive].Find(value =>
{
return value == key;
}) != null)
{
Debug.LogError($"对于<{name}>存档接口<{archive}>已存在于任务<{key}>中");
return;
}
if (!saves.ContainsKey(key))
{
saves.Add(key, new List<IArchive>());
}
saves[key].Add(archive);
if (!saveIndex.ContainsKey(archive))
{
saveIndex.Add(archive, new List<string>());
}
saveIndex[archive].Add(key);
string sd = string.Join(",", saveIndex[archive]);
if (isAllowRepeat)
{
Debug.LogWarning($"存档接口<{archive}>已添加到任务<{key}>中,当前任务列表为<{sd}>");
}
}
/// <summary>
/// 对所有任务进行存档
/// </summary>
public static void SaveAll(string name)
{
Debug.LogWarning($"<{name}>发起全部存档命令");
foreach (var item in saves)
{
SaveData(item.Key);
}
}
/// <summary>
/// 加载所有读档任务
/// </summary>
public static void LoadAll(string name)
{
Debug.LogWarning($"<{name}>发起全部读档命令");
foreach (var item in saves)
{
LoadData(item.Key);
}
}
public static void ClearAll(string name)
{
Debug.LogWarning($"<{name}>发起全部清除存档命令");
saves.Clear();
}
public static void RemoveTask(string key, IArchive archive, string name)
{
Debug.LogWarning($"<{name}>发出从任务<{key}>中移除存档接口<{archive}>的命令");
if (saves.ContainsKey(key))
{
saves[key].Remove(archive);
}
}
}
然后实现两个测试类
public class Test1: MonoBehaviour,IArchive
{
private void Start()
{
ArchiveSystem.AddTask("测试", this);
}
public void Save()
{
Debug.Log("存档1");
}
public void Load()
{
Debug.Log("读档1");
}
}
public class Test2 : MonoBehaviour,IArchive
{
private void Start()
{
ArchiveSystem.AddTask("测试", this);
}
public void Save()
{
Debug.Log("存档2");
}
public void Load()
{
Debug.Log("读档2");
}
}
将Test1与Test2分别挂载到Objet1与Object2物体,然后新建GameTest
脚本
public class GameTest : MonoBehaviour
{
[ContextMenu("测试存档功能")]
public void SaveTest()
{
ArchiveSystem.SaveData("测试");
}
[ContextMenu("测试读档功能")]
public void LoadTest()
{
ArchiveSystem.LoadData("测试");
}
}
挂载到Test物体,点击按钮,结果如图
改写Test1
脚本,如下
public class Test1 : MonoBehaviour, IArchive
{
private void Start()
{
ArchiveSystem.AddTask("测试", this);
}
public void Save()
{
string data = JsonUtility.ToJson(transform.position);
File.WriteAllText("./test.json", data);
Debug.Log("存档1" + data);
}
public void Load()
{
string data = File.ReadAllText("./test.json");
transform.position = JsonUtility.FromJson<Vector3>(data); ;
Debug.Log("读档1" + data);
}
}
运行,效果如下
可以看到,存档系统正常工作。另外需要特别提示的一点是,存档系统相对较为消耗性能。因此,务必避免将存档相关的函数调用写入到游戏的Update方法中,最好是在必要时进行调用,而且尽量保证一个关键操作只调用一次。这样可以有效地降低存档操作对游戏性能的影响,确保游戏的流畅运行。