Unity简易存档系统实现

 

此文章已废弃,存档系统已经做了很多修改。


源码

首先,先上源码,解释项目结构,后面再讲每个类、结构体和函数的作用

源代码分两个类:ArchiveManager.cs和ArchiveSO.cs

ArchiveManager.cs



using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEditor;
using UnityEngine;
using UnityEngine.SceneManagement;

public class ArchiveManager : MonoBehaviour
{

    #region 成员、属性

    #region 静态
    private static ArchiveManager instance;
    public static ArchiveManager Instance { get => instance; }
    public static ArchiveData CurrentArchive = ArchiveData.None;
    public static Scene CurrentScene;
    public ArchiveData SceneBuffer = new ArchiveData("Buffer","Buffer");
    #endregion

    #region 非静态
    [SerializeField]
    private ArchiveSO ArchiveSoData;

    #endregion

    #endregion
    #region 生命周期

    private void Awake()
    {
        InstanceInit();
    }

    private void OnEnable()
    {
        SceneManager.sceneUnloaded += SceneUnloaded;
        SceneManager.sceneLoaded += SceneLoaded;
    }


    private void OnDisable()
    {
        SceneManager.sceneUnloaded -= SceneUnloaded;
        SceneManager.sceneLoaded -= SceneLoaded;
    }

    #endregion
    #region 单例

    /// <summary>
    /// 实现单例
    /// </summary>
    private void InstanceInit()
    {
        if (instance != null)
        {
            Destroy(this);
        }
        instance = this;
        
        DontDestroyOnLoad(this);
    }
    #endregion
    /// <summary>
    /// 卸载场景前,对场景所有物体进行遍历,将其值存到缓存结构体中
    /// </summary>
    /// <param name="scene"></param>
    private void SceneUnloaded(Scene scene)
    {
        //TODO:装载场景里所有GameObject到缓存池中
        var objs = GameObject.FindObjectsOfType<MonoBehaviour>().OfType<IArchive>();
        SceneData sceneData = new SceneData(scene.name);
        foreach (var archive in objs)
        {
            long id = archive.GetComponent().GetOnlyID();
            string value = archive.Save();
            sceneData.SaveValue(id,value);
        }
        SceneBuffer.SaveValue(scene,sceneData);

    }
    /// <summary>
    /// 场景加载后,对场景所有物体进行遍历,调用接口赋值
    /// </summary>
    /// <param name="scene"></param>
    /// <param name="mode"></param>
    private void SceneLoaded(Scene scene, LoadSceneMode mode)
    {
        //TODO:加载数据到场景中
        if(CurrentArchive == ArchiveData.None)
            return;
        var sceneData = CurrentArchive.LoadValue(scene);
        if(sceneData == SceneData.None)
            return;
        var objs = GameObject.FindObjectsOfType<MonoBehaviour>().OfType<IArchive>();
        foreach (var archive in objs)
        {
            long key = archive.GetComponent().GetOnlyID();
            var value = sceneData.LoadValue(key);
            archive.Load(value);
        }

        CurrentScene = scene;
    }

    #region 函数
    /// <summary>
    /// 返回给UI去查看有哪些存档
    /// </summary>
    /// <returns></returns>
    public List<ArchiveData> GetArchiveList()
    {
        return ArchiveSoData.Archives;
    }

    public void LoadInIndex(int index)
    {
        if (index < 0 || index >= ArchiveSoData.Archives.Count)
        {
            Debug.LogError("下标不在范围内");
            return;
        }
        CurrentArchive = ArchiveSoData.Archives[index];
        CurrentArchive.ArchiveValue = CurrentArchive.FromJSON();
    }

    /// <summary>
    /// 保存为新存档
    /// </summary>
    public void SaveAsNew()
    {
        ArchiveSoData.Archives.Add(Save());
    }

