feat(save): 移除旧 StorageSystem 并适配 Game Loop
This commit is contained in:
@@ -2414,7 +2414,6 @@ GameObject:
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 314243162}
|
||||
- component: {fileID: 314243163}
|
||||
m_Layer: 0
|
||||
m_Name: Storage System
|
||||
m_TagString: Untagged
|
||||
@@ -2437,18 +2436,6 @@ Transform:
|
||||
m_Children: []
|
||||
m_Father: {fileID: 1936621685}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!114 &314243163
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 314243161}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: bd2f44edfbd942b49bb7a37f80d7a5eb, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
--- !u!1 &319430488
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
@@ -12775,12 +12762,12 @@ MonoBehaviour:
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
yarnProject: {fileID: 0}
|
||||
variableStorage: {fileID: 314243163}
|
||||
lineProvider: {fileID: 331068880}
|
||||
variableStorage: {fileID: 0}
|
||||
lineProvider: {fileID: 0}
|
||||
saliencyStrategy: 0
|
||||
dialoguePresenters:
|
||||
- {fileID: 381795663}
|
||||
- {fileID: 1829169897}
|
||||
- {fileID: 0}
|
||||
- {fileID: 0}
|
||||
verboseLogging: 1
|
||||
autoStart: 0
|
||||
startNode: Start
|
||||
|
||||
@@ -4,6 +4,7 @@ using System.Collections.Generic;
|
||||
using AibisDream.FixSystem;
|
||||
using AibisDream.Framework;
|
||||
using AibisDream.Kit;
|
||||
using AibisDream.SaveSystem;
|
||||
using AibisDream.UI;
|
||||
using AibisDream.Utility;
|
||||
using UnityEngine;
|
||||
@@ -27,8 +28,6 @@ namespace AibisDream
|
||||
#endregion
|
||||
|
||||
private BindProperty<TalkSceneSO> _currentTalkSceneSo;
|
||||
private readonly GameLoopData _data = new();
|
||||
|
||||
public override void OnSingletonInit()
|
||||
{
|
||||
LoadAllSceneSos();
|
||||
@@ -38,11 +37,9 @@ namespace AibisDream
|
||||
{
|
||||
// 管理游戏开始时各部分的初始化
|
||||
SettingLoader.Init();
|
||||
// 数据类注册
|
||||
StorageSystem.Instance.RegisterData(_data);
|
||||
SnapshotBootstrap.EnsureInitialized();
|
||||
// 场景So事件注册
|
||||
_currentTalkSceneSo = new BindProperty<TalkSceneSO>();
|
||||
_currentTalkSceneSo.Register(item => _data.curSceneSoName = item.name);
|
||||
|
||||
#if UNITY_EDITOR
|
||||
if (runMode == RunMode.SingleScene)
|
||||
@@ -114,7 +111,12 @@ namespace AibisDream
|
||||
// 游戏开始
|
||||
EnumEventSystem.Global.Send(GameLoopEnum.GameStart);
|
||||
|
||||
StorageSystem.Instance.Load(fileName);
|
||||
StartCoroutine(LoadFromSaveFile(fileName));
|
||||
}
|
||||
|
||||
private IEnumerator LoadFromSaveFile(string fileName)
|
||||
{
|
||||
yield return SaveRestoreOrchestrator.RestoreFromFile(fileName);
|
||||
}
|
||||
|
||||
private IEnumerator LoadFirstScene()
|
||||
@@ -270,6 +272,11 @@ namespace AibisDream
|
||||
DialogController.Instance.LoadDialog(_currentTalkSceneSo.Value.yarnProject);
|
||||
}
|
||||
|
||||
public TalkSceneSO GetCurrentTalkSceneSo()
|
||||
{
|
||||
return _currentTalkSceneSo?.Value;
|
||||
}
|
||||
|
||||
public string GetSceneSoName()
|
||||
{
|
||||
return _currentTalkSceneSo.Value.name;
|
||||
@@ -336,26 +343,12 @@ namespace AibisDream
|
||||
#endregion
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
[LoadIndex(10)]
|
||||
public class GameLoopData : IData
|
||||
{
|
||||
public string curSceneSoName;
|
||||
|
||||
public IEnumerator Load()
|
||||
{
|
||||
GameManager.Instance.SetSceneSoByName(curSceneSoName);
|
||||
GameManager.Instance.StartDialog();
|
||||
yield break;
|
||||
}
|
||||
}
|
||||
|
||||
public static class GameLoopYarnCommand
|
||||
{
|
||||
[YarnCommand("auto_save")]
|
||||
public static void AutoSave()
|
||||
{
|
||||
StorageSystem.Instance.Save();
|
||||
SaveRestoreOrchestrator.SaveToTestPath();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections;
|
||||
using AibisDream.Framework;
|
||||
using AibisDream.Kit;
|
||||
using AibisDream.FixSystem;
|
||||
@@ -19,11 +18,12 @@ namespace AibisDream
|
||||
private string _sceneToLoad;
|
||||
private bool _isLoading;
|
||||
|
||||
private readonly SceneData _data = new();
|
||||
private string _currentSceneName;
|
||||
|
||||
public string CurrentSceneName => _currentSceneName;
|
||||
|
||||
public override void OnSingletonInit()
|
||||
{
|
||||
StorageSystem.Instance.RegisterData(_data);
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -56,7 +56,7 @@ namespace AibisDream
|
||||
_sceneToLoad = targetScene;
|
||||
|
||||
// 在卸载场景前清理所有系统
|
||||
if (!string.IsNullOrEmpty(_data.sceneName))
|
||||
if (!string.IsNullOrEmpty(_currentSceneName))
|
||||
{
|
||||
DOTween.KillAll();
|
||||
|
||||
@@ -79,7 +79,7 @@ namespace AibisDream
|
||||
|
||||
// 场景加载完成,通知各系统
|
||||
EnumEventSystem.Global.Send(EventEnum.SceneLoad, _sceneToLoad);
|
||||
_data.sceneName = _sceneToLoad;
|
||||
_currentSceneName = _sceneToLoad;
|
||||
UIManager.Instance.GetPanel<InfoPanel>().HideLoading();
|
||||
_isLoading = false;
|
||||
}
|
||||
@@ -100,7 +100,7 @@ namespace AibisDream
|
||||
ResourceSystem.ReleaseSceneLoader();
|
||||
|
||||
yield return Addressables.UnloadSceneAsync(_loadHandle);
|
||||
_data.sceneName = null;
|
||||
_currentSceneName = null;
|
||||
}
|
||||
|
||||
_isLoading = false;
|
||||
@@ -108,16 +108,4 @@ namespace AibisDream
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
[LoadIndex(1)]
|
||||
public class SceneData : IData
|
||||
{
|
||||
public string sceneName;
|
||||
|
||||
public IEnumerator Load()
|
||||
{
|
||||
// yield return UIManager.Instance.GetPanel<PlayToolPanel>().FadeInAsync();
|
||||
yield return SceneLoader.Instance.LoadSceneAsync(sceneName);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,368 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using AibisDream.Framework;
|
||||
using AibisDream.Kit;
|
||||
using AibisDream.Utility;
|
||||
using AibisDream.UI;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using UnityEngine;
|
||||
using Yarn.Unity;
|
||||
|
||||
|
||||
namespace AibisDream
|
||||
{
|
||||
public class StorageSystem : VariableStorageBehaviour
|
||||
{
|
||||
private const string VariablePrefix = "$";
|
||||
private const string GlobalPrefix = "$global_";
|
||||
private const string DataPrefix = "$data.";
|
||||
private const int MaxSaveNum = 200;
|
||||
|
||||
#region 存储部分
|
||||
|
||||
private readonly Dictionary<string, object> _variableDict = new();
|
||||
private readonly DataContainer _dataContainer = new();
|
||||
|
||||
#endregion
|
||||
|
||||
#region 数据类注册和注销
|
||||
|
||||
public void RegisterData<T>(T data) where T : IData
|
||||
{
|
||||
_dataContainer.Register(data);
|
||||
}
|
||||
|
||||
public void UnregisterData<T>() where T : class, IData
|
||||
{
|
||||
_dataContainer.Unregister<T>();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 变量设置
|
||||
|
||||
public override void SetValue(string variableName, bool boolValue)
|
||||
{
|
||||
SetValue(variableName, boolValue);
|
||||
}
|
||||
|
||||
public override void SetValue(string variableName, string stringValue)
|
||||
{
|
||||
SetValue(variableName, stringValue);
|
||||
}
|
||||
|
||||
public override void SetValue(string variableName, float floatValue)
|
||||
{
|
||||
SetValue(variableName, floatValue);
|
||||
}
|
||||
|
||||
private void SetValue<T>(string variableName, T value)
|
||||
{
|
||||
var variableType = ValidateVariableName(variableName);
|
||||
|
||||
if (variableType == VariableType.Illegal)
|
||||
{
|
||||
Debug.LogError($"{variableName}: 缺少$前缀");
|
||||
return;
|
||||
}
|
||||
|
||||
if (variableType == VariableType.DataField)
|
||||
{
|
||||
Debug.LogError($"{variableName}: 暂时不支持直接设置系统数据");
|
||||
return;
|
||||
}
|
||||
|
||||
// Global和Local直接设置
|
||||
_variableDict[variableName] = value;
|
||||
|
||||
// 数据设置事件广播
|
||||
var variableItem = new VariableItem
|
||||
{
|
||||
variableName = variableName,
|
||||
value = value
|
||||
};
|
||||
EnumEventSystem.Global.Send(StorageEvent.VariableSet, variableItem);
|
||||
}
|
||||
|
||||
public override void Clear()
|
||||
{
|
||||
_variableDict.Clear();
|
||||
}
|
||||
|
||||
public void ClearLocal()
|
||||
{
|
||||
// 收集所有Local类型的变量
|
||||
var locals = _variableDict.Keys.Where(item => ValidateVariableName(item) == VariableType.Local).ToArray();
|
||||
// 清除
|
||||
foreach (var localKey in locals)
|
||||
{
|
||||
_variableDict.Remove(localKey);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 变量获取
|
||||
|
||||
public override bool Contains(string variableName)
|
||||
{
|
||||
var variableType = ValidateVariableName(variableName);
|
||||
if (variableType == VariableType.Illegal)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return variableType == VariableType.DataField
|
||||
? _dataContainer.TryGetFieldValue<string>(variableName, out _)
|
||||
: _variableDict.ContainsKey(variableName);
|
||||
}
|
||||
|
||||
public override bool TryGetValue<T>(string variableName, out T result)
|
||||
{
|
||||
var variableType = ValidateVariableName(variableName);
|
||||
// 非法的直接pass
|
||||
if (variableType == VariableType.Illegal)
|
||||
{
|
||||
result = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
// 在系统数据中查找
|
||||
if (variableType == VariableType.DataField)
|
||||
{
|
||||
return _dataContainer.TryGetFieldValue(variableName, out result);
|
||||
}
|
||||
|
||||
// 直接查找数据
|
||||
if (_variableDict.TryGetValue(variableName, out var value))
|
||||
{
|
||||
if (value is T target)
|
||||
{
|
||||
result = target;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
result = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 序列化
|
||||
|
||||
public override void SetAllVariables(Dictionary<string, float> floats, Dictionary<string, string> strings,
|
||||
Dictionary<string, bool> bools, bool clear = true)
|
||||
{
|
||||
if (clear)
|
||||
{
|
||||
Clear();
|
||||
}
|
||||
|
||||
foreach (var value in floats)
|
||||
{
|
||||
SetValue(value.Key, value.Value);
|
||||
}
|
||||
|
||||
foreach (var value in strings)
|
||||
{
|
||||
SetValue(value.Key, value.Value);
|
||||
}
|
||||
|
||||
foreach (var value in bools)
|
||||
{
|
||||
SetValue(value.Key, value.Value);
|
||||
}
|
||||
}
|
||||
|
||||
public override (Dictionary<string, float> FloatVariables, Dictionary<string, string> StringVariables,
|
||||
Dictionary<string, bool> BoolVariables) GetAllVariables()
|
||||
{
|
||||
var floatDict = new Dictionary<string, float>();
|
||||
var stringDict = new Dictionary<string, string>();
|
||||
var boolDict = new Dictionary<string, bool>();
|
||||
|
||||
foreach (var variablePair in _variableDict)
|
||||
{
|
||||
switch (variablePair.Value)
|
||||
{
|
||||
case float:
|
||||
{
|
||||
var value = Convert.ToSingle(variablePair.Value);
|
||||
floatDict.Add(variablePair.Key, value);
|
||||
break;
|
||||
}
|
||||
case string:
|
||||
{
|
||||
var value = Convert.ToString(variablePair.Value);
|
||||
stringDict.Add(variablePair.Key, value);
|
||||
break;
|
||||
}
|
||||
case bool:
|
||||
{
|
||||
var value = Convert.ToBoolean(variablePair.Value);
|
||||
boolDict.Add(variablePair.Key, value);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
Debug.Log($"{variablePair.Key} is not a valid type");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return (floatDict, stringDict, boolDict);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 保存/加载
|
||||
|
||||
public void Save()
|
||||
{
|
||||
ActionKit.Sequence()
|
||||
.Callback(() => UIManager.Instance.GetPanel<InfoPanel>().ShowSaveLoading())
|
||||
.Delay(1f)
|
||||
.Callback(() => UIManager.Instance.GetPanel<InfoPanel>().HideSaveLoading())
|
||||
.Start(this);
|
||||
|
||||
using (new CodeTimer("Save"))
|
||||
{
|
||||
// 先序列化Yarn变量
|
||||
var saveFile = new JObject();
|
||||
|
||||
// 保存版本号
|
||||
saveFile["version"] = $"Ver.{Application.version}";
|
||||
saveFile["time"] = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
// 保存系统数据
|
||||
saveFile["systemData"] = _dataContainer.SaveAsJson();
|
||||
|
||||
// 保存对话变量
|
||||
var (floatDict, stringDict, boolDict) = GetAllVariables();
|
||||
saveFile["floatDict"] = JObject.FromObject(floatDict);
|
||||
saveFile["stringDict"] = JObject.FromObject(stringDict);
|
||||
saveFile["boolDict"] = JObject.FromObject(boolDict);
|
||||
|
||||
// 存为Json
|
||||
JsonUtil.SaveJObject(saveFile, GetSavePath());
|
||||
#if !UNITY_EDITOR
|
||||
DeleteOldSave();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
private void DeleteOldSave()
|
||||
{
|
||||
if (Directory.Exists(ConstRef.SaveFilePath))
|
||||
{
|
||||
var fileList = Directory.GetFiles(ConstRef.SaveFilePath);
|
||||
if (fileList.Length > MaxSaveNum)
|
||||
{
|
||||
var sorted = fileList.OrderByDescending(f => f).ToArray();
|
||||
for (var i = MaxSaveNum; i < fileList.Length; i++)
|
||||
{
|
||||
File.Delete(sorted[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Directory.CreateDirectory(ConstRef.SaveFilePath);
|
||||
}
|
||||
}
|
||||
|
||||
public void Load(string savePath)
|
||||
{
|
||||
// 先将存档文件转为JObject
|
||||
var saveData = JsonUtil.ReadJObject(savePath);
|
||||
// 加载Yarn数据
|
||||
var floatDict = saveData["floatDict"]?.ToObject<Dictionary<string, float>>();
|
||||
var stringDict = saveData["stringDict"]?.ToObject<Dictionary<string, string>>();
|
||||
var boolDict = saveData["boolDict"]?.ToObject<Dictionary<string, bool>>();
|
||||
SetAllVariables(floatDict, stringDict, boolDict);
|
||||
|
||||
// 加载系统数据
|
||||
if (saveData["systemData"] is not JObject systemDataJson)
|
||||
{
|
||||
Debug.Log("维修数据读取问题");
|
||||
return;
|
||||
}
|
||||
|
||||
StartCoroutine(_dataContainer.LoadByJson(systemDataJson));
|
||||
}
|
||||
|
||||
private string GetSavePath()
|
||||
{
|
||||
Directory.CreateDirectory(ConstRef.SaveFilePath);
|
||||
|
||||
string gameStage;
|
||||
if (TryGetValue("$gameStage", out float gameStageFloat))
|
||||
{
|
||||
gameStage = gameStageFloat.ToString(CultureInfo.InvariantCulture);
|
||||
}
|
||||
else
|
||||
{
|
||||
TryGetValue("$gameStage", out string gameStageStr);
|
||||
gameStage = gameStageStr;
|
||||
}
|
||||
|
||||
return
|
||||
$"{ConstRef.SaveFilePath}/{DateTime.Now:yyyyMMdd-HHmmss}_{GameManager.Instance.GetSceneSoName()}_{gameStage}";
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private static VariableType ValidateVariableName(string variableName)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(variableName) || !variableName.StartsWith(VariablePrefix))
|
||||
{
|
||||
return VariableType.Illegal;
|
||||
}
|
||||
|
||||
if (variableName.StartsWith(GlobalPrefix))
|
||||
{
|
||||
return VariableType.Global;
|
||||
}
|
||||
|
||||
if (variableName.StartsWith(DataPrefix))
|
||||
{
|
||||
return VariableType.DataField;
|
||||
}
|
||||
|
||||
return VariableType.Local;
|
||||
}
|
||||
|
||||
#region 单例部分
|
||||
|
||||
public static StorageSystem Instance { get; private set; }
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (Instance != null)
|
||||
{
|
||||
Destroy(gameObject);
|
||||
}
|
||||
|
||||
Instance = this;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
public enum VariableType
|
||||
{
|
||||
Local,
|
||||
Global,
|
||||
DataField,
|
||||
Illegal
|
||||
}
|
||||
|
||||
public struct VariableItem
|
||||
{
|
||||
public string variableName;
|
||||
public object value;
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bd2f44edfbd942b49bb7a37f80d7a5eb
|
||||
timeCreated: 1742802488
|
||||
Reference in New Issue
Block a user