功能使用说明和示意
本功能是对Unity中,借助Newtonsoft.Json和PlayerPrefs实现简单的存储功能中描述的功能的改进。
提供如下方法:
- 保存特定数据:将obj存储起来,需提供一个key。后续可通过key获得之前存储的该数据、删除该数据。
- 载入特定数据:载入之前存储的某数据。返回值表示是否载入成功。
- 删除特定数据:传入key,删除对应数据。
-
删除所有数据:删除所有已存储的数据。
存储功能示意.gif
使用说明:
将下面的SaveSys.cs文件拖入工程(其中包含了多个类)。
修改字段GameName为当前游戏的名称。
为了序列化(以便储存),必须使用下面的特性修改要存储的类。
[System.Serializable]
为了在WebGL平台使用,还需进行如下操作(具体原因详见下文):
如图创建这样的目录:
![](https://img.haomeiwen.com/i10492731/4554725d4d5f751d.png)
图中的Save文件的内容详见下文。
功能解析
实现一个SaveSys的单例类,实现Save的相关功能。
该类中的字段主要是对文件路径的描述:使用二进制存储数据,将数据保存到对应名称的路径下。
实现一个SerializationManager来序列化和反序列化数据、生成和读取相应的存储文件。
对于某些无法序列化的数据类型(如Vector3、Quaternion),必须自己实现相应的Surrogate,提供给formatter用于解析对应类型的数据。
详见下面代码的实现。
在WebGL平台使用时需额外做的
参考:【Unity记录】问题:WebGL游戏保存数据到Application.persistentDataPath不生效
创建文件Save.jslib放到目录Plugins\WebGL下。
Save.jslib
mergeInto(LibraryManager.library,
{
// 刷新数据到IndexedDB
SyncDB: function()
{
FS.syncfs(false, function(err)
{
if (err)
{
console.log("syncfs error:"+err);
}
});
}
});
在SerializationManager的相关位置添加相应内容(详见下面的代码)
功能代码
SaveSys.cs
using System;
using System.Runtime.InteropServices;
using System.Runtime.Serialization;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
#if UNITY_WEBGL && !UNITY_EDITOR
using System.Runtime.InteropServices;
#endif
public class SaveSys
{
private static SaveSys _instance;
public static SaveSys Instance
{
get
{
if (_instance == null)
{
_instance = new SaveSys();
}
return _instance;
}
}
// 用于标识当前游戏
private const string GameName = "MySaveTest";
private const string DelSign = "_Del_";
private string CurTimeStr { get { return DateTime.Now.ToString("yyyy_MM_dd_HH_mm_ss"); } }
private string SaveDirectory { get { return Application.persistentDataPath + "/Saves/" + GameName; } }
private string SaveSuffix { get { return ".save"; } }
private string KeyNameToPath(string name)
{
return $"{SaveDirectory}/{name}{SaveSuffix}";
}
public SaveSys()
{
}
public void MyDestroy()
{
_instance = null;
}
// 删除特定数据:实际是将目标文件改名
public void Delete(string name)
{
var oldPath = KeyNameToPath(name);
if (System.IO.File.Exists(oldPath))
{
var newPath = KeyNameToPath(name + DelSign + CurTimeStr);
System.IO.File.Move(oldPath, newPath);
}
}
// 删除本游戏所有已存储的数据:实际是将目标文件夹改名
public void DeleteAll()
{
if (System.IO.Directory.Exists(SaveDirectory))
{
var newPath = SaveDirectory + DelSign + CurTimeStr;
System.IO.Directory.Move(SaveDirectory, newPath);
}
}
// 保存特定数据
public void Save(string name, object data)
{
if (data != null)
{
var path = KeyNameToPath(name);
SerializationManager.Save(path, data);
}
}
// 载入特定数据
public bool Load<T>(string name, out T obj) where T : class
{
var path = KeyNameToPath(name);
obj = SerializationManager.Load(path) as T;
return obj != null;
}
}
public class SerializationManager
{
#if UNITY_WEBGL && !UNITY_EDITOR
[DllImport("__Internal")]
private static extern void SyncDB();
#endif
public static bool Save(string path, object saveData)
{
BinaryFormatter formatter = GetBinaryFormatter();
var directory = System.IO.Path.GetDirectoryName(path);
if (!Directory.Exists(directory))
{
Directory.CreateDirectory(directory);
}
FileStream file = File.Create(path);
formatter.Serialize(file, saveData);
file.Close();
#if UNITY_WEBGL && !UNITY_EDITOR
SyncDB();
#endif
return true;
}
public static object Load(string path)
{
if (!File.Exists(path))
{
return null;
}
BinaryFormatter formatter = GetBinaryFormatter();
FileStream file = File.Open(path, FileMode.Open);
try
{
object save = formatter.Deserialize(file);
file.Close();
return save;
}
catch
{
Debug.LogErrorFormat("Failed to load file at {0}", path);
file.Close();
return null;
}
}
public static BinaryFormatter GetBinaryFormatter()
{
BinaryFormatter formatter = new BinaryFormatter();
SurrogateSelector selector = new SurrogateSelector();
Vector3SerializationSurrogate vector3Surrogate = new Vector3SerializationSurrogate();
QuaternionSerializationSurrogate quaternionSurrogate = new QuaternionSerializationSurrogate();
selector.AddSurrogate(typeof(Vector3), new StreamingContext(StreamingContextStates.All), vector3Surrogate);
selector.AddSurrogate(typeof(Quaternion), new StreamingContext(StreamingContextStates.All), quaternionSurrogate);
formatter.SurrogateSelector = selector;
return formatter;
}
}
public class Vector3SerializationSurrogate : ISerializationSurrogate
{
public void GetObjectData(object obj, SerializationInfo info, StreamingContext context)
{
Vector3 v3 = (Vector3)obj;
info.AddValue("x", v3.x);
info.AddValue("y", v3.y);
info.AddValue("z", v3.z);
}
public object SetObjectData(object obj, SerializationInfo info, StreamingContext context, ISurrogateSelector selector)
{
Vector3 v3 = (Vector3)obj;
v3.x = (float)info.GetValue("x", typeof(float));
v3.y = (float)info.GetValue("y", typeof(float));
v3.z = (float)info.GetValue("z", typeof(float));
obj = v3;
return obj;
}
}
public class QuaternionSerializationSurrogate : ISerializationSurrogate
{
public void GetObjectData(object obj, SerializationInfo info, StreamingContext context)
{
Quaternion quaternion = (Quaternion)obj;
info.AddValue("x", quaternion.x);
info.AddValue("y", quaternion.y);
info.AddValue("z", quaternion.z);
info.AddValue("w", quaternion.w);
}
public object SetObjectData(object obj, SerializationInfo info, StreamingContext context, ISurrogateSelector selector)
{
Quaternion quaternion = (Quaternion)obj;
quaternion.x = (float)info.GetValue("x", typeof(float));
quaternion.y = (float)info.GetValue("y", typeof(float));
quaternion.z = (float)info.GetValue("z", typeof(float));
quaternion.w = (float)info.GetValue("w", typeof(float));
obj = quaternion;
return obj;
}
}
测试代码
PlayerData.cs
using System.Text;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[System.Serializable]
public class PlayerData
{
public string PlayerName;
public int Currency;
public int Experience;
public Dictionary<string, string> Dict;
public override string ToString()
{
StringBuilder sb = new StringBuilder();
sb.AppendLine("玩家数据:");
sb.AppendLine("名称:" + PlayerName);
sb.AppendLine("货币:" + Currency.ToString());
sb.AppendLine("经验:" + Experience.ToString());
sb.AppendLine("字典:");
foreach (var d in Dict)
{
sb.AppendLine($"{d.Key}-{d.Value}");
}
return sb.ToString();
}
public static PlayerData GenerateDataRandomly()
{
PlayerData curData = new PlayerData();
curData.PlayerName = "Player_" + UnityEngine.Random.Range(0, 1000).ToString().PadLeft(3, '0');
curData.Currency = UnityEngine.Random.Range(0, 10000);
curData.Experience = UnityEngine.Random.Range(0, 10000);
var dictCnt = UnityEngine.Random.Range(0, 5);
curData.Dict = new Dictionary<string, string>();
for (int i = 0; i < dictCnt; i++)
{
curData.Dict.Add(UnityEngine.Random.Range(0, 10000).ToString(), UnityEngine.Random.Range(0, 10000).ToString());
}
return curData;
}
}
ToyData.cs
using System.Text;
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public enum ToyType
{
Car,
Penguin,
Pig
}
[System.Serializable]
public class ToyData
{
public string Id;
public ToyType ToyType;
public Vector3 Position;
public Quaternion Rotation;
public static ToyType GetToyTypeRandomly()
{
ToyType[] myEnumsArray = (ToyType[])Enum.GetValues(typeof(ToyType));
return myEnumsArray[UnityEngine.Random.Range(0, myEnumsArray.Length)];
}
public override string ToString()
{
StringBuilder sb = new StringBuilder();
sb.AppendLine("玩具数据:");
sb.AppendLine("Id:" + Id);
sb.AppendLine("类型:" + ToyType.ToString());
sb.AppendLine("位置:" + Position.ToString());
sb.AppendLine("旋转:" + Rotation.ToString());
return sb.ToString();
}
public static ToyData GenerateDataRandomly()
{
var curToy = new ToyData();
curToy.Id = UnityEngine.Random.Range(0, 1000).ToString().PadLeft(3, '0');
curToy.ToyType = ToyData.GetToyTypeRandomly();
curToy.Position = new Vector3(UnityEngine.Random.Range(0, 100), UnityEngine.Random.Range(0, 100), UnityEngine.Random.Range(0, 100));
curToy.Rotation = new Quaternion(UnityEngine.Random.Range(0, 100), UnityEngine.Random.Range(0, 100), UnityEngine.Random.Range(0, 100), UnityEngine.Random.Range(0, 100));
return curToy;
}
}
SaveTest.cs
using System.ComponentModel;
using System.Net.Mime;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class SaveTest : MonoBehaviour
{
[SerializeField]
private Text _dataText;
private PlayerData _playerData;
private ToyData _toyData;
[SerializeField]
private Button _generateAndShowPlayerDataBtn;
[SerializeField]
private Button _generateAndShowToyDataBtn;
[SerializeField]
private Button _savePlayerDataBtn;
[SerializeField]
private Button _saveToyDataBtn;
[SerializeField]
private Button _delPlayerDataBtn;
[SerializeField]
private Button _delToyDataBtn;
[SerializeField]
private Button _loadAndShowPlayerDataBtn;
[SerializeField]
private Button _loadAndShowToyDataBtn;
[SerializeField]
private Button _delAllBtn;
private void Start()
{
Init();
}
private void Update()
{
MyUpdate();
}
private void OnDestroy()
{
MyDestroy();
}
private void Init()
{
_generateAndShowPlayerDataBtn.onClick.AddListener(OnClickGenerateAndShowPlayerDataBtn);
_generateAndShowToyDataBtn.onClick.AddListener(GenerateAndShowToyData);
_savePlayerDataBtn.onClick.AddListener(OnClickSavePlayerDataBtn);
_saveToyDataBtn.onClick.AddListener(OnClickSaveToyDataBtn);
_delPlayerDataBtn.onClick.AddListener(OnClickDelPlayerDataBtn);
_delToyDataBtn.onClick.AddListener(OnClickDelToyDataBtn);
_loadAndShowPlayerDataBtn.onClick.AddListener(OnClickLoadAndShowPlayerDataBtn);
_loadAndShowToyDataBtn.onClick.AddListener(OnClickLoadAndShowToyDataBtn);
_delAllBtn.onClick.AddListener(OnClickDelAllBtn);
}
private void MyUpdate()
{
}
private void MyDestroy()
{
_generateAndShowPlayerDataBtn.onClick.RemoveListener(OnClickGenerateAndShowPlayerDataBtn);
_generateAndShowToyDataBtn.onClick.RemoveListener(GenerateAndShowToyData);
_savePlayerDataBtn.onClick.RemoveListener(OnClickSavePlayerDataBtn);
_saveToyDataBtn.onClick.RemoveListener(OnClickSaveToyDataBtn);
_delPlayerDataBtn.onClick.RemoveListener(OnClickDelPlayerDataBtn);
_delToyDataBtn.onClick.RemoveListener(OnClickDelToyDataBtn);
_loadAndShowPlayerDataBtn.onClick.RemoveListener(OnClickLoadAndShowPlayerDataBtn);
_loadAndShowToyDataBtn.onClick.RemoveListener(OnClickLoadAndShowToyDataBtn);
_delAllBtn.onClick.RemoveListener(OnClickDelAllBtn);
}
private void OnClickGenerateAndShowPlayerDataBtn()
{
GenerateAndShowPlayerData();
}
private void OnClickGenerateAndShowToyDataBtn()
{
GenerateAndShowToyData();
}
private void OnClickSavePlayerDataBtn()
{
SavePlayerData();
}
private void OnClickSaveToyDataBtn()
{
SaveToyData();
}
private void OnClickDelPlayerDataBtn()
{
DelPlayerData();
}
private void OnClickDelToyDataBtn()
{
DelToyData();
}
private void OnClickLoadAndShowPlayerDataBtn()
{
LoadAndShowPlayerData();
}
private void OnClickLoadAndShowToyDataBtn()
{
LoadAndShowToyData();
}
private void OnClickDelAllBtn()
{
DelAll();
}
// 随机生成并显示玩家数据
private void GenerateAndShowPlayerData()
{
_playerData = PlayerData.GenerateDataRandomly();
_dataText.text = _playerData.ToString();
}
// 随机生成并显示玩具数据
private void GenerateAndShowToyData()
{
_toyData = ToyData.GenerateDataRandomly();
_dataText.text = _toyData.ToString();
}
// 保存玩家数据
private void SavePlayerData()
{
SaveSys.Instance.Save(nameof(_playerData), _playerData);
}
// 保存玩具数据
private void SaveToyData()
{
SaveSys.Instance.Save(nameof(_toyData), _toyData);
}
// 删除玩家数据
private void DelPlayerData()
{
SaveSys.Instance.Delete(nameof(_playerData));
}
// 删除玩具数据
private void DelToyData()
{
SaveSys.Instance.Delete(nameof(_toyData));
}
// 载入并显示玩家数据
private void LoadAndShowPlayerData()
{
SaveSys.Instance.Load<PlayerData>(nameof(_playerData), out _playerData);
_dataText.text = _playerData == null ? "无" : _playerData.ToString();
}
// 载入并显示玩具数据
private void LoadAndShowToyData()
{
SaveSys.Instance.Load<ToyData>(nameof(_toyData), out _toyData);
_dataText.text = _toyData == null ? "无" : _toyData.ToString();
}
private void DelAll()
{
SaveSys.Instance.DeleteAll();
}
}
网友评论