数据持久化多种方案的随笔记录

数据持久化常用的方案

在游戏开发中,数据持久化是指将游戏的状态或其他信息保存下来,以便在下一次游戏运行时能够恢复这些信息。这对于保持游戏进度、玩家偏好设置以及其他重要数据至关重要。那么, 都有哪些常用的游戏数据持久化方案呢?

1. PlayerPrefs

PlayerPrefs 是 Unity 提供的一个简单的键值对存储系统,适用于存储轻量级且非敏感的数据,例如玩家的分数、音量设置等。PlayerPrefs 的数据会随着应用程序被卸载而清除。

示例代码:

1
2
3
4
5
6
// 保存数据
PlayerPrefs.SetInt("HighScore", highScore);
PlayerPrefs.Save();

// 读取数据
int highScore = PlayerPrefs.GetInt("HighScore", 0);

2. 文件系统 (File I/O)

使用文件系统可以将数据以文件的形式保存在磁盘上,适用于保存较大的数据结构或二进制数据。可以通过序列化机制将复杂的数据结构转换成文件。

示例代码:

1
2
3
4
5
6
// 保存数据
string path = Application.persistentDataPath + "/savegame.dat";
System.IO.File.WriteAllBytes(path, data);

// 读取数据
byte[] data = System.IO.File.ReadAllBytes(path);

3. JSON 序列化

JSON 是一种轻量级的数据交换格式,易于人阅读和编写,同时也易于机器解析和生成。Unity 支持将对象序列化为 JSON 字符串,然后可以将 JSON 字符串写入文件或通过网络传输。

示例代码:(下文有详细演示跳转)

1
2
3
4
5
6
7
8
// 保存数据
string json = JsonUtility.ToJson(gameState);
string path = Application.persistentDataPath + "/savegame.json";
System.IO.File.WriteAllText(path, json);

// 读取数据
string json = System.IO.File.ReadAllText(path);
GameSaveData gameState = JsonUtility.FromJson<GameSaveData>(json);

4. XML 序列化

XML 是另一种常用的数据交换格式,虽然比 JSON 复杂一些,但在某些情况下可能更适用。Unity 支持使用 System.Xml 命名空间来序列化和反序列化 XML 数据。

5. 二进制序列化

对于非常大的数据集或需要高性能的应用场景,可以考虑使用二进制序列化。Unity 支持 BinaryFormatter 类来序列化对象。

代码示例位于下文跳转

6. SQLite 数据库

SQLite 是一个轻量级的嵌入式数据库引擎,非常适合用于移动设备和桌面应用。它可以用来存储复杂的数据结构,并支持 SQL 查询语言。

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 使用 SQLite 的基本示例
using (var conn = new SQLiteConnection("Data Source=database.db;Version=3;"))
{
conn.Open();
// 创建表
conn.Execute(@"CREATE TABLE IF NOT EXISTS Players (
ID INTEGER PRIMARY KEY AUTOINCREMENT,
Name TEXT NOT NULL,
Score INTEGER NOT NULL)");

// 插入数据
conn.Execute("INSERT INTO Players (Name, Score) VALUES (@name, @score)",
new { name = "Player1", score = 100 });

// 查询数据
var players = conn.Query<Player>("SELECT * FROM Players WHERE Score > @score", new { score = 50 });
}

7. 云存储

对于多人在线游戏,可以考虑使用云存储服务来保存数据,如 Firebase、AWS S3 或 Azure Blob 存储等。

对比Json方案和二进制方案

JSON (JavaScript Object Notation)

优点
  1. 可读性强:JSON 是一种基于文本的格式,可以直接阅读和调试。
  2. 跨平台兼容性:JSON 被广泛支持,几乎所有的现代编程语言都有相应的库来处理 JSON 数据。
  3. 灵活性:JSON 支持动态添加新的字段,即使客户端和服务器端的 JSON 结构发生变化,只要保持兼容的核心字段不变,通常不需要对现有代码做大的改动。
  4. 易于解析:大多数语言都有内置的支持来解析 JSON 数据,因此解析起来相对容易。
  5. 扩展性:JSON 支持复杂的数据类型,如嵌套的对象和数组。