    /// <summary>
    /// 替换存档
    /// </summary>
    /// <param name="index"></param>
    public void SaveInIndex(int index)
    {
        if(index < 0 || index >= ArchiveSoData.Archives.Count)
            return;
        ArchiveSoData.Archives[index] = Save();
    }

    /// <summary>
    /// 创建新档
    /// </summary>
    public void CreatAsNew()
    {
        CurrentArchive = ArchiveData.None;
    }
    private ArchiveData Save()
    {
        var data = new ArchiveData(String.Empty,SceneManager.GetActiveScene().name);
        //更新当前场景的数据到缓存中
        SceneUnloaded(SceneManager.GetActiveScene());
        data.ArchiveValue = SceneBuffer.ArchiveValue;
        data.ArchiveValueJSON = data.ToJSON();
        return data;
    }
    #endregion

}
/// <summary>
/// 单个场景存储的信息
/// </summary>
[Serializable]
public struct SceneData
{
    public static SceneData None = new SceneData() { SceneName = null };
    /// <summary>
    /// 场景名称
    /// </summary>
    public string SceneName;
    /// <summary>
    /// 储存的数据
    /// </summary>
    public Dictionary<long, string> sceneValue;
        
    public SceneData(string sceneName)
    {
        this.SceneName = sceneName;
        sceneValue = new Dictionary<long, string>();
    }

    public static bool operator ==(SceneData data1, SceneData data2)
    {
        if (data1.SceneName == data2.SceneName)
            return true;
        return false;
    }
    public static bool operator !=(SceneData data1, SceneData data2)
    {
        if (data1.SceneName == data2.SceneName)
            return false;
        return true;
    }
}
/// <summary>
/// 单个存档的信息
/// </summary>
[Serializable]
public struct ArchiveData
{
    public static ArchiveData None = new ArchiveData();
    /// <summary>
    /// 当前存档名
    /// </summary>
    public string ArchiveName;

    /// <summary>
    /// 存档最后一次存储时间
    /// </summary>
    public string CurrentTime;

    /// <summary>
    /// 存档所处的场景
    /// </summary>
    public string CurrentSceneName;
    [TextArea]
    public string ArchiveValueJSON;

    
    public Dictionary<string, SceneData> ArchiveValue;

    public ArchiveData(string archiveName,string sceneName)
    {
        ArchiveName = archiveName;
        CurrentTime = System.DateTime.Now.ToString();
        CurrentSceneName = sceneName;
        ArchiveValue = new Dictionary<string, SceneData>();
        ArchiveValueJSON = String.Empty;
    }
    public static bool operator ==(ArchiveData data1, ArchiveData data2)
    {
        if (data1.ArchiveName == data2.ArchiveName && data1.ArchiveValue == data2.ArchiveValue)
            return true;
        return false;
    }
    public static bool operator !=(ArchiveData data1, ArchiveData data2)
    {
        if (data1.ArchiveName == data2.ArchiveName && data1.ArchiveValue == data2.ArchiveValue)
            return false;
        return true;
    }
}


public static class SceneDataExtension
{
    public static void SaveValue(this SceneData data, Component component, string jsonValue)
    {
        long onlyID = component.GetOnlyID();
        data.SaveValue(onlyID,jsonValue);
    }
    public static void SaveValue(this SceneData data, long id, string jsonValue)
    {
        if (data.sceneValue.ContainsKey(id))
            data.sceneValue[id] = jsonValue;
        else
            data.sceneValue.Add(id,jsonValue);
    }

    public static string LoadValue(this SceneData data, Component component)
    {
        long id = component.GetOnlyID();
        return data.LoadValue(id);
    }

    public static string LoadValue(this SceneData data, long id)
    {
        if(!data.sceneValue.ContainsKey(id))
            return String.Empty;
        return data.sceneValue[id];
    }
}

public static class ArchiveDataExtension
{
    public static void SaveValue(this ArchiveData data,string sceneName,SceneData sceneData)
    {
        if (data.ArchiveValue.ContainsKey(sceneName))
            data.ArchiveValue[sceneName] = sceneData;
        else
            data.ArchiveValue.Add(sceneName,sceneData);
    }

    public static void SaveValue(this ArchiveData data, Scene scene, SceneData sceneData)
    {
        data.SaveValue(scene.name,sceneData);
    }

    public static SceneData LoadValue(this ArchiveData data,string sceneName)
    {
        if(!data.ArchiveValue.ContainsKey(sceneName))
            return SceneData.None;
        return data.ArchiveValue[sceneName];
    }

    public static SceneData LoadValue(this ArchiveData data, Scene scene)
    {
        return data.LoadValue(scene.name);
    }

    public static string ToJSON(this ArchiveData data)
    {
        string value = JsonConvert.SerializeObject(data.ArchiveValue);
        return value;
    }

    public static  Dictionary<string, SceneData> FromJSON(this ArchiveData data)
    {
        if(data.ArchiveValueJSON == String.Empty)
            return null;
        Dictionary<string, SceneData> datas =
            JsonConvert.DeserializeObject<Dictionary<string, SceneData>>(data.ArchiveValueJSON);
        return datas;
    }
}

public static class ComponentExtension
{
    public static long GetOnlyID(this Component component)
    {
        long onlyID = component.GetInstanceID() - component.gameObject.GetInstanceID();
        string s = onlyID.ToString() + component.GetLocalID();
        onlyID = Convert.ToInt64(s);
        return onlyID;
    }

    public static int GetLocalID(this Component component)
    {
        PropertyInfo info = typeof(SerializedObject).GetProperty("inspectorMode", BindingFlags.NonPublic | BindingFlags.Instance);
        SerializedObject sObj = new SerializedObject(component);
        info.SetValue(sObj, InspectorMode.Debug, null);
        SerializedProperty localIdProp = sObj.FindProperty("m_LocalIdentfierInFile");
        return localIdProp.intValue;
    }
}

public interface IArchive
{
    public void Load(string jsonValue);
    public string Save();
    public Component GetComponent();
}

 此文件包含

        一个单例:ArchiveManager,

        两个结构体定义:ArchiveData、SceneData

        一个接口:IArchive

        三个拓展类:SceneDataExtension、ArchiveDataExtension、ComponentExtension

ArchiveSO.cs

using System.Collections.Generic;
using UnityEngine;
[CreateAssetMenu(fileName = "ArchiveSO", menuName = "Data/ArchiveSO")]
public class ArchiveSO : ScriptableObject
{
    public List<ArchiveData> Archives = new List<ArchiveData>();
}

此文件包含

        一个继承SciptableObject的类:ArchiveSO

存储原理

        该系统本着可拓展,简易,低耦合的理念(实际上是我不会)

        其核心存储介质其实就是ScriptableObject,数据类型是Json。

        本来是打算用PlayerPrefs做存储介质,但项目开工前做技术调查发现,好像PlayerPrefs的SetString有点问题,且加上这个类是静态类不方便我管理和查看(可视化),所以最终使用ScriptableObject作为存储介质。

数据架构

从上往下

 

代码解释

SceneData(struct)

[Serializable]
public struct SceneData
{
    public static SceneData None = new SceneData() { SceneName = null };
    /// <summary>
    /// 场景名称
    /// </summary>
    public string SceneName;
    /// <summary>
    /// 储存的数据
    /// </summary>
    public Dictionary<long, string> sceneValue;
        
    public SceneData(string sceneName)
    {
        this.SceneName = sceneName;
        sceneValue = new Dictionary<long, string>();
    }

    public static bool operator ==(SceneData data1, SceneData data2)
    {
        if (data1.SceneName == data2.SceneName)
            return true;
        return false;
    }
    public static bool operator !=(SceneData data1, SceneData data2)
    {
        if (data1.SceneName == data2.SceneName)
            return false;
        return true;
    }
}

