游戏存档
基于 JSON + 接口 (Interface) + 单例管理器 实现存档
在 Unity 中实现“数据与逻辑分离”的存档架构,核心思想是:游戏对象负责逻辑,纯 C# 类负责携带数据,管理器负责磁盘 IO。
在 Unity 中实现“数据与逻辑分离”的存档架构,核心思想是:游戏对象负责逻辑,纯 C# 类负责携带数据,管理器负责磁盘 IO。
以下是一个基于 JSON + 接口 (Interface) + 单例管理器 的完整代码示例:
1. 定义数据传输对象 (DTO)
这是一个纯粹的 C# 类,只包含变量,用于被序列化。
C#
using System;
using System.Collections.Generic;
using UnityEngine;
[Serializable]
public class GameSaveData
{
// 玩家数据
public Vector3 playerPosition;
public float playerHealth;
// 任务/物品数据(示例:使用字典需特殊处理,此处用列表简化)
public List<string> inventoryItems = new List<string>();
// 游戏状态
public int currentLevel;
public string lastSaveTime;
public GameSaveData()
{
// 默认构造函数,初始化默认值
lastSaveTime = DateTime.Now.ToString();
}
}
2. 定义存档接口 (ISaveable)
任何需要存档的组件(如玩家、敌人、任务系统)都必须实现这个接口。
C#
public interface ISaveable
{
// 将当前游戏状态存入 Data 对象
void CaptureState(GameSaveData data);
// 从 Data 对象恢复游戏状态
void RestoreState(GameSaveData data);
}
3. 实现具体逻辑组件
例如,你的玩家控制脚本:
C#
using UnityEngine;
public class PlayerController : MonoBehaviour, ISaveable
{
public float health = 100f;
public void CaptureState(GameSaveData data)
{
data.playerPosition = transform.position;
data.playerHealth = health;
Debug.Log("玩家数据已捕获");
}
public void RestoreState(GameSaveData data)
{
transform.position = data.playerPosition;
health = data.playerHealth;
Debug.Log("玩家数据已恢复");
}
// 这里的逻辑代码(如移动、受伤)与存档代码互不干扰
void Update() { /* 逻辑代码 */ }
}
4. 存档管理器 (SaveManager)
这是核心大脑,负责遍历所有实现接口的对象,并将结果写入磁盘。
C#
using UnityEngine;
using System.IO;
using System.Linq;
public class SaveManager : MonoBehaviour
{
public static SaveManager Instance { get; private set; }
private string savePath;
private const string SAVE_FILENAME = "gamesave.json";
private void Awake()
{
Instance = this;
savePath = Path.Combine(Application.persistentDataPath, SAVE_FILENAME);
}
// 核心存档方法
public void SaveGame()
{
GameSaveData data = new GameSaveData();
// 1. 找到场景中所有实现了 ISaveable 接口的脚本
var saveables = FindObjectsOfType<MonoBehaviour>().OfType<ISaveable>();
// 2. 让每个脚本把自己的数据填进 data 对象
foreach (var saveable in saveables)
{
saveable.CaptureState(data);
}
// 3. 序列化为 JSON 字符串
string json = JsonUtility.ToJson(data, true);
// 4. 写入文件
File.WriteAllText(savePath, json);
Debug.Log($"游戏已存档至: {savePath}");
}
// 核心读档方法
public void LoadGame()
{
if (!File.Exists(savePath))
{
Debug.LogWarning("未找到存档文件");
return;
}
// 1. 读取文件并反序列化
string json = File.ReadAllText(savePath);
GameSaveData data = JsonUtility.FromJson<GameSaveData>(json);
// 2. 找到所有实现了接口的对象并分发数据
var saveables = FindObjectsOfType<MonoBehaviour>().OfType<ISaveable>();
foreach (var saveable in saveables)
{
saveable.RestoreState(data);
}
Debug.Log("游戏存档已加载");
}
}
5. 如何使用?
在你的 UI 按钮或者触发器中直接调用:
C#
// 点击存档按钮
public void OnClickSave()
{
SaveManager.Instance.SaveGame();
}
// 点击读档按钮
public void OnClickLoad()
{
SaveManager.Instance.LoadGame();
}
为什么这是最佳实践?
- 低耦合:
SaveManager不需要知道PlayerController的存在,它只认ISaveable接口。如果你以后增加了一个“任务系统”,只需让它实现接口,无需修改SaveManager的代码。 - 易于扩展:如果你想从 JSON 切换到二进制存储,你只需要修改
SaveManager里的两行代码(序列化和反序列化部分),而不需要动所有的游戏逻辑脚本。 - 单一权责:
PlayerController只负责管理自己的血量和坐标,而不必关心文件是怎么写入磁盘的。 - 调试友好:由于使用了 JSON,你可以直接打开
persistentDataPath下的文件,手动修改血量或坐标来测试游戏。
进阶提示:在实际项目中,建议将 FindObjectsOfType 替换为在 OnEnable/OnDisable 时主动向 SaveManager 注册/注销,以提高性能。
- 1. 定义数据传输对象 (DTO)
- 2. 定义存档接口 (ISaveable)
- 3. 实现具体逻辑组件
- 4. 存档管理器 (SaveManager)
- 5. 如何使用?
- 为什么这是最佳实践?