数据持久化

15.1 为什么需要数据持久化

游戏数据持久化是指将运行时数据保存到磁盘,在下次运行时恢复。常见场景:

  • 存档/读档
  • 玩家设置(音量、画质)
  • 排行榜分数
  • 解锁进度

15.2 PlayerPrefs

PlayerPrefs 是最简单的持久化方式,以键值对形式存储。

存储数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 保存数据
PlayerPrefs.SetInt("HighScore", 1000);
PlayerPrefs.SetFloat("MusicVolume", 0.8f);
PlayerPrefs.SetString("PlayerName", "ByteFisher");
PlayerPrefs.Save(); // 立即写入磁盘

// 读取数据(需提供默认值)
int highScore = PlayerPrefs.GetInt("HighScore", 0);
float musicVol = PlayerPrefs.GetFloat("MusicVolume", 1f);
string playerName = PlayerPrefs.GetString("PlayerName", "Player");

// 检查键是否存在
if (PlayerPrefs.HasKey("HighScore"))
{
// 存在
}

// 删除
PlayerPrefs.DeleteKey("HighScore");
PlayerPrefs.DeleteAll(); // 删除所有

PlayerPrefs 存储位置

平台 路径
Windows HKCU\Software\公司名\产品名(注册表)或 %APPDATA%\Unity
macOS ~/Library/Preferences/com.公司名.产品名.plist
Android /data/data/包名/shared_prefs/
iOS NSUserDefaults

局限:PlayerPrefs 只能存基本类型,不适合复杂数据结构。


15.3 JSON 序列化

JSON 是 Unity 中推荐的数据持久化格式,支持复杂对象。

使用 JsonUtility

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
using UnityEngine;

[System.Serializable]
public class PlayerData
{
public string playerName;
public int level;
public float health;
public Vector3 position;
public int[] inventoryIds;
}

public class SaveManager : MonoBehaviour
{
private string SavePath => Application.persistentDataPath + "/save.json";

public void Save(PlayerData data)
{
string json = JsonUtility.ToJson(data, prettyPrint: true);
File.WriteAllText(SavePath, json);
Debug.Log($"存档已保存到:{SavePath}");
}

public PlayerData Load()
{
if (File.Exists(SavePath))
{
string json = File.ReadAllText(SavePath);
return JsonUtility.FromJson<PlayerData>(json);
}
return null;
}

public void DeleteSave()
{
if (File.Exists(SavePath))
{
File.Delete(SavePath);
}
}
}

使用 Newtonsoft.Json(功能更强大)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 安装:Package Manager → Newtonsoft.Json
using Newtonsoft.Json;

public class SaveManagerJsonNet : MonoBehaviour
{
public void Save(PlayerData data)
{
string json = JsonConvert.SerializeObject(data, Formatting.Indented);
File.WriteAllText(SavePath, json);
}

public PlayerData Load()
{
if (File.Exists(SavePath))
{
string json = File.ReadAllText(SavePath);
return JsonConvert.DeserializeObject<PlayerData>(json);
}
return null;
}
}

15.4 ScriptableObject

ScriptableObject 是一种数据容器,用于在编辑器中创建和配置数据资产。

定义数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[CreateAssetMenu(fileName = "NewItem", menuName = "Game/Item")]
public class ItemData : ScriptableObject
{
public string itemName;
public int itemID;
[TextArea] public string description;
public Sprite icon;
public ItemType type;
public int maxStack = 1;
public GameObject pickupPrefab;
}

public enum ItemType
{
Weapon, Armor, Consumable, Quest
}

创建和使用

1
2
3
4
5
6
7
8
9
10
11
12
13
// 在 Project 面板:右键 → Create → Game → Item
// 在 Inspector 中配置数据

public class Inventory : MonoBehaviour
{
public List<ItemData> items = new List<ItemData>();

public void AddItem(ItemData item)
{
items.Add(item);
Debug.Log($"获得物品:{item.itemName}");
}
}

ScriptableObject 的优势

  • 编辑器内配置,无需运行即可创建
  • 数据与逻辑分离
  • 多个对象可共享同一数据实例(节省内存)
  • 支持继承和多态

15.5 存档系统设计

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
[System.Serializable]
public class GameSaveData
{
public string saveVersion;
public string saveTime;
public PlayerData player;
public List<EnemyData> enemies;
public List<string> completedQuests;
public Dictionary<string, bool> unlockedAchievements;
}

public class SaveSystem : MonoBehaviour
{
private const string SaveFileName = "save.sav";
private const int MaxSlots = 10;

public static string GetSlotPath(int slotIndex)
{
return $"{Application.persistentDataPath}/slot_{slotIndex}.sav";
}

public static void SaveToSlot(int slotIndex, GameSaveData data)
{
string json = JsonUtility.ToJson(data);
string path = GetSlotPath(slotIndex);
File.WriteAllText(path, json);
}

public static GameSaveData LoadFromSlot(int slotIndex)
{
string path = GetSlotPath(slotIndex);
if (File.Exists(path))
{
string json = File.ReadAllText(path);
return JsonUtility.FromJson<GameSaveData>(json);
}
return null;
}

public static bool SlotExists(int slotIndex)
{
return File.Exists(GetSlotPath(slotIndex));
}
}

15.6 本章小结

  • PlayerPrefs 适合简单设置,复杂数据推荐 JSON
  • Application.persistentDataPath 是跨平台存储目录
  • JsonUtility.ToJson / FromJson 实现数据序列化
  • ScriptableObject 是编辑器中配置数据的利器
  • 设计多存档位系统需规划好数据结构

练习题

  1. 使用 PlayerPrefs 保存音乐音量和画质设置
  2. 实现角色位置和生命值的 JSON 存档/读档
  3. 创建一个 ScriptableObject 武器数据表
  4. 设计一个 3 个存档位的系统,支持保存时间显示
ByteFisher
分享编程技术 · 记录钓鱼乐趣
扫码关注
▸ 扫码关注 ◂
Unity3D开发基础教程 系列
第 15/20 篇
分享: