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 public partial class ScriptableObject : IScriptableObject { /// /// 用于开放给倒置接口 /// public ScriptableObject SharedInterfaceScriptObject => this; } /// /// 初始化与对象设置 /// 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; /// /// 设置坐标 /// /// /// /// [Convention.RScript.Variable.Attr.Method] public void SetLocalPosition(float x, float y, float z) { EnterGameLocalPosition = new(x, y, z); } /// /// 设置欧拉角 /// /// /// /// [Convention.RScript.Variable.Attr.Method] public void SetLocalEulerAngles(float x, float y, float z) { EnterGameEulerAngles = new(x, y, z); } /// /// 设置缩放 /// /// /// /// [Convention.RScript.Variable.Attr.Method] public void SetLocalScaling(float x, float y, float z) { EnterGameLocalScaling = new(x, y, z); } /// /// 关闭该物体, /// 在面对如多Game场景时关闭某些GameWorld中默认存在的全局灯光等场景时非常有用 /// [Convention.RScript.Variable.Attr.Method] public void SetObjectDisable() { IsSetObjectDisable = true; } /// /// 指定多少个状态的执行一次更新,不会影响到子物体 /// 属于性能优化的高级选项 /// /// 每frame帧更新一次, 等于0代表不会在状态运行 [Convention.RScript.Variable.Attr.Method] public void SetUpdatePerFrame(int frame) { UpdatePerFrame = Mathf.Max(1, frame); } private void ScriptableObjectDoReset() { transform.localPosition = EnterGameLocalPosition; transform.localEulerAngles = EnterGameEulerAngles; transform.localScale = EnterGameLocalScaling; gameObject.SetActive(IsSetObjectDisable == false); } } /// /// 跨脚本上下文变量 /// public partial class ScriptableObject { public readonly Dictionary ScriptableObjectContents = new(); [Convention.RScript.Variable.Attr.Method] public float GetContent(string key) { if (ScriptableObjectContents.TryGetValue(key, out var result)) { return result; } 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; } } /// /// 基础信息, 父子关系与EnableScript /// public partial class ScriptableObject { public static bool IsAutoPlay = false; public static float OneBarTime = 60; private bool isEnableScript = false; public string ScriptName = ""; private string s_ScriptType = null; public string m_ScriptType { get { if (s_ScriptType == null) s_ScriptType = this.GetType().Name; return s_ScriptType; } } public ScriptableObject Parent; public readonly List Childs = new(); /// /// 获取根脚本对象 /// /// [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; if (parent != null) this.ScriptableObjectContents.AddRange(parent.ScriptableObjectContents); 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 + $"<{m_ScriptType}>"; MyHierarchyItem.GetHierarchyItem().target = this; MyHierarchyItem.GetHierarchyItem().ButtonGameObject.GetComponent().ScriptObjectMenu = OnHierarchyItemRightClick; // 增数 { EnableScriptableObjectCounter++; AllScriptableObjectCounterHierarchyItem.GetHierarchyItem().text = $"SOC: {ApplyScriptableObjectCounter}/{EnableScriptableObjectCounter}"; } } #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 } /// /// 脚本解析与Update执行 /// public partial class ScriptableObject { public static Dictionary FastScriptableObjectTypen = new(); public static int EnableScriptableObjectCounter = 0; public static int ApplyScriptableObjectCounter = 0; public bool IsParseScript2Expr { get; private set; } = false; protected virtual bool IsSelfEnableUpdate { get => true; } #region LoadSubScript internal static int LoadingTaskCoroutineCount { get; set; } = 0; internal readonly static LinkedList WaitingTaskForLoadingCoroutine = new(); private readonly static WaitForEndOfFrame WaitForEndOfFrameInstance = new(); private const int MaxLoadingCoroutine = 100; /// /// 创建基于子脚本的实例对象 /// /// /// /// [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); // Load Child Script IEnumerator Foo() { // 缓存并延迟运行 yield return WaitForEndOfFrameInstance; yield return child.ParseFromScriptFile2Expr(file); LoadingTaskCoroutineCount--; } child.IsParseScript2Expr = true; WaitingTaskForLoadingCoroutine.AddLast(Foo()); while (LoadingTaskCoroutineCount < MaxLoadingCoroutine && WaitingTaskForLoadingCoroutine.Count > 0) { LoadingTaskCoroutineCount++; ConventionUtility.StartCoroutine(WaitingTaskForLoadingCoroutine.First.Value); WaitingTaskForLoadingCoroutine.RemoveFirst(); } return child; } /// /// 创建不基于子脚本的实例对象 /// /// /// /// [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); return child; } /// /// 获取已加载的脚本对象 /// /// 无法找到时将返回空 [Convention.RScript.Variable.Attr.Method] public ScriptableObject GetLoadedObject(string path) { return FindWithPath(path, false); } /// /// 获取父脚本对象 /// /// [Convention.RScript.Variable.Attr.Method] public ScriptableObject GetParentObject() { return Parent; } #endregion 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 (tickType == TickType.Reset) { ResetEnterGameStatus(); foreach (var child in Childs) { child.ScriptUpdate(currentTime, deltaTime, tickType); } } else { // UpdateTicks if (ScriptUpdateCounter % UpdatePerFrame == 0) UpdateTicks(currentTime, deltaTime, tickType); ScriptUpdateCounter += tickType == TickType.Update ? 1 : 0; } } protected virtual void UpdateTicks(float currentTime, float deltaTime, TickType tickType) { } } /// /// 使用标记可编辑脚本所能够调用的函数 /// 使用标记派生类,并附加默认模板 /// public partial class ScriptableObject : #if ENABLE_SerializedMonoBehaviour_CLASS SerializedMonoBehaviour, #else MonoBehaviour, #endif IHierarchyItemClickEventListener { #if UNITY_EDITOR internal static readonly HashSet s_DebugContainer = new(); private void Awake() { s_DebugContainer.Add(this); } private void OnDestroy() { s_DebugContainer.Remove(this); } #endif protected virtual IEnumerator DoSomethingDuringApplyScript() { yield break; } [Content] public bool IsScriptApply { get; private set; } = false; /// /// 确认脚本已经完全执行完成, 允许其进入工作阶段 /// 注意不要使用this.ApplyScript, 将会导致 /// /// while (this.IsParseScript2Expr) /// { /// yield return null; /// } /// /// 永远等待 /// /// [Convention.RScript.Variable.Attr.Method] public IEnumerator ApplyScript() { if (EnsureEnableScript() == false) { yield break; } if (IsScriptApply) yield break; // 等待自身脚本解析完毕 while (this.IsParseScript2Expr) { yield return this; } while (LoadingTaskCoroutineCount < MaxLoadingCoroutine && WaitingTaskForLoadingCoroutine.Count > 0) { LoadingTaskCoroutineCount++; ConventionUtility.StartCoroutine(WaitingTaskForLoadingCoroutine.First.Value); WaitingTaskForLoadingCoroutine.RemoveFirst(); } yield return DoSomethingDuringApplyScript(); // 增数 { ApplyScriptableObjectCounter++; AllScriptableObjectCounterHierarchyItem.GetHierarchyItem().text = $"SOC: {ApplyScriptableObjectCounter}/{EnableScriptableObjectCounter}"; } // 统计更新能力 { if (this.IsSelfEnableUpdate) { var type = this.GetType(); if (GetRoot().UpdateChilds.TryGetValue(type, out var scriptables)) scriptables.Add(this); else GetRoot().UpdateChilds[type] = new() { this }; } } // 释放资源 { this.ScriptableObjectContents.Clear(); } IsScriptApply = true; } public virtual IEnumerator UnloadScript() { if (IsScriptApply) { this.name = ""; foreach (var child in Childs) { ConventionUtility.StartCoroutine(child.UnloadScript()); } yield return new WaitUntil(() => Childs.Any(x => x.isEnableScript == false) == false); // 清理各种状态 IsScriptApply = false; // 清理Cache // // 减数 ApplyScriptableObjectCounter--; EnableScriptableObjectCounter--; AllScriptableObjectCounterHierarchyItem.GetHierarchyItem().text = $"SOC: {ApplyScriptableObjectCounter}/{EnableScriptableObjectCounter}"; if (MyHierarchyItem != null) { // 卸载UI MyHierarchyItem.Release(); MyHierarchyItem = null; } this.isEnableScript = false; this.Parent = null; this.name = ""; } } public virtual void ResetEnterGameStatus() { ScriptableObjectDoReset(); } public virtual void OnHierarchyItemRightClick(RectTransform item) { DefaultInstantiate.OpenInstantiateMenu(this, item); } public virtual void OnHierarchyItemClick(HierarchyItem item) { } } #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 AssetBundleCounter = new(); private static Dictionary AssetBundleLoading = new(); private static PropertiesWindow.ItemEntry AssetBundlesItemEntry = null; public static IEnumerator LoadAssetBundleAsync(string ab, Action 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().title = nameof(AssetBundlesLoadHelper); } var abLoadingItem = AssetBundlesItemEntry.ref_value.GetComponent().CreateSubPropertyItem(1)[0]; var loadingHierarchyItem = abLoadingItem.ref_value.GetComponent(); loadingHierarchyItem.title = $"{ab}"; var ir = downloader.LoadAsAssetBundle(p => { loadingHierarchyItem.title = $"{ab}"; }, 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().title = Path.GetFileName(assets[i]); } AssetBundleCounter[ab] = new(x, 1, abLoadingItem); } else { loadingHierarchyItem.title = $"{ab}"; } 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 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 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); [Content, SerializeField] private bool IsEnableTimelineItem = false; [Convention.RScript.Variable.Attr.Method] public void EnableTimelineItem() { IsEnableTimelineItem = true; } protected override IEnumerator DoSomethingDuringApplyScript() { yield return base.DoSomethingDuringApplyScript(); if (IsEnableTimelineItem) { if (MyTimelineEntry == null) { MyTimelineEntry = TimelineWindow.CreateRootItemEntries(1)[0]; MyTimelineItem = MyTimelineEntry.ref_value.GetComponent(); } 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(); 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); // 存在严重的性能开销, 在解决之前将不会允许其快速自动更新 if (IsEnableTimelineItem) { 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); } } }