缺点
  1. 空间占用大:由于 JSON 是基于文本的,所以相比于二进制格式,它通常会占用更多的存储空间和网络带宽。
  2. 序列化/反序列化速度较慢:文本格式的解析通常比二进制格式慢。
  3. 安全性较低:JSON 数据容易被篡改,因为它可以直接被编辑。

二进制序列化

优点
  1. 紧凑高效:二进制格式通常比 JSON 更紧凑,占用较少的存储空间和网络带宽。
  2. 序列化/反序列化速度快:二进制数据的读写通常比文本格式快。
  3. 安全性较高:二进制数据难以直接阅读和编辑,这增加了数据的安全性。
缺点
  1. 可读性差:二进制数据很难直接阅读,这对于调试和故障排查来说是个挑战。
  2. 平台相关性:二进制数据的解释可能会依赖于特定的平台和字节序,因此在不同平台上可能需要额外的工作来确保一致性。
  3. 兼容性问题:当数据结构发生变化时,可能需要更新所有相关的序列化和反序列化代码。
  4. 灵活性较低:二进制格式通常不支持动态添加新的字段,如果数据结构发生变化,可能需要重新定义整个序列化格式。

代码演示两种方案

假设有一个 Person 类,包含姓名和年龄两个字段。

1
2
3
4
5
6
7
8
9
10
11
public class Person
{
public string Name;
public int Age;

public Person(string name, int age)
{
Name = name;
Age = age;
}
}
JSON 序列化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
using UnityEngine;
using System.IO;

public static class JsonSerialization
{
public static void SerializeToJson()
{
Person person = new Person("Alice", 30);
string jsonString = JsonUtility.ToJson(person);
Debug.Log(jsonString); // 输出: {"Name":"Alice","Age":30}

// 写入文件
File.WriteAllText("person.json", jsonString);
}

public static Person DeserializeFromJson()
{
string jsonString = File.ReadAllText("person.json");
return JsonUtility.FromJson<Person>(jsonString);
}
}
二进制序列化
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
using UnityEngine;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;

public static class BinarySerialization
{
public static void SerializeToBinary()
{
Person person = new Person("Bob", 25);

// 使用 BinaryFormatter 进行序列化
using (FileStream stream = File.Create("person.bin"))
{
BinaryFormatter formatter = new BinaryFormatter();
formatter.Serialize(stream, person);
}
}

public static Person DeserializeFromBinary()
{
using (FileStream stream = File.OpenRead("person.bin"))
{
BinaryFormatter formatter = new BinaryFormatter();
return (Person)formatter.Deserialize(stream);
}
}
}

总结

  • 如果需要跨平台兼容性和易于调试,且对网络带宽和存储空间的要求不高,那么 JSON 是更好的选择。
  • 如果需要最小的存储空间和最快的序列化/反序列化速度,且能够接受二进制格式带来的不便,那么二进制序列化可能是更合适的选择。

另外, 在实际应用中,还可以考虑使用像 MessagePack 这样的高效二进制序列化库,它结合了 JSON 的易用性和二进制格式的高效性。

MessagePack简单示例

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
using UnityEngine;
using System.IO;
using System.Linq;
using MessagePack;

// 标记类以便 MessagePack 可以识别它
[MessagePackObject]
public class Person
{
[Key(0)] public string Name;
[Key(1)] public int Age;

public Person() { }

public Person(string name, int age)
{
Name = name;
Age = age;
}
}

public class MessagePackSerializationExample : MonoBehaviour
{
private void Start()
{
SerializeAndDeserialize();
}

private void SerializeAndDeserialize()
{
Person person = new Person("Alice", 30);

// 序列化
// 将 Person 对象序列化为 MessagePack 字节数组
byte[] messagePackData = MessagePackSerializer.Serialize(person);

// 反序列化
// 反序列化 MessagePack 字节数组为 Person 对象
Person deserializedPerson = MessagePackSerializer.Deserialize<Person>(messagePackData);

Debug.Log($"原始的 Person: Name={person.Name}, Age={person.Age}");
Debug.Log($"反序列化后的 Person: Name={deserializedPerson.Name}, Age={deserializedPerson.Age}");
}
}

使用Json序列化的一些要注意的点

// TODO: 未完待续