SceneData结构体主要用于存储单个场景里需要保存的信息,其主要由一个SceneName(string)记录场景名,和一个sceneValue(Dictionary<long, string>)字典,主要讲解一下这个字典存的什么东西。

public Dictionary<long, string> sceneValue

        在讲解这个字典前,我们需要先理清一下思路:

首先,我们使用

GameObject.FindObjectsOfType<MonoBehaviour>().OfType<IArchive>();

 查找场景内,所有实例中,带有继承了IArchive接口的组件的实例。

然后调用其接口,把每一个实例需要保存的JSON数据存在当前场景的SceneData的字典中。

那么问题来了,我们应该如何设置字典的key值,以达到每一个实例的每一个脚本(Component)都独一无二,能精准在字典里获取其独有的数据。

可能稍微了解过一点GameObject类的读者会想到,使用gameObject.Getinstance()作为字典的key值。

确实,在Unity编辑器的Inspector的Debug模式下,我们可以看到,一个GameObject的每个组件的InstanceID都是互不相同的。但我们需要注意一个点,我们这里的key,需要保证在整个项目里独一无二。但我们可以看官方API文档解释

618dd7f7605d4766963a0e82373d1b26.png

 这一句:The instance ID of an object is always unique.

翻译中文:对象的实例 ID 始终是唯一的。

只是看这句话,很多人可能都会和我一样被这句话误导,会以为每个对象的InstanceID在这个对象被创立的时候就固定不变了。

实际上不是的,如果我们单只开一个场景查看,不管怎么重启项目,移动实例,确实它的InstanceID始终是不变的,但如果我们尝试使用SceneManagment卸载加载场景,我们会发现它的InstanceID发生了改变。

如果读者愿意再去查查,会发现Unity的实例的InstanceID并不是固定存在,而是通过两个值计算而来:GUID、LocalID。

GUID读者可以自行百度,该系统没有使用到该属性就不做讲解。

如果我们再仔细查看,我们可以发现

314adfb605334f7ea2f8a71b133f078a.png

组件属性里有一个Local Identfier In File的属性,这个属性值无论怎么切换场景,重启项目,值都是不变的,根据官方说法,这个值是存在Asset下,至于具体怎么存的我们先不管。 然后就是我们可以看到这个值在整个GameObject中所有组件包括GameObject都是一样的,所以可以知道这个是指的GameObject实例的localID。

这个ID的获取,Unity并没有提供方法,我们需要使用C#的反射来获取

    public static int GetLocalID(this Component component)
    {
        PropertyInfo info = typeof(SerializedObject).GetProperty("inspectorMode", BindingFlags.NonPublic | BindingFlags.Instance);
        SerializedObject sObj = new SerializedObject(component);
        info.SetValue(sObj, InspectorMode.Debug, null);
        SerializedProperty localIdProp = sObj.FindProperty("m_LocalIdentfierInFile");
        return localIdProp.intValue;
    }

如此,我们就获取了GameObject实例的唯一ID。

接下来,我们需要解决的是,单个实例下,多个相同组件的唯一问题。

其实解决方法很简单,组件Component有自己的InstanceID,GameObject也有自己的InstanceID,这两者并不相同,而且通过实验,组件的id和组件附属的GameObject的id之间的差值,其实是固定的,也就是:

long onlyID = component.GetInstanceID() - component.gameObject.GetInstanceID();

 这里使用long数据类型来储存,为后面的处理做准备。

然后将onlyID和LocalID组合一下,就得到该组件的唯一ID;

拓展方法如下

public static class ComponentExtension
{
    public static long GetOnlyID(this Component component)
    {
        long onlyID = component.GetInstanceID() - component.gameObject.GetInstanceID();
        string s = onlyID.ToString() + component.GetLocalID();
        onlyID = Convert.ToInt64(s);
        return onlyID;
        
    }

    public static int GetLocalID(this Component component)
    {
        PropertyInfo info = typeof(SerializedObject).GetProperty("inspectorMode", BindingFlags.NonPublic | BindingFlags.Instance);
        SerializedObject sObj = new SerializedObject(component);
        info.SetValue(sObj, InspectorMode.Debug, null);
        SerializedProperty localIdProp = sObj.FindProperty("m_LocalIdentfierInFile");
        return localIdProp.intValue;
    }
}

综上,sceneValue的key就可以确定好了。

然后就是value,文章开头已经解释了,该存档系统的存储数据格式就是JSON字符串,所以这里的value也就是字符串,不过是序列化成json格式的字符串。

SceneData函数拓展

public static class SceneDataExtension
{
    public static void SaveValue(this SceneData data, Component component, string jsonValue)
    {
        long onlyID = component.GetOnlyID();
        data.SaveValue(onlyID,jsonValue);
    }
    public static void SaveValue(this SceneData data, long id, string jsonValue)
    {
        if (data.sceneValue.ContainsKey(id))
            data.sceneValue[id] = jsonValue;
        else
            data.sceneValue.Add(id,jsonValue);
    }

    public static string LoadValue(this SceneData data, Component component)
    {
        long id = component.GetOnlyID();
        return data.LoadValue(id);
    }

    public static string LoadValue(this SceneData data, long id)
    {
        if(!data.sceneValue.ContainsKey(id))
            return String.Empty;
        return data.sceneValue[id];
    }
}

ArchiveData(Struct)

[Serializable]
public struct ArchiveData
{
    public static ArchiveData None = new ArchiveData();
    /// <summary>
    /// 当前存档名
    /// </summary>
    public string ArchiveName;

    /// <summary>
    /// 存档最后一次存储时间
    /// </summary>
    public string CurrentTime;

    /// <summary>
    /// 存档所处的场景
    /// </summary>
    public string CurrentSceneName;
    [TextArea]
    public string ArchiveValueJSON;

    
    public Dictionary<string, SceneData> ArchiveValue;

    public ArchiveData(string archiveName,string sceneName)
    {
        ArchiveName = archiveName;
        CurrentTime = System.DateTime.Now.ToString();
        CurrentSceneName = sceneName;
        ArchiveValue = new Dictionary<string, SceneData>();
        ArchiveValueJSON = String.Empty;
    }
    public static bool operator ==(ArchiveData data1, ArchiveData data2)
    {
        if (data1.ArchiveName == data2.ArchiveName && data1.ArchiveValue == data2.ArchiveValue)
            return true;
        return false;
    }
    public static bool operator !=(ArchiveData data1, ArchiveData data2)
    {
        if (data1.ArchiveName == data2.ArchiveName && data1.ArchiveValue == data2.ArchiveValue)
            return false;
        return true;
    }
}

ArchiveData结构体是单个存档的数据结构,内容和SceneData相似。

存档名称、存档最后一次存储时间、存档最后一次存储时的场景名称三个属性就不一一描述了,都是string类型,最后一个场景名称就是Scene.name,主要方便告诉场景管理器这个存档最后一次存储时所在的场景,如果游戏有需要,可以选择加载存档时加载到该场景。

主要讲解内容是

ArchiveValueJSON(string)
ArchiveValue(Dictionary<string, SceneData>)

可能会有读者会有疑惑,ArchiveValueJSON的作用是什么,不是已经有一个字典存数据了嘛。

这里就涉及到一个Unity理论知识,根据官方定义,Unity的ScriptableObject只能存储可序列化的数据,也就是那个标签[Serializable]。

熟悉Unity序列化的读者都知道,Unity的字典是没有被序列化的,可能会有人想到Odin的对字典序列化。

但我这里为了降低耦合度,非必要我是不会使用非官方插件,而且Odin的序列化只在Odin插件中有效,如果要存储到ScriptableObject中,可能需要重写一些东西(没仔细研究过Odin)。

所以,字典的数据最终是不会被存储到ScriptableObject中,所以这里我们使用Newtonsoft.Json库的Json序列化字典为json格式字符串并储存,读取时再取出来反序列化为字典。

ArchiveData函数拓展

public static class ArchiveDataExtension
{
    public static void SaveValue(this ArchiveData data,string sceneName,SceneData sceneData)
    {
        if (data.ArchiveValue.ContainsKey(sceneName))
            data.ArchiveValue[sceneName] = sceneData;
        else
            data.ArchiveValue.Add(sceneName,sceneData);
    }

    public static void SaveValue(this ArchiveData data, Scene scene, SceneData sceneData)
    {
        data.SaveValue(scene.name,sceneData);
    }

    public static SceneData LoadValue(this ArchiveData data,string sceneName)
    {
        if(!data.ArchiveValue.ContainsKey(sceneName))
            return SceneData.None;
        return data.ArchiveValue[sceneName];
    }

    public static SceneData LoadValue(this ArchiveData data, Scene scene)
    {
        return data.LoadValue(scene.name);
    }

    public static string ToJSON(this ArchiveData data)
    {
        string value = JsonConvert.SerializeObject(data.ArchiveValue);
        return value;
    }

    public static  Dictionary<string, SceneData> FromJSON(this ArchiveData data)
    {
        if(data.ArchiveValueJSON == String.Empty)
            return null;
        Dictionary<string, SceneData> datas =
            JsonConvert.DeserializeObject<Dictionary<string, SceneData>>(data.ArchiveValueJSON);
        return datas;
    }
}

ArchiveSO(ScriptableObject)

using System.Collections.Generic;
using UnityEngine;
[CreateAssetMenu(fileName = "ArchiveSO", menuName = "Data/ArchiveSO")]
public class ArchiveSO : ScriptableObject
{
    public List<ArchiveData> Archives = new List<ArchiveData>();
}

这个就没必要多解释,一个list列表。

ArchiveManager(MonoBehaviour)

public class ArchiveManager : MonoBehaviour
{
    // public ButtonFunction 保存存档;
    #region 成员、属性

    #region 静态
    private static ArchiveManager instance;
    public static ArchiveManager Instance { get => instance; }
    public static ArchiveData CurrentArchive = ArchiveData.None;
    public static Scene CurrentScene;
    public ArchiveData SceneBuffer = new ArchiveData("Buffer","Buffer");
    #endregion

    #region 非静态
    [SerializeField]
    private ArchiveSO ArchiveSoData;

    #endregion

    #endregion
    #region 生命周期

    private void Awake()
    {
        InstanceInit();
        // 保存存档 = new ButtonFunction(this,"保存新档", SaveAsNew);
    }

    private void OnEnable()
    {
        SceneManager.sceneUnloaded += SceneUnloaded;
        SceneManager.sceneLoaded += SceneLoaded;
    }


    private void OnDisable()
    {
        SceneManager.sceneUnloaded -= SceneUnloaded;
        SceneManager.sceneLoaded -= SceneLoaded;
    }

    #endregion
    #region 单例

    /// <summary>
    /// 实现单例
    /// </summary>
    private void InstanceInit()
    {
        if (instance != null)
        {
            Destroy(this);
        }
        instance = this;
        
        DontDestroyOnLoad(this);
    }
    #endregion
    /// <summary>
    /// 卸载场景前,对场景所有物体进行遍历,将其值存到缓存结构体中
    /// </summary>
    /// <param name="scene"></param>
    private void SceneUnloaded(Scene scene)
    {
        //TODO:装载场景里所有GameObject到缓存池中
        var objs = GameObject.FindObjectsOfType<MonoBehaviour>().OfType<IArchive>();
        SceneData sceneData = new SceneData(scene.name);
        foreach (var archive in objs)
        {
            long id = archive.GetComponent().GetOnlyID();
            string value = archive.Save();
            sceneData.SaveValue(id,value);
        }
        SceneBuffer.SaveValue(scene,sceneData);

    }
    /// <summary>
    /// 场景加载后,对场景所有物体进行遍历,调用接口赋值
    /// </summary>
    /// <param name="scene"></param>
    /// <param name="mode"></param>
    private void SceneLoaded(Scene scene, LoadSceneMode mode)
    {
        //TODO:加载数据到场景中
        if(CurrentArchive == ArchiveData.None)
            return;
        var sceneData = CurrentArchive.LoadValue(scene);
        if(sceneData == SceneData.None)
            return;
        var objs = GameObject.FindObjectsOfType<MonoBehaviour>().OfType<IArchive>();
        foreach (var archive in objs)
        {
            long key = archive.GetComponent().GetOnlyID();
            var value = sceneData.LoadValue(key);
            archive.Load(value);
        }

        CurrentScene = scene;
    }

    #region 函数
    /// <summary>
    /// 返回给UI去查看有哪些存档
    /// </summary>
    /// <returns></returns>
    public List<ArchiveData> GetArchiveList()
    {
        return ArchiveSoData.Archives;
    }

   

    /// <summary>
    /// 保存为新存档
    /// </summary>
    public void SaveAsNew()
    {
        ArchiveSoData.Archives.Add(Save());
    }

    /// <summary>
    /// 替换存档
    /// </summary>
    /// <param name="index"></param>
    public void SaveInIndex(int index)
    {
        if(index < 0 || index >= ArchiveSoData.Archives.Count)
            return;
        ArchiveSoData.Archives[index] = Save();
    }

    /// <summary>
    /// 创建新档
    /// </summary>
    public void LoadAsNew()
    {
        CurrentArchive = ArchiveData.None;
    }
     public void LoadInIndex(int index)
    {
        if (index < 0 || index >= ArchiveSoData.Archives.Count)
        {
            Debug.LogError("下标不在范围内");
            return;
        }
        CurrentArchive = ArchiveSoData.Archives[index];
        CurrentArchive.ArchiveValue = CurrentArchive.FromJSON();
    }
    private ArchiveData Save()
    {
        var data = new ArchiveData(String.Empty,SceneManager.GetActiveScene().name);
        //更新当前场景的数据到缓存中
        SceneUnloaded(SceneManager.GetActiveScene());
        data.ArchiveValue = SceneBuffer.ArchiveValue;
        data.ArchiveValueJSON = data.ToJSON();
        return data;
    }
    #endregion

}

ArchiveManager类作为管理类,其使用单例模式保证项目里只会有一个实例。

我们先讲该类的运行逻辑再根据逻辑衍生去讲属性和函数的意义。

程序逻辑

当我们新建存档后,就加载进游戏场景,然后根据游戏需求,可能会存在卸载场景,加载场景的步骤。根据官方api文档介绍,SceneManager.sceneUnloaded

SceneManager.sceneLoaded

两个事件分别对应场景卸载前和场景加载后(?加载后还有待商议,为进行大场景测试)。

根据我们的框架逻辑:场景卸载前遍历场景实例储存数据到临时的ArchiveData容器,场景加载后遍历场景实例加载当前存档数据到实例中。

于是我们就可以订阅这两个事件,让框架自动在切换场景时存储和加载数据。

PS,笔者能力、时间有限,为进行大场景测试,所有这里笔者推荐大家不要使用框架的自动读取加载,而是自己手动控制流程

 

然后介绍一下几个公共函数接口

GetArchiveList :返回存档的列表

SaveAsNew :保存为新建存档

SaveInIndex(int index):替换存档列表里下标为index的存档为当前存档。

LoadAsNew:新建存档时调用此函数,重置存档管理器的临时存档。

LoadInIndex(int index):加载下标index的存档

 

  • 3
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值