美文网首页
Unity中,用二进制形式存储数据

Unity中,用二进制形式存储数据

作者: 全新的饭 | 来源:发表于2023-07-13 14:44 被阅读0次

功能使用说明和示意

本功能是对Unity中,借助Newtonsoft.Json和PlayerPrefs实现简单的存储功能中描述的功能的改进。
提供如下方法:

  1. 保存特定数据:将obj存储起来,需提供一个key。后续可通过key获得之前存储的该数据、删除该数据。
  2. 载入特定数据:载入之前存储的某数据。返回值表示是否载入成功。
  3. 删除特定数据:传入key,删除对应数据。
  4. 删除所有数据:删除所有已存储的数据。


    存储功能示意.gif

使用说明:
将下面的SaveSys.cs文件拖入工程(其中包含了多个类)。
修改字段GameName为当前游戏的名称。

为了序列化(以便储存),必须使用下面的特性修改要存储的类。

[System.Serializable]

为了在WebGL平台使用,还需进行如下操作(具体原因详见下文):
如图创建这样的目录:


WebGLSave插件.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();
    }
}

相关文章

  • 内存及数据存储

    内存及数据存储 程序运行在内存中 抽象: 单位 内存中已二进制形式存储数据 内存地址用16进制表示 进制转化 数据...

  • 2018-08-22 Day3-字符串

    1.数据存储形式 计算机以二进制的形式存储 (1).原码:数据的二进制形式 10 --> 1010 原码:0000...

  • 算法训练 -- 第一章 线性表

    一、位操作 所有数据在计算机底层都是以二进制形式存在的存储时:数据以二进制数字形式进行存储计算时:数据以补码形式参...

  • MySQL定点数据类型(精确值)-DECIMAL

    1 DECIMAL介绍 在MySQL中,DECIMAL和NUMERIC是定点类型数据,以二进制形式存储,因此存储的...

  • 3总 字符串

    1.计算机的储存形式: 计算机在存储数据的时候,都是以二进制的形式存在计算机中。1.原码:数据的二进制形式'''正...

  • Oracle中关于blob数的处理

    blob(二进制大对象) blob是以二进制的形式存储大型数据,如图片、视频、文本 orcle中blob转文本显示...

  • redis持久化存储

    redis有两种存储机制,RDB,AOF RDB:将数据库的状态以快照形式写入磁盘且用二进制方式存储,相当于数据库...

  • C语言 day11

    数据的存储 数据在内存中以2进制的形式存储整数在内存中存的是二进制的补码 大小端 大端字节序:把数据的低位字节序的...

  • 3.03.字符集编码

    在计算存储字符都是存储的字符所对应的数值以二进制的形式表示 常见的:ASCII表用7bit来表示存储数据ISO-8...

  • Day3—字符串

    题外:计算机的数据存储 计算机在存储数据的时候,都是以二进制的形式存储在计算机中的。并且数据存储时,存的是一个数的...

网友评论

      本文标题:Unity中,用二进制形式存储数据

      本文链接:https://www.haomeiwen.com/subject/koumudtx.html