正文
主要方法:
- ScriptableObject
- PlayerPrefs
- JSON
- XML
- 数据库(如Sqlite)
1. PlayerPerfs
PlayerPrefs 存储的数据是全局共享的,它们存储在用户设备的本地存储中,并且可以被应用程序的所有部分访问。这意味着,无论在哪个场景、哪个脚本中,只要是同一个应用程序中的代码,都可以读取和修改 PlayerPrefs 中的数据。
这意味着耦合性的增加、安全性的降低。它适合存储少量的基本数据(比如玩家的偏好设置、游戏设置、游戏进度等),但不适合存储大量或复杂的数据结构。
注意:
- 每次Set完数据要调用
PlayerPrefs.Save()
把数据写入磁盘。 - Get有两个参数,第一个是键名,第二个是没找到时传入的默认值。
csharp
int highScore = 1000;
PlayerPrefs.SetInt("HighScore", highScore);
// 记得保存
PlayerPrefs.Save();
// 没找到就返回3
int score = PlayerPrefs.GetInt("Score", 3);
2. ScriptableObject
ScriptableObject的值在播放模式之后不会恢复原样,会保留修改
可以用于只读和读写两种数据,不过原则上还是只用于只读数据。
ScriptableObject 并不依赖于游戏对象(GameObject),也不受场景加载和卸载的影响。它的生命周期是由 Unity 引擎管理的。
使用流程:
- 派生自ScriptableObject创建基类
- 实例化后由编辑器对实例进行配置
- 其他C#脚本使用某个实例
- 合理使用
OnValidate()
方法(这是ScriptableObject中的值更改时触发的事件,但是仅限在编辑器使用)
下述代码在数值发生变化时触发定义的valueChanged事件。注意需要在其他脚本中向 valueChanged 事件添加侦听器才能响应
csharp
// 代码源自参考链接2
using UnityEngine;
using UnityEngine.Events;
[CreateAssetMenu(fileName = "NewWeapon", menuName = "Game/Weapon")]
public class WeaponScriptableObject : ScriptableObject
{
public string weaponName;
public int damage;
public Sprite icon;
[SerializeField, Range(0, 100)]
private int maxHealth;
// Define a UnityEvent with no arguments
public UnityEvent valueChanged;
#if UNITY_EDITOR
private void OnValidate()
{
// This method is called in the Unity Editor whenever a value is changed.
// Invoke the UnityEvent when values change.
if (UnityEditor.EditorApplication.isPlaying)
{
valueChanged.Invoke(); // Fire the UnityEvent
}
}
#endif
}
这里要注意的是类似于[CreateAssetMenu(fileName = "mySharedData", menuName = "SharedData/MySharedData", order = 1)]
这样的东西,意思是:
- fileName:新创建实例的默认文件名为"mySharedData"。
- menuName:在"Assets/Create"菜单中显示的类型条目名称为"SharedData"下的"MySharedData"。
- order:菜单项在"Assets/Create"菜单中的位置为1(显示靠前的优先级)
由于SO尽量存储运行时不更改的数据,所以要修改当前的生命值会考虑如下方法。此处PlayerXP实际上是个引用,在Unity中,当一个类的成员是另一个类的实例时,默认情况下它就是引用类型,不需要额外的标记(我是对比着[SerializeReference]
来看,见下文 )
csharp
public class PlayerXP {
public int XP = 100;
}
public class PlayerData : ScriptableObject {
public PlayerXP PlayerXP;
}
PlayerData playerData = GetComponent<PlayerData>();
playerData.PlayerXP.XP = 200;
注:引用类型的序列化通常会占用更多的存储空间和加载时间(性能降低)
3. 序列化:Json、XML与二进制
csharp
using UnityEngine;
[System.Serializable]
public class PlayerData
{
public string playerName;
public int playerScore;
}
关于序列化:
- 必须是
public
类,其中的字段或者属性必须是可序列化的 - 类中的方法不会被序列化
- 构造函数不可以被序列化
不可以序列化的数据类型:
- 静态类或静态成员(Static Members)
- 委托(Delegate)
- 事件(Event)
- 指针(Pointer)
- 索引器(Indexers)
静态成员是属于整个类的,但是序列化和反序列化是构造一个类的实例的
csharp
// 保存
string json = JsonUtility.ToJson(sourceObject);
System.IO.File.WriteAllText("playerData.json", json);
// 读取
string json = System.IO.File.ReadAllText("playerData.json");
SomeClass loadedPlayer = JsonUtility.FromJson<SomeClass>(json);
json是字符串文本,XML是标记语言(本身还是文本),二进制就是01序列。二进制在数据存储和传输的效率、紧凑性和速度上占有优势(但丧失了内容的可读性)
二进制有很多种方案:
- protobuf (Protocol Buffers,谷歌研发的一种二进制序列化格式)
- MessagePack
我们可以在官方仓库找到使用说明Github - MessagePack-CSharp
注:几个含Serialize的相关属性
[Serializable]
是一个 C# 中的特性,它告诉编译器这个类可以被序列化。[SerializeReference]
是 Unity 2019.3 引入的新特性,用于处理多态对象的序列化,使得可以在 Inspector 窗口中为该字段分配任意继承自同一父类的对象。[SerializeField]
(这个是把Private变量暴露到Inspector中的,跟上面那俩没关系)
注:标记为 [Serializable]
的类需要自行确保其内容是可序列化的。该标记的作用仅是告诉编译器这个类可以被序列化,但是不对内容做任何保证,如果存在不可被序列化的字段则会被忽略(不会引起报错)。此外还要避免类中存在循环引用(例如类 A 包含类 B 的实例,而类 B 又包含类 A 的实例)
csharp
// example
using UnityEngine;
using System;
[Serializable]
public class Shape
{
public float area;
public virtual void Draw() { }
}
[Serializable]
public class Circle : Shape
{
public float radius;
public override void Draw()
{
Debug.Log("Drawing a circle");
}
}
[Serializable]
public class Rectangle : Shape
{
public float width;
public float height;
public override void Draw()
{
Debug.Log("Drawing a rectangle");
}
}
public class ShapeHolder : MonoBehaviour
{
[SerializeReference]
public Shape shape;
}
4. 数据库
数据库通常用于存储大量结构化数据,例如用户信息、游戏配置、游戏关卡数据、成就和统计信息等。相较于ScriptableObject、PlayerPrefs、JSON和XML文件,数据库的优势在于能够更灵活地管理和查询大量数据,并支持复杂的数据结构和关联查询。
数据库的优势包括:
- 数据结构灵活:数据库可以存储和管理复杂的数据结构,支持表与表之间的关联,适用于需要大量结构化数据的场景。
- 查询功能强大:数据库引擎提供强大的查询功能,可以进行复杂的数据筛选、排序和统计分析。
- 数据持久化:数据库中的数据可以持久保存,不受应用程序生命周期的影响,适用于长期存储和管理数据。
- 并发处理:数据库引擎通常支持并发处理,能够处理多个用户同时对数据进行读写的情况。
- 数据安全性:数据库可以提供数据加密、权限控制等安全功能,保护数据不被未授权访问。
参考与进一步阅读
PavCreations - Data persistence or how to save / load game data in Unity
Medium - A Beginner's Guide to Storing and Retrieving Data in Unity
Medium - "How to Harness the Power of Scriptable Objects in Unity"