导入odin

This commit is contained in:
2026-01-01 23:09:08 +08:00
parent 9ceffccd39
commit 04dd4a23b2
814 changed files with 120820 additions and 87 deletions

View File

@@ -0,0 +1,56 @@
using UnityEngine;
using UnityEngine.LowLevel;
using UnityEngine.PlayerLoop;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Obvious.Soap
{
public static class ScriptableObjectUpdateSystem
{
private static List<ScriptableSaveBase> _objectsToUpdate = new List<ScriptableSaveBase>();
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
private static void Init()
{
var playerLoop = PlayerLoop.GetCurrentPlayerLoop();
var updateSystemIndex = Array.FindIndex(playerLoop.subSystemList, x => x.type == typeof(Update));
if (updateSystemIndex != -1)
{
var updateSystem = playerLoop.subSystemList[updateSystemIndex];
var oldList = updateSystem.subSystemList.ToList();
oldList.Add(new PlayerLoopSystem
{
type = typeof(ScriptableObjectUpdateSystem),
updateDelegate = UpdateScriptableObjects
});
updateSystem.subSystemList = oldList.ToArray();
playerLoop.subSystemList[updateSystemIndex] = updateSystem;
PlayerLoop.SetPlayerLoop(playerLoop);
}
}
public static void RegisterObject(ScriptableSaveBase scriptableSave)
{
if (!_objectsToUpdate.Contains(scriptableSave))
{
_objectsToUpdate.Add(scriptableSave);
}
}
public static void UnregisterObject(ScriptableSaveBase scriptableSave)
{
_objectsToUpdate.Remove(scriptableSave);
}
private static void UpdateScriptableObjects()
{
foreach (var savableScriptableObject in _objectsToUpdate)
{
savableScriptableObject.Update();
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 535c1578b709d0740b740ada14b25759
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,237 @@
using System;
using System.Diagnostics;
using System.IO;
using Obvious.Soap.Attributes;
using UnityEngine;
using Debug = UnityEngine.Debug;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace Obvious.Soap
{
public abstract class ScriptableSave<T> : ScriptableSaveBase where T : class, new()
{
[SerializeField] protected bool _debugLogEnabled = false;
[SerializeField] protected ELoadMode _loadMode = ELoadMode.Automatic;
public ELoadMode LoadMode => _loadMode;
[SerializeField] protected ESaveMode _saveMode = ESaveMode.Manual;
public ESaveMode SaveMode => _saveMode;
#if ODIN_INSPECTOR
[ShowIf("_saveMode", ESaveMode.Interval)]
#else
[ShowIf(nameof(_saveMode), ESaveMode.Interval)]
#endif
[SerializeField]
protected double _saveIntervalSeconds = 120f;
public double SaveIntervalSeconds
{
get => _saveIntervalSeconds;
set => _saveIntervalSeconds = value;
}
public Action OnSaved = null;
public Action OnLoaded = null;
public Action OnDeleted = null;
[Header("Runtime Data")] [SerializeField]
protected T _data = new T();
protected double _lastSaveTime;
public virtual string FilePath => $"{Application.persistentDataPath}" +
$"{Path.AltDirectorySeparatorChar}" +
$"{name}.json";
private string Directory => Path.GetDirectoryName(FilePath);
public string LastSerializedJson { get; private set; }
public bool FileExists => File.Exists(FilePath);
public override Type GetGenericType => typeof(T);
protected virtual void OnEnable()
{
#if UNITY_EDITOR
EditorApplication.playModeStateChanged += OnPlayModeStateChanged;
#else
if (_loadMode == ELoadMode.Automatic)
{
Load();
}
if (_saveMode == ESaveMode.Interval)
{
ScriptableObjectUpdateSystem.RegisterObject(this);
}
#endif
}
protected virtual void OnDisable()
{
#if UNITY_EDITOR
EditorApplication.playModeStateChanged -= OnPlayModeStateChanged;
#else
ScriptableObjectUpdateSystem.UnregisterObject(this);
#endif
}
public override void Save()
{
LastSerializedJson = JsonUtility.ToJson(_data, prettyPrint: true);
try
{
using (StreamWriter writer = new StreamWriter(FilePath))
{
writer.Write(LastSerializedJson);
}
OnSaved?.Invoke();
if (_debugLogEnabled)
Debug.Log("<color=#f75369>Save Saved:</color> \n" + LastSerializedJson);
_lastSaveTime = Time.time;
#if UNITY_EDITOR
RepaintRequest?.Invoke();
#endif
}
catch (Exception e)
{
Debug.LogError($"Error saving file: {e.Message}");
}
}
public override void Load()
{
if (!FileExists)
{
Save();
}
try
{
using (StreamReader reader = new StreamReader(FilePath))
{
LastSerializedJson = reader.ReadToEnd();
if (_debugLogEnabled)
Debug.Log("<color=#f75369>Save Loaded:</color> \n" + LastSerializedJson);
var saveData = JsonUtility.FromJson(LastSerializedJson, GetGenericType);
RestoreSaveData(saveData);
OnLoaded?.Invoke();
#if UNITY_EDITOR
RepaintRequest?.Invoke();
#endif
}
}
catch (Exception e)
{
Debug.LogError($"Error loading file: {e.Message}");
}
}
private void RestoreSaveData(object data)
{
var saveData = data as T;
if (RequiresUpgrade(saveData))
{
Upgrade(saveData);
}
_data = saveData;
}
public override void Delete()
{
if (FileExists)
File.Delete(FilePath);
Debug.Log("<color=#f75369>Save Deleted: </color>" + FilePath);
LastSerializedJson = string.Empty;
ResetData();
OnDeleted?.Invoke();
#if UNITY_EDITOR
RepaintRequest?.Invoke();
#endif
}
public virtual void PrintToConsole()
{
Debug.Log($"<color=#f75369>Save Data:</color>\n{LastSerializedJson}");
}
public override void Update()
{
if (Time.time - _lastSaveTime >= _saveIntervalSeconds)
{
Save();
_lastSaveTime = Time.time;
}
}
public void OpenSaveLocation()
{
if (!System.IO.Directory.Exists(Directory))
{
Debug.LogWarning("Save directory does not exist yet.");
return;
}
try
{
#if UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
Process.Start("explorer.exe", Directory);
#elif UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX
Process.Start("open", Directory);
#elif UNITY_EDITOR_LINUX || UNITY_STANDALONE_LINUX
Process.Start("xdg-open", Directory);
#else
Debug.LogWarning("Opening save location is not supported on this platform.");
#endif
if (_debugLogEnabled)
Debug.Log($"Opened save location: {Directory}");
}
catch (Exception e)
{
Debug.LogError($"Failed to open save location: {e.Message}");
}
}
internal override void Reset()
{
base.Reset();
_debugLogEnabled = false;
_loadMode = ELoadMode.Automatic;
_saveMode = ESaveMode.Manual;
ResetData();
}
private void ResetData() => _data = new T();
protected virtual bool RequiresUpgrade(T saveData) => false;
protected virtual void Upgrade(T oldData)
{
}
#if UNITY_EDITOR
private void OnPlayModeStateChanged(PlayModeStateChange playModeStateChange)
{
if (playModeStateChange == PlayModeStateChange.ExitingEditMode)
{
if (_loadMode == ELoadMode.Automatic)
{
Load();
}
if (_saveMode == ESaveMode.Interval)
{
ScriptableObjectUpdateSystem.RegisterObject(this);
}
}
else if (playModeStateChange == PlayModeStateChange.EnteredEditMode)
{
ScriptableObjectUpdateSystem.UnregisterObject(this);
}
}
#endif
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: da429c369cbee6c4ab430b79eb741a7a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,32 @@
using System;
using UnityEngine;
namespace Obvious.Soap
{
[HelpURL("https://obvious-game.gitbook.io/soap/soap-core-assets/scriptable-save")]
public abstract class ScriptableSaveBase : ScriptableBase
{
//Override these methods to implement your own save/load/delete logic
public abstract void Save();
public abstract void Load();
public abstract void Delete();
/// <summary>
/// Called by the ScriptableObjectUpdateSystem if SaveMode is set to Interval.
/// Equivalent to MonoBehaviour.Update().
/// </summary>
public abstract void Update();
public enum ELoadMode
{
Automatic,
Manual
}
public enum ESaveMode
{
Manual,
Interval
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 5ceca6b4d98a475e96d20a97f1798f6d
timeCreated: 1725757344