Files
Convention-Unity-Demo/Assets/Scripts/Framework/ScriptableObject.cs

1017 lines
38 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using Convention;
using Convention.RScript;
using Convention.WindowsUI.Variant;
using Demo.Game;
using Dreamteck.Splines;
using Unity.VisualScripting;
using UnityEngine;
namespace Demo
{
public interface IScriptableObject
{
ScriptableObject SharedInterfaceScriptObject { get; }
}
#region ScriptableObject Inside
namespace PrivateType
{
public class EaseCurveTypeInstance
{
public static EaseCurveTypeInstance instance = new();
public MathExtension.EaseCurveType Linear => MathExtension.EaseCurveType.Linear;
public MathExtension.EaseCurveType InQuad => MathExtension.EaseCurveType.InQuad;
public MathExtension.EaseCurveType OutQuad => MathExtension.EaseCurveType.OutQuad;
public MathExtension.EaseCurveType InOutQuad => MathExtension.EaseCurveType.InOutQuad;
public MathExtension.EaseCurveType InCubic => MathExtension.EaseCurveType.InCubic;
public MathExtension.EaseCurveType OutCubic => MathExtension.EaseCurveType.OutCubic;
public MathExtension.EaseCurveType InOutCubic => MathExtension.EaseCurveType.InOutCubic;
public MathExtension.EaseCurveType InQuart => MathExtension.EaseCurveType.InQuart;
public MathExtension.EaseCurveType OutQuart => MathExtension.EaseCurveType.OutQuart;
public MathExtension.EaseCurveType InOutQuart => MathExtension.EaseCurveType.InOutQuart;
public MathExtension.EaseCurveType InQuint => MathExtension.EaseCurveType.InQuint;
public MathExtension.EaseCurveType OutQuint => MathExtension.EaseCurveType.OutQuint;
public MathExtension.EaseCurveType InOutQuint => MathExtension.EaseCurveType.InOutQuint;
public MathExtension.EaseCurveType InSine => MathExtension.EaseCurveType.InSine;
public MathExtension.EaseCurveType OutSine => MathExtension.EaseCurveType.OutSine;
public MathExtension.EaseCurveType InOutSine => MathExtension.EaseCurveType.InOutSine;
public MathExtension.EaseCurveType InExpo => MathExtension.EaseCurveType.InExpo;
public MathExtension.EaseCurveType OutExpo => MathExtension.EaseCurveType.OutExpo;
public MathExtension.EaseCurveType InOutExpo => MathExtension.EaseCurveType.InOutExpo;
public MathExtension.EaseCurveType InCirc => MathExtension.EaseCurveType.InCirc;
public MathExtension.EaseCurveType OutCirc => MathExtension.EaseCurveType.OutCirc;
public MathExtension.EaseCurveType InOutCirc => MathExtension.EaseCurveType.InOutCirc;
public MathExtension.EaseCurveType InBounce => MathExtension.EaseCurveType.InBounce;
public MathExtension.EaseCurveType OutBounce => MathExtension.EaseCurveType.OutBounce;
public MathExtension.EaseCurveType InOutBounce => MathExtension.EaseCurveType.InOutBounce;
public MathExtension.EaseCurveType InElastic => MathExtension.EaseCurveType.InElastic;
public MathExtension.EaseCurveType OutElastic => MathExtension.EaseCurveType.OutElastic;
public MathExtension.EaseCurveType InOutElastic => MathExtension.EaseCurveType.InOutElastic;
public MathExtension.EaseCurveType InBack => MathExtension.EaseCurveType.InBack;
public MathExtension.EaseCurveType OutBack => MathExtension.EaseCurveType.OutBack;
public MathExtension.EaseCurveType InOutBack => MathExtension.EaseCurveType.InOutBack;
public MathExtension.EaseCurveType Custom => MathExtension.EaseCurveType.Custom;
}
public class SplineComputerSampleModeInstance
{
public static SplineComputerSampleModeInstance instance = new();
public SplineComputer.SampleMode Default => SplineComputer.SampleMode.Default;
public SplineComputer.SampleMode Uniform => SplineComputer.SampleMode.Uniform;
public SplineComputer.SampleMode Optimized => SplineComputer.SampleMode.Optimized;
}
public class SplineTypeInstance
{
public static SplineTypeInstance instance = new();
public Spline.Type Linear => Spline.Type.Linear;
public Spline.Type BSpline => Spline.Type.BSpline;
public Spline.Type CatmullRom => Spline.Type.CatmullRom;
public Spline.Type Bezier => Spline.Type.Bezier;
}
public class IEffectHookObjectInstance
{
public static IEffectHookObjectInstance instance = new();
public IEffectHookObject.InteractiveEffectType VisibleDuration => IEffectHookObject.InteractiveEffectType.VisibleDuration;
public IEffectHookObject.InteractiveEffectType InteractiveDuration => IEffectHookObject.InteractiveEffectType.InteractiveDuration;
public IEffectHookObject.InteractiveEffectType InteractableScoreInterval => IEffectHookObject.InteractiveEffectType.InteractableScoreInterval;
public IEffectHookObject.InteractiveEffectType InteractableIntervalThatCanScoreBest => IEffectHookObject.InteractiveEffectType.InteractableIntervalThatCanScoreBest;
}
}
public partial class ScriptableObject : IScriptableObject
{
/// <summary>
/// 用于开放给倒置接口
/// </summary>
public ScriptableObject SharedInterfaceScriptObject => this;
}
/// <summary>
/// 初始化与对象设置
/// </summary>
public partial class ScriptableObject
{
[Content, SerializeField]
private Vector3
EnterGameLocalPosition = Vector3.zero,
EnterGameEulerAngles = Vector3.zero,
EnterGameLocalScaling = Vector3.one;
[Content, SerializeField] private bool IsSetObjectDisable = false;
[Content] public int UpdatePerFrame = 1;
[Content, SerializeField, Header("UpdateMode")]
protected UpdateMode _updateMode = UpdateMode.Permanent;
[Content, SerializeField]
protected float _activeStartTime = 0f;
[Content, SerializeField]
protected float _activeEndTime = float.MaxValue;
[Content, SerializeField]
private bool _useUpdateScheduler = false; // 控制是否使用新的UpdateScheduler
/// <summary>
/// 设置坐标
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
/// <param name="z"></param>
[Convention.RScript.Variable.Attr.Method]
public void SetLocalPosition(float x, float y, float z)
{
EnterGameLocalPosition = new(x, y, z);
}
/// <summary>
/// 设置欧拉角
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
/// <param name="z"></param>
[Convention.RScript.Variable.Attr.Method]
public void SetLocalEulerAngles(float x, float y, float z)
{
EnterGameEulerAngles = new(x, y, z);
}
/// <summary>
/// 设置缩放
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
/// <param name="z"></param>
[Convention.RScript.Variable.Attr.Method]
public void SetLocalScaling(float x, float y, float z)
{
EnterGameLocalScaling = new(x, y, z);
}
/// <summary>
/// 关闭该物体,
/// 在面对如多Game场景时关闭某些GameWorld中默认存在的全局灯光等场景时非常有用
/// </summary>
[Convention.RScript.Variable.Attr.Method]
public void SetObjectDisable()
{
IsSetObjectDisable = true;
}
/// <summary>
/// 指定多少个<see cref="TickType.Update"/>状态的<see cref="UpdateTicks(float, float, TickType)"/>执行一次更新,不会影响到子物体
/// 属于性能优化的高级选项
/// </summary>
/// <param name="frame">每frame帧更新一次, 等于0代表不会在<see cref="TickType.Update"/>状态运行</param>
[Convention.RScript.Variable.Attr.Method]
public void SetUpdatePerFrame(int frame)
{
UpdatePerFrame = Mathf.Max(1, frame);
}
/// <summary>
/// 启用新的UpdateScheduler用于逐步迁移
/// </summary>
[Convention.RScript.Variable.Attr.Method]
public void EnableUpdateScheduler()
{
_useUpdateScheduler = true;
}
/// <summary>
/// 子类可重写定义自己的Update模式
/// </summary>
protected virtual UpdateMode GetUpdateMode() => UpdateMode.Permanent;
/// <summary>
/// 子类可重写,定义自己的活跃时间范围
/// </summary>
protected virtual (float start, float end) GetActiveTimeRange()
=> (0f, float.MaxValue);
private void ScriptableObjectDoReset()
{
transform.localPosition = EnterGameLocalPosition;
transform.localEulerAngles = EnterGameEulerAngles;
transform.localScale = EnterGameLocalScaling;
gameObject.SetActive(IsSetObjectDisable == false);
}
}
/// <summary>
/// 跨脚本上下文变量
/// </summary>
public partial class ScriptableObject
{
public Dictionary<string, float> ScriptableObjectContents = new();
[Convention.RScript.Variable.Attr.Method]
public float GetContent(string key)
{
if (ScriptableObjectContents.TryGetValue(key, out var result))
{
return result;
}
if (Parent != null)
{
return Parent.GetContent(key);
}
throw new InvalidOperationException($"Key {key} is not find in contnet");
}
[Convention.RScript.Variable.Attr.Method]
public float SetContent(string key, float value)
{
ScriptableObjectContents[key] = value;
return value;
}
}
/// <summary>
/// 基础信息, 父子关系与EnableScript
/// </summary>
public partial class ScriptableObject
{
public static bool IsAutoPlay = false;
public static float OneBarTime = 60;
private bool isEnableScript = false;
public string ScriptName = "";
public ScriptableObject Parent;
public readonly List<ScriptableObject> Childs = new();
[Content, SerializeField] private List<ScriptableObject> UpdateChilds = new();
/// <summary>
/// 获取根脚本对象
/// </summary>
/// <returns></returns>
[Convention.RScript.Variable.Attr.Method]
public RootObject GetRoot()
{
if (Parent == null)
return this as RootObject;
if (Parent is RootObject result)
return result;
else
return Parent.GetRoot();
}
public const string RootObjectQuickPath = "project/";
public ScriptableObject FindWithPath(string path, bool isMustExist = true)
{
if (string.IsNullOrEmpty(path))
{
if (isMustExist)
throw new Exception("path is null or empty");
else
return null;
}
// GetParent
ScriptableObject result = Parent;
if (path.Replace('\\', '/').ToLower().StartsWith(RootObjectQuickPath))
{
result = GetRoot();
path = path[RootObjectQuickPath.Length..];
}
if (Parent == null)
{
throw new InvalidOperationException($"Root is nosupport to {nameof(FindWithPath)}");
}
// Find
var components = path.Split('/', '\\');
components[^1] = new ToolFile(components[^1]).GetFilename(true);
foreach (var component in components)
{
if (component == "..")
result = result.Parent;
else if (component == "." || string.IsNullOrEmpty(component))
continue;
else
{
int index = 0;
string targetScriptObjectPath = component;
Regex regex = new(@"^(.*)\[(\d*)\]$");
var match = regex.Match(component);
if (match.Success)
{
targetScriptObjectPath = match.Groups[1].Value;
index = int.Parse(match.Groups[2].Value);
}
var target = result.Childs.FirstOrDefault(x =>
{
bool stats = x.ScriptName == targetScriptObjectPath;
if (index == 0)
return stats;
else if (stats)
index--;
return false;
});
if (target != null)
result = target;
else
{
if (isMustExist)
throw new Exception($"{component} in {path} is not found");
else
{
Debug.Log($"{component} in {path} is not found", this);
return null;
}
}
}
}
return result;
}
public void EnableScript(ScriptableObject parent)
{
if (isEnableScript)
{
Debug.LogError($"ScriptableObject is currently enableScript, start coroutine {nameof(UnloadScript)} to disable", this);
return;
}
this.Parent = parent;
this.name = ScriptName;
isEnableScript = true;
// 只有RootObject的parent会是空的
if (parent != null)
{
MyHierarchyItem = parent.MyHierarchyItem.GetHierarchyItem().CreateSubPropertyItem(1)[0];
}
else
{
MyHierarchyItem = HierarchyWindow.instance.CreateRootItemEntryWithBinders(1)[0];
}
MyHierarchyItem.GetHierarchyItem().title = this.ScriptName + $"<{this.GetType()}>";
MyHierarchyItem.GetHierarchyItem().target = this;
MyHierarchyItem.GetHierarchyItem().ButtonGameObject.GetComponent<Editor.UI.RightClick>().ScriptObjectMenu = OnHierarchyItemRightClick;
}
#region Hierarchy
public PropertiesWindow.ItemEntry MyHierarchyItem;
public static PropertiesWindow.ItemEntry AllScriptableObjectCounterHierarchyItem;
public bool EnsureEnableScript()
{
if (isEnableScript == false)
{
Debug.LogError("ScriptableObject is currently disableScript", this);
}
return isEnableScript;
}
#endregion
}
/// <summary>
/// 脚本解析与Update执行
/// </summary>
public partial class ScriptableObject
{
public static Dictionary<string, Type> FastScriptableObjectTypen = new();
public static int AllScriptableObjectCounter = 0;
public bool IsParseScript2Expr { get; private set; } = false;
protected virtual bool IsSelfEnableUpdate { get => true; }
public bool IsEnableUpdate { get; private set; } = true;
#region LoadSubScript
/// <summary>
/// 创建基于子脚本的实例对象
/// </summary>
/// <param name="type"></param>
/// <param name="name"></param>
/// <returns></returns>
[Convention.RScript.Variable.Attr.Method]
public ScriptableObject LoadSubScript([In] string type, [In] string name, [In] string path)
{
// 判断类型是否合法
if (DefaultInstantiate.GetScriptableObjectInstantiate().TryGetValue(type, out var creater) == false)
{
Debug.LogError($"{type} is not exist or {type}'s Instantiater is not valid", this);
return null;
}
// 生成对象
var child = creater();
// 获取文件
var file = new ToolFile(GetRoot().SourcePath);
file = file | path;
// 找不到脚本
if (file.Exists() == false)
{
Debug.LogError($"{file}<{path}> is not found", this);
return null;
}
child.ScriptName = name;
child.transform.SetParent(this.transform);
child.transform.SetLocalPositionAndRotation(Vector3.zero, Quaternion.identity);
child.transform.localScale = Vector3.one;
child.EnableScript(this);
// Add Child
Childs.Add(child);
UpdateChilds.Add(child);
// Load Child Script
ConventionUtility.StartCoroutine(child.ParseScript2Expr(file.LoadAsText()));
return child;
}
/// <summary>
/// 创建不基于子脚本的实例对象
/// </summary>
/// <param name="type"></param>
/// <param name="name"></param>
/// <returns></returns>
[Convention.RScript.Variable.Attr.Method]
public ScriptableObject NewSubScript([In] string type, string name)
{
// 判断类型是否合法
if (DefaultInstantiate.GetScriptableObjectInstantiate().TryGetValue(type, out var creater) == false)
{
Debug.LogError($"{type} is not exist or {type}'s Instantiater is not valid", this);
return null;
}
// 生成对象
var child = creater();
child.ScriptName = name;
child.transform.SetParent(this.transform);
child.transform.SetLocalPositionAndRotation(Vector3.zero, Quaternion.identity);
child.transform.localScale = Vector3.one;
child.EnableScript(this);
// Add Child
Childs.Add(child);
UpdateChilds.Add(child);
return child;
}
/// <summary>
/// 获取已加载的脚本对象
/// </summary>
/// <returns>无法找到时将返回空</returns>
[Convention.RScript.Variable.Attr.Method]
public ScriptableObject GetLoadedObject(string path)
{
return FindWithPath(path, false);
}
/// <summary>
/// 获取父脚本对象
/// </summary>
/// <returns></returns>
[Convention.RScript.Variable.Attr.Method]
public ScriptableObject GetParentObject()
{
return Parent;
}
#endregion
public static class RandomTool
{
public static float Random(float min, float max)
{
return UnityEngine.Random.Range(min, max);
}
public static float Random(double min, double max)
{
return UnityEngine.Random.Range((float)min, (float)max);
}
}
public class ConsoleTool
{
private GameObject gameObject;
public ConsoleTool(GameObject gameObject)
{
this.gameObject = gameObject;
}
public void Log(object obj)
{
Debug.Log(obj, gameObject);
}
}
public IEnumerator ParseScript2Expr(string script)
{
IsParseScript2Expr = true;
try
{
RScriptEngine engine = new();
RScriptImportClass importClass = new()
{
typeof(Mathf),
typeof(RandomTool),
};
RScriptVariables variables = new()
{
{ "this", new() { data = this, type = this.GetType() } },
{ "self", new() { data = this, type = this.GetType() } },
{ "console", new() { data = new ConsoleTool(gameObject), type = typeof(ConsoleTool) } },
{ nameof(MathExtension.EaseCurveType), new() { data = PrivateType.EaseCurveTypeInstance.instance, type = typeof(PrivateType.EaseCurveTypeInstance) } },
{ $"Spline{nameof(SplineComputer.SampleMode)}",
new() { data = PrivateType.SplineComputerSampleModeInstance.instance, type = typeof(PrivateType.SplineComputerSampleModeInstance)} },
{ $"Spline{nameof(Spline.Type)}",
new() { data = PrivateType.SplineTypeInstance.instance, type = typeof(PrivateType.SplineTypeInstance)} },
{ nameof(IEffectHookObject.InteractiveEffectType),
new() { data = PrivateType.IEffectHookObjectInstance.instance, type = typeof(PrivateType.IEffectHookObjectInstance)} }
};
foreach (var ir in engine.RunAsync(script, importClass, variables).Yield())
{
yield return ir;
}
}
finally
{
IsParseScript2Expr = false;
}
}
public enum TickType
{
Reset,
Pause,
Start,
Update
}
[Content, SerializeField] private int ScriptUpdateCounter = 0;
public void ScriptUpdate(float currentTime, float deltaTime, TickType tickType)
{
if (IsScriptApply == false)
return;
if (gameObject.activeInHierarchy == false)
return;
using (Profiler.BeginZone($"{m_GetTypeName}.ScriptUpdate"))
{
if (tickType == TickType.Reset)
{
ResetEnterGameStatus();
// Childs UpdateTicks
foreach (var child in Childs)
{
child.ScriptUpdate(currentTime, deltaTime, tickType);
}
}
else
{
// UpdateTicks
if (this.IsSelfEnableUpdate && UpdatePerFrame > 0)
{
if (ScriptUpdateCounter % UpdatePerFrame == 0)
using (Profiler.BeginZone($"{this.ScriptName}.UpdateTicks"))
UpdateTicks(currentTime, deltaTime, tickType);
ScriptUpdateCounter += tickType == TickType.Update ? 1 : 0;
}
// Childs UpdateTicks
foreach (var child in UpdateChilds)
{
child.ScriptUpdate(currentTime, deltaTime, tickType);
}
}
}
}
protected virtual void UpdateTicks(float currentTime, float deltaTime, TickType tickType)
{
}
}
/// <summary>
/// <para>使用<see cref="Convention.RScript.Variable.Attr.MethodAttribute"/>标记可编辑脚本所能够调用的函数</para>
/// <para>使用<see cref="Convention.RScript.Variable.Attr.DefaultAttribute"/>标记派生类,并附加默认模板</para>
/// </summary>
public partial class ScriptableObject :
#if ENABLE_SerializedMonoBehaviour_CLASS
SerializedMonoBehaviour,
#else
MonoBehaviour,
#endif
IHierarchyItemClickEventListener
{
protected virtual IEnumerator DoSomethingDuringApplyScript()
{
return null;
}
[Content]
public bool IsScriptApply { get; private set; } = false;
/// <summary>
/// 确认脚本已经完全执行完成, 允许其进入工作阶段
/// <b>注意不要使用this.ApplyScript</b>, 将会导致
/// <code>
/// while (this.IsParseScript2Expr)
/// {
/// yield return null;
/// }
/// </code>
/// 永远等待
/// </summary>
/// <returns></returns>
[Convention.RScript.Variable.Attr.Method]
public IEnumerator ApplyScript()
{
if (EnsureEnableScript() == false)
{
yield break;
}
// 等待自身脚本解析完毕
while (this.IsParseScript2Expr)
{
yield return null;
}
yield return DoSomethingDuringApplyScript();
// 增数
{
AllScriptableObjectCounter++;
AllScriptableObjectCounterHierarchyItem.GetHierarchyItem().text = $"ScriptableObjectCount: {AllScriptableObjectCounter}";
}
// 统计更新能力
{
IsEnableUpdate = this.IsSelfEnableUpdate;
foreach (var child in this.Childs)
{
if (IsEnableUpdate)
break;
IsEnableUpdate |= child.IsEnableUpdate;
}
if (IsEnableUpdate == false && Parent != null)
Parent.UpdateChilds.Remove(this);
// 当只存在一个需要更新的子物体并且自身不需要更新时, 直接简化调用
if (this.IsSelfEnableUpdate == false && this.UpdateChilds.Count == 1)
{
Parent.UpdateChilds.Remove(this);
var child = this.UpdateChilds[0];
this.UpdateChilds.Clear();
Parent.UpdateChilds.Add(child);
IsEnableUpdate = false;
}
}
// 可选的调度器注册
if (_useUpdateScheduler && (IsSelfEnableUpdate || IsEnableUpdate))
{
RegisterToScheduler();
}
IsScriptApply = true;
}
/// <summary>
/// 注册到UpdateScheduler
/// </summary>
private void RegisterToScheduler()
{
// 获取Update模式
_updateMode = GetUpdateMode();
// 获取时间范围
if (_updateMode == UpdateMode.TimeBound)
{
(_activeStartTime, _activeEndTime) = GetActiveTimeRange();
}
// 注册到调度器
try
{
GetRoot()?.Scheduler?.Register(this, _updateMode, _activeStartTime, _activeEndTime);
Debug.Log($"[ScriptableObject] 已注册到调度器: {GetUpdateName()}, Mode={_updateMode}");
}
catch (System.Exception ex)
{
Debug.LogError($"[ScriptableObject] 注册到调度器失败: {ex.Message}", this);
}
}
public virtual IEnumerator UnloadScript()
{
if (EnsureEnableScript())
{
this.name = "<Unloading>";
try
{
foreach (var child in Childs)
{
yield return child.UnloadScript();
}
}
finally
{
this.isEnableScript = false;
this.Parent = null;
this.name = "<Unload>";
if (IsScriptApply)
{
// 清理各种状态
IsScriptApply = false;
// 清理Cache
//
// 减数
AllScriptableObjectCounter--;
AllScriptableObjectCounterHierarchyItem.GetHierarchyItem().text = $"ScriptableObjectCount: {AllScriptableObjectCounter}";
}
if (MyHierarchyItem != null)
{
// 卸载UI
MyHierarchyItem.Release();
MyHierarchyItem = null;
}
}
}
}
public virtual void ResetEnterGameStatus()
{
ScriptableObjectDoReset();
}
public virtual void OnHierarchyItemRightClick(RectTransform item)
{
DefaultInstantiate.OpenInstantiateMenu(this, item);
}
public virtual void OnHierarchyItemClick(HierarchyItem item)
{
}
}
/// <summary>
/// IUpdateable接口实现
/// </summary>
public partial class ScriptableObject : IUpdateable
{
/// <summary>
/// IUpdateable.FlatOptimizationUpdate实现 - 扁平化优化Update入口直接更新对象自身无递归遍历子对象
/// </summary>
public void FlatOptimizationUpdate(float currentTime, float deltaTime, TickType tickType)
{
if (IsScriptApply == false)
return;
if (gameObject.activeInHierarchy == false)
return;
// 只更新自己,不递归子节点
if (this.IsSelfEnableUpdate && UpdatePerFrame > 0)
{
if (ScriptUpdateCounter % UpdatePerFrame == 0)
{
using (Profiler.BeginZone($"{this.ScriptName}.UpdateTicks"))
{
UpdateTicks(currentTime, deltaTime, tickType);
}
}
ScriptUpdateCounter += tickType == TickType.Update ? 1 : 0;
}
}
private string c_GetTypeName;
private string m_GetTypeName
{
get
{
if (string.IsNullOrEmpty(c_GetTypeName))
{
c_GetTypeName = GetType().Name;
}
return c_GetTypeName;
}
}
/// <summary>
/// IUpdateable.GetUpdateName实现
/// </summary>
public string GetUpdateName()
{
return $"{ScriptName}<{m_GetTypeName}>";
}
/// <summary>
/// IUpdateable.IsUpdateReady实现
/// </summary>
public bool IsUpdateReady => IsScriptApply;
}
#endregion
public interface IAssetBundleLoader : IScriptableObject
{
public RootObject GetRoot();
}
public static class AssetBundlesLoadHelper
{
public struct Entry
{
public AssetBundle assetBundle;
public int referenceCounter;
public PropertiesWindow.ItemEntry entry;
public Entry(AssetBundle assetBundle, int referenceCounter = 0, PropertiesWindow.ItemEntry entry = null)
{
this.assetBundle = assetBundle;
this.referenceCounter = referenceCounter;
this.entry = entry;
}
}
public static Dictionary<string, Entry> AssetBundleCounter = new();
private static Dictionary<string, IEnumerator> AssetBundleLoading = new();
private static PropertiesWindow.ItemEntry AssetBundlesItemEntry = null;
public static IEnumerator LoadAssetBundleAsync(string ab, Action<AssetBundle> callback)
{
if (AssetBundleCounter.TryGetValue(ab, out var result))
{
result.referenceCounter++;
callback?.Invoke(result.assetBundle);
yield break;
}
if (AssetBundleLoading.TryGetValue(ab, out var tir))
{
yield return tir;
if (AssetBundleCounter.TryGetValue(ab, out result))
{
result.referenceCounter++;
callback?.Invoke(result.assetBundle);
yield break;
}
}
var file = new ToolFile(PlatformIndicator.StreamingAssetsPath) | "AssetBundle/";
#if PLATFORM_WINDOWS
file = file | "Windows";
#endif
file = file | ab;
#if ENABLE_CLASS_Interaction
var downloader = new Interaction(file.GetFullPath());
#else
var downloader = new ToolFile(file.GetFullPath());
#endif
if (AssetBundlesItemEntry == null)
{
var hierarchy = HierarchyWindow.instance;
AssetBundlesItemEntry = hierarchy.CreateRootItemEntryWithBinders(nameof(AssetBundlesLoadHelper))[0];
AssetBundlesItemEntry.ref_value.GetComponent<HierarchyItem>().title = nameof(AssetBundlesLoadHelper);
}
var abLoadingItem = AssetBundlesItemEntry.ref_value.GetComponent<HierarchyItem>().CreateSubPropertyItem(1)[0];
var loadingHierarchyItem = abLoadingItem.ref_value.GetComponent<HierarchyItem>();
loadingHierarchyItem.title = $"{ab}<Loading>";
var ir = downloader.LoadAsAssetBundle(p =>
{
loadingHierarchyItem.title = $"{ab}<Loading{(int)(p * 100)}%>";
}, x =>
{
if (x != null)
{
loadingHierarchyItem.title = $"{ab}";
var assets = x.GetAllAssetNames();
var subAssetsItems = loadingHierarchyItem.CreateSubPropertyItem(assets.Length);
for (int i = 0, e = assets.Length; i < e; i++)
{
subAssetsItems[i].ref_value.GetComponent<HierarchyItem>().title = Path.GetFileName(assets[i]);
}
AssetBundleCounter[ab] = new(x, 1, abLoadingItem);
}
else
{
loadingHierarchyItem.title = $"{ab}<Failed>";
}
AssetBundleLoading.Remove(ab);
callback?.Invoke(x);
});
AssetBundleLoading.Add(ab, ir);
yield return ir;
AssetBundleLoading.Remove(ab);
}
public static IEnumerator LoadAssetBundle(this IAssetBundleLoader self, string ab, Action<AssetBundle> callback)
{
yield return LoadAssetBundleAsync(ab, callback);
}
public static IEnumerator UnloadAssetBundle(this IAssetBundleLoader self, string ab)
{
// Editor中暂时忽略卸载功能
yield break;
}
}
public abstract class TimelineScriptObject : ScriptableObject
{
public static PropertiesWindow TimelineWindow;
private PropertiesWindow.ItemEntry MyTimelineEntry;
private Editor.UI.TimelineItem MyTimelineItem;
//[SerializeField] private bool IsTimelineItemShow = false;
private static List<TimelineScriptObject> TimelineScriptObjectWhichOnShow = new();
private static UnityEngine.UI.Image CacheLastFocusImage;
private static Color CacheLastFocusImageOriginColor = new(1, 1, 1, 0.01f);
private static Color FocusImageColor = new(1f, 47f / 51f, 0.0156862754f, 0.1f);
protected override IEnumerator DoSomethingDuringApplyScript()
{
yield return base.DoSomethingDuringApplyScript();
if (MyTimelineEntry == null)
{
MyTimelineEntry = TimelineWindow.CreateRootItemEntries(1)[0];
MyTimelineItem = MyTimelineEntry.ref_value.GetComponent<Editor.UI.TimelineItem>();
}
MyTimelineItem.title = ScriptName;
MyTimelineItem.RawButton.onClick.RemoveAllListeners();
MyTimelineItem.AddListener(() =>
{
HierarchyWindow.instance.MakeFocusOn(MyHierarchyItem.GetHierarchyItem());
if (CacheLastFocusImage != null)
CacheLastFocusImage.color = CacheLastFocusImageOriginColor;
CacheLastFocusImage = MyHierarchyItem.GetHierarchyItem().ButtonGameObject.GetComponent<UnityEngine.UI.Image>();
CacheLastFocusImage.color = FocusImageColor;
});
SetupTimelineItem(MyTimelineItem);
// 暂时的逻辑是总是展示的
{
TimelineScriptObjectWhichOnShow.Add(this);
}
}
public override IEnumerator UnloadScript()
{
yield return base.UnloadScript();
// 这里的两处判空是因为如果抛出错误就会打断了逻辑, 所以这里需要判断
if (MyTimelineItem != null)
{
MyTimelineItem.RawButton.onClick.RemoveAllListeners();
MyTimelineItem = null;
}
if (MyTimelineEntry != null)
{
MyTimelineEntry.Release();
MyTimelineEntry = null;
}
}
public override void OnHierarchyItemClick(HierarchyItem item)
{
}
private float UIResizeOnTimelineCount = 0;
protected override void UpdateTicks(float currentTime, float deltaTime, TickType tickType)
{
base.UpdateTicks(currentTime, deltaTime, tickType);
// 存在严重的性能开销, 在解决之前将不会允许其快速自动更新
using (Profiler.BeginZone($"{nameof(TimelineScriptObject)}.{nameof(MyTimelineItem.ResizeOnTimeline)}"))
{
if (UIResizeOnTimelineCount > 0.1 || tickType != TickType.Update)
{
UIResizeOnTimelineCount = 0;
MyTimelineItem.ResizeOnTimeline();
}
UIResizeOnTimelineCount += deltaTime;
}
}
protected abstract void SetupTimelineItem(Editor.UI.TimelineItem item);
protected virtual Color GetTimelineItemColor()
{
static float Foo(uint hash)
{
float a = (hash % 10) * 0.1f;
return a;
}
var a1 = (uint)this.GetType().GetHashCode();
var a2 = (uint)a1.ToString().GetHashCode();
var a3 = (uint)(a2.ToString().GetHashCode() >> 5);
var color = new Vector3(Foo(a1), Foo(a2), Foo(a3)).normalized;
return new(color.x, color.y, color.z);
}
}
}