using System; using System.Collections; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using System.Text.RegularExpressions; using Convention; using Convention.WindowsUI.Variant; using Demo.Game; using Unity.Profiling; using UnityEngine; namespace Demo { public enum ProjectDefaultFileStyle { CPP, PY } [System.AttributeUsage(AttributeTargets.Method, Inherited = true, AllowMultiple = false)] public sealed class ScriptableCallAttribute : Attribute { public string Description; public ScriptableCallAttribute(string description) { this.Description = description; } } public static class ScriptCallUtility { // 解析函数调用的方法 public static (string functionName, string[] arguments) ParseFunctionCall(string input) { // 匹配函数名和参数部分 string pattern = @"^(\w+)\s*\(\s*(.*?)\s*\)\s*;?$"; Match match = Regex.Match(input, pattern); if (!match.Success) return (null, new string[0]); string functionName = match.Groups[1].Value; string argumentsString = match.Groups[2].Value; // 解析参数数组 string[] arguments = ParseArguments(argumentsString); return (functionName, arguments); } // 解析参数的方法 private static string[] ParseArguments(string argumentsString) { if (string.IsNullOrWhiteSpace(argumentsString)) return new string[0]; // 处理字符串字面量和普通参数的正则表达式 string argPattern = @"""(?:[^""\\]|\\.)*""|[^,]+"; return Regex.Matches(argumentsString, argPattern) .Cast() .Select(m => m.Value.Trim()) .Where(arg => !string.IsNullOrEmpty(arg)) .ToArray(); } } [System.AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)] public sealed class DefaultScriptAttribute : Attribute { public string DefaultScript; public DefaultScriptAttribute(string script) { this.DefaultScript = script; } } public static class DefaultScriptUtility { public static void WriteStyleMethodHelper(StreamWriter stream,MethodInfo methodInfo) { } public static void WriteDefaultScript(StreamWriter fs, Type type) { if (typeof(ScriptableObject).IsAssignableFrom(type) == false) throw new InvalidOperationException($"{type.FullName} is not inherited from {typeof(ScriptableObject).FullName}"); var attr = type.GetCustomAttribute(); var typeName = type.Name; if (typeName.Contains('`')) { typeName = typeName[..typeName.LastIndexOf('`')]; } switch (Editor.EditorController.instance.MainGameController.CurrentProjectDefaultFileStyle) { case ProjectDefaultFileStyle.CPP: { fs.WriteLine($"#include \"{typeName}.helper.h\""); if (attr == null) { var scriptCalls = (from info in type.GetMembers(BindingFlags.Public | BindingFlags.Instance) where info is MethodInfo where info.GetCustomAttribute(false) != null select info as MethodInfo).ToList(); if (scriptCalls.Count > 0) { foreach (var scriptCall in scriptCalls) { fs.WriteLine($"#ifndef {scriptCall.Name}"); fs.WriteLine($"#define {scriptCall.Name}({string.Join(',', (from param in scriptCall.GetParameters() select param.Name))})"); fs.WriteLine($"#endif"); } } } else { var text = attr.DefaultScript; fs.Write(text); } } break; case ProjectDefaultFileStyle.PY: { } break; } } public static void OpenScriptFile(ScriptableObject so) { string path = so.ScriptPath; var ScriptEditor = so.GetRoot().RootGameController.WhichOpenScript; try { System.Diagnostics.Process.Start(string.Format($"{ScriptEditor}", $"\"{path}\"")); } catch (Exception ex) { Debug.LogError($"Cannt open {path}: {string.Format($"{ScriptEditor}", $"\"{path}\"")}", so); Debug.LogException(ex, so); } } } public interface IScriptableObject { ScriptableObject SharedInterfaceScriptObject { get; } } #region ScriptableObject Inside public partial class ScriptableObject : IScriptableObject { /// /// 用于开放给倒置接口 /// public ScriptableObject SharedInterfaceScriptObject => this; } public partial class ScriptableObject { // 时间点系统 public static Dictionary TimePointDelta = new(); public static Dictionary TimePoints = new(); /// /// 重设指定时间线 /// /// 时间线ID,若不存在则创建 /// 当每次调用NextTimePoint函数时使用的单位值 /// 初始化时间 [ScriptableCall(@" 重设指定时间线 时间线ID,若不存在则创建 当每次调用NextTimePoint函数时使用的单位值 初始化时间 ")] public void ResetTimePoint(string id, string delta, string value) { TimePointDelta[id] = float.Parse(delta); TimePoints[id] = float.Parse(value); } /// /// 推动时间线前进 /// /// 时间线ID /// 前进次数,最终时间的增量为前进次数乘该时间线的单位值 [ScriptableCall(@" 推动时间线前进 时间线ID 前进次数,最终时间的增量为前进次数乘该时间线的单位值 ")] public void NextTimePoint(string id, string times) { TimePoints[id] += TimePointDelta[id] * float.Parse(times); } /// /// 设置时间线的值 /// /// 时间线ID /// 次数,时间线的值将被设置为次数乘该时间线的单位值 [ScriptableCall(@" 设置时间线的值 时间线ID 次数,时间线的值将被设置为次数乘该时间线的单位值 ")] public void SetTimePoint(string id, string value) { TimePoints[id] = TimePointDelta[id] * float.Parse(value); } } public partial class ScriptableObject { // 上下文系统 public readonly Dictionary ScriptContextSpace = new(); public Dictionary GetCompleteScriptContext() { if (Parent != null) { Dictionary context = Parent != null ? Parent.GetCompleteScriptContext() : new(); foreach (var (name, value) in ScriptContextSpace) { context[name] = value; } return context; } else { return new(ScriptContextSpace); } } /// /// 设置局部上下文变量,将会传递给子物体使用 /// /// 字符串 /// 浮点数 [ScriptableCall(@" 设置局部上下文变量,将会传递给子物体使用 字符串 浮点数 ")] public void SetContext(string name, string value) { ScriptContextSpace[name] = float.Parse(value); } } public partial class ScriptableObject { // 数值解析工具 private readonly Dictionary DDTCache = new(); public static float OneBarTime = 1; /// /// 从时间点列表中获取 /// 或是从上下文变量中获取 /// 或是从数据驱动对象泛型中获取 /// 或是直接调用 /// /// /// public float Parse(string value) { value = value.Trim(); if (TimePoints.TryGetValue(value, out var result)) return result; if (GetCompleteScriptContext().TryGetValue(value, out result)) return result; if(value.EndsWith(']')) { { Regex regex = new(@"^(.+)\[(\d+)\]$"); var match = regex.Match(value); if (match.Success) { if (FindWithPath(match.Groups[1].Value) is DDT ddt) { DDTCache[match.Groups[1].Value] = ddt; } return DDTCache[match.Groups[1].Value].Datas[int.Parse(match.Groups[2].Value)]; } } throw new ArgumentException("value is end by ']' but not match on any invlid parse"); } if (value.EndsWith('}')) { { Regex regex = new(@"^\{\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\}$"); var match = regex.Match(value); if (match.Success) { int barSplitTimes = int.Parse(match.Groups[1].Value); int barCount = int.Parse(match.Groups[2].Value); int tickCount = int.Parse(match.Groups[3].Value); return (barCount + tickCount / (float)barSplitTimes) * OneBarTime; } } throw new ArgumentException("value is end by '}' but not match on any invlid parse"); } return float.Parse(value); } } public partial class ScriptableObject { // 先天支持的工具函数 /// /// 设置坐标 /// /// /// /// [ScriptableCall(@" 设置坐标 ")] public void SetLocalPosition(string x, string y, string z) { transform.localPosition = new(Parse(x), Parse(y), Parse(z)); } /// /// 设置欧拉角 /// /// /// /// [ScriptableCall(@" 设置欧拉角 ")] public void SetLocalEulerAngles(string x, string y, string z) { transform.localEulerAngles = new(Parse(x), Parse(y), Parse(z)); } /// /// 设置缩放 /// /// /// /// [ScriptableCall(@" 设置缩放 ")] public void SetLocalScaling(string x, string y, string z) { transform.localScale = new(Parse(x), Parse(y), Parse(z)); } /// /// 关闭该物体, /// 在面对如多Game场景时关闭某些GameWorld中默认存在的全局灯光等场景时非常有用 /// [ScriptableCall(@" 关闭该物体, 在面对如多Game场景时关闭某些GameWorld中默认存在的全局灯光等场景时非常有用 ")] public void SetObjectDisable() { gameObject.SetActive(false); } } /// /// 使用标记可编辑脚本所能够调用的函数,并附加注释 /// 使用标记派生类,并附加默认模板 /// public partial class ScriptableObject : MonoBehaviour, IHierarchyItemClickEventListener { public static Dictionary FastScriptableObjectTypen = new(); public static bool IsAutoPlay = false; public string SourcePath = ""; public string ScriptName = ""; public string ScriptPath; public string ScriptTypename; private bool isEnableScript = false; #if UNITY_EDITOR public ProfilerMarker s_PreparePerfMarker; #endif public ScriptableObject Parent; public RootObject GetRoot() { if (Parent == null) return this as RootObject; if (Parent is RootObject result) return result; else return Parent.GetRoot(); } public List Childs = new(); // Hierarchy public PropertiesWindow.ItemEntry MyHierarchyItem; // Cache public static Dictionary> MethodInvokerCache = new(); public void EnableScript(string sourcePath, string scriptPath, string scriptType, ScriptableObject parent) { #if UNITY_EDITOR s_PreparePerfMarker = new(scriptPath); #endif if (isEnableScript) { Debug.LogError($"ScriptableObject is currently enableScript, start coroutine {nameof(UnloadScript)} to disable", this); return; } this.SourcePath = sourcePath; this.ScriptPath = scriptPath; this.ScriptTypename = scriptType; 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 + $"<{scriptType}>"; MyHierarchyItem.GetHierarchyItem().target = this; MyHierarchyItem.GetHierarchyItem().ButtonGameObject.GetComponent().ScriptObjectMenu = OnHierarchyItemRightClick; } 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 (Parent == null) { throw new InvalidOperationException($"Root is nosupport to {nameof(FindWithPath)}"); } 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 == ".") 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; } /// /// 加载子脚本 /// /// 指定类型 /// 指定脚本,可用决定路径或与当前脚本目录的相对路径 [ScriptableCall(@" 加载子脚本 指定类型 指定脚本,可用决定路径或与当前脚本目录的相对路径 ")] [return:ReturnMayNull] public ScriptableObject LoadSubScript(string type, 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(); // 路径预处理 if (path.Replace('\\', '/').ToLower().StartsWith("project/")) path = $"{new ToolFile(GetRoot().SourcePath) | path[5..]}"; // 获取文件 ToolFile file; if (File.Exists(path)) file = new(path); else file = new ToolFile(SourcePath)| path; // 找不到脚本 if (file.Exists() == false) { Debug.LogError($"{file}<{path}> is not found", this); return null; } child.ScriptName = file.GetName(true); child.transform.SetParent(this.transform); child.transform.SetLocalPositionAndRotation(Vector3.zero, Quaternion.identity); child.transform.localScale = Vector3.one; child.EnableScript(file.GetCurrentDirName(), Path.Combine(file.GetCurrentDirName(), file.GetName(false)), type, this); // Add Child Childs.Add(child); // Load Child Script if (gameObject.activeInHierarchy) StartCoroutine(child.LoadScript(file.LoadAsText())); else GetRoot().GetComponent().StartCoroutine(child.LoadScript(file.LoadAsText())); return child; } public object DynamicBindingTarget { get; protected set; } = null; private enum ParseStats { None, Continue, Break } private IEnumerator ParseScript2Expr(string script) { // 预处理 var lines = script.Split('\n'); string preprocessing = ""; foreach (var line in lines) { var expr = line.Trim(); //注释语句(py风格与c风格) if (expr.StartsWith('#') || expr.StartsWith("//")) continue; preprocessing += expr; } var exprs = preprocessing.Split(';'); ParseStats ParseCommandAndParamaterWords(int commandIterator, ref string command, ref string[] words) { var expr = exprs[commandIterator].Trim(); //空语句 if (string.IsNullOrEmpty(expr)) return ParseStats.Continue; //作用域标识符 if (expr.StartsWith('{') || expr.EndsWith('}')) //忽略导入语句(py风格) if (expr.StartsWith("import ") || expr.StartsWith("from ")) return ParseStats.Continue; //进入主函数后中断读取 if (expr.StartsWith("if __name__") || expr.StartsWith("int main") || expr.StartsWith("void main")) return ParseStats.Break; if (MethodInvokerCache.ContainsKey(this.GetType()) == false) { MethodInvokerCache.Add(this.GetType(), new()); } //函数解析 (command, words) = ScriptCallUtility.ParseFunctionCall(expr); if (string.IsNullOrEmpty(command)) return ParseStats.Continue; for (int i = 0, e = words.Length; i < e; i++) { //去除 words[i] = words[i].Trim('\"'); } return ParseStats.None; } // Runtime Structures Dictionary commandLabels = new(); for (int commandIterator = 0; commandIterator < exprs.Length; commandIterator++) { string command = null; string[] words = null; var stats = ParseCommandAndParamaterWords(commandIterator, ref command, ref words); if (stats == ParseStats.Continue) continue; else if (stats == ParseStats.Break) break; var expr = exprs[commandIterator].Trim(); if (command=="label") { if(words.Length<1) { Debug.LogError($"in line \"{expr}\", label miss argument", this); } commandLabels[words[0]] = commandIterator; } } // Main Loop for (int commandIterator = 0; commandIterator < exprs.Length; commandIterator++) { string command = null; string[] words = null; var stats = ParseCommandAndParamaterWords(commandIterator, ref command, ref words); if (stats == ParseStats.Continue) continue; else if (stats == ParseStats.Break) break; // Logic if (command == "label") { continue; } var expr = exprs[commandIterator].Trim(); if (command == "goto") { if (words.Length < 3) { Debug.LogError($"in line \"{expr}\", goto need 3 arguments, but currently less", this); yield break; } float a = Parse(words[0]), b = Parse(words[1]); if (a > b) { commandIterator = commandLabels[words[2]]; continue; } } var paramsList = (from word in words where string.IsNullOrEmpty(word.Trim()) == false select (object)word.Trim()).ToArray(); if (MethodInvokerCache[this.GetType()].TryGetValue(command, out MemberInfo commandInfo) == false) { var commandInfo_try = ConventionUtility.SeekMemberInfo(this, new Type[] { typeof(ScriptableCallAttribute) }, null); commandInfo_try.RemoveAll(x => x.Name != command); if (commandInfo_try.Count <= 0) { Debug.LogError($"in line {expr}, {command} is unable to invoke, because the function is not found", this); continue; } commandInfo = commandInfo_try[0]; MethodInvokerCache[this.GetType()].Add(command, commandInfo); } Debug.Log($"in line \"{expr}\" of \"{ScriptPath}\", {command} is try to invoke", this); try { if (ConventionUtility.TryInvokeMember(commandInfo, this, out var _, paramsList) == false) { MethodInvokerCache[this.GetType()].Remove(command); var attr = commandInfo.GetCustomAttribute(); if (attr == null) Debug.LogError($"in line \"{expr}\" of \"{ScriptPath}\", {command} is unable to invoke", this); else Debug.LogError($"in line \"{expr}\" of \"{ScriptPath}\", {command} is failed to invoke, see: {attr.Description}", this); } else { Debug.Log($"in line \"{expr}\" of \"{ScriptPath}\", {command} is invoke succeed", this); } } catch (Exception ex) { Debug.LogError($"in line \"{expr}\" of \"{ScriptPath}\", {command}({string.Join(',', words)}) is failed to invoke , see: {ex.Message}", this); Debug.LogException(ex, this); yield break; } yield return null; } } public bool EnsureEnableScript() { if (isEnableScript == false) { Debug.LogError("ScriptableObject is currently disableScript", this); } return isEnableScript; } [Content] private bool IsEnableUpdate = false; public virtual IEnumerator LoadScript(string script) { if (EnsureEnableScript() == false) { yield break; } yield return ParseScript2Expr(script); IsEnableUpdate = true; } private void Reset() { StartCoroutine(UnloadScript()); } public virtual IEnumerator UnloadScript() { if (EnsureEnableScript()) { this.name = ""; try { foreach (var child in Childs) { yield return child.UnloadScript(); } } finally { // 清理各种状态 IsEnableUpdate = false; if (MyHierarchyItem != null) { // 卸载UI MyHierarchyItem.Release(); MyHierarchyItem = null; } this.isEnableScript = false; this.Parent = null; DynamicBindingTarget = null; this.name = ""; // 清理Cache DDTCache.Clear(); } } } public enum TickType { Reset, Pause, Start, Update//, //LateUpdate } public void ScriptUpdate(float currentTime, float deltaTime, TickType tickType) { if (IsEnableUpdate == false) return; if (gameObject.activeInHierarchy == false) return; #if UNITY_EDITOR s_PreparePerfMarker.Begin(this); #endif UpdateTicks(currentTime, deltaTime, tickType); foreach (var child in Childs) { child.ScriptUpdate(currentTime, deltaTime, tickType); } #if UNITY_EDITOR s_PreparePerfMarker.End(); #endif } protected virtual void UpdateTicks(float currentTime, float deltaTime, TickType tickType) { } protected T ConvertValue(string str) { return ConventionUtility.convert_xvalue(str); } 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 LoadAssetBundle(this IAssetBundleLoader self, string ab, Action callback) { Debug.Log($"{self.SharedInterfaceScriptObject.ScriptName}.{nameof(LoadAssetBundle)}({ab})", self.SharedInterfaceScriptObject); if (AssetBundleCounter.TryGetValue(ab, out var result)) { result.referenceCounter++; callback(result.assetBundle); yield break; } if (AssetBundleLoading.TryGetValue(ab, out var tir)) { yield return tir; if (AssetBundleCounter.TryGetValue(ab, out result)) { result.referenceCounter++; callback(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(x); }); AssetBundleLoading.Add(ab, ir); yield return ir; AssetBundleLoading.Remove(ab); } 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 Color(1f, 47f / 51f, 0.0156862754f, 0.1f); public override IEnumerator LoadScript(string script) { MyTimelineEntry = TimelineWindow.CreateRootItemEntries(1)[0]; MyTimelineItem = MyTimelineEntry.ref_value.GetComponent(); yield return base.LoadScript(script); MyTimelineItem.title = ScriptName; 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); //IsTimelineItemShow = true; } } public override void OnHierarchyItemClick(HierarchyItem item) { //foreach (var tso in TimelineScriptObjectWhichOnShow) //{ // tso.IsTimelineItemShow = false; // tso.MyTimelineItem.RawButton.gameObject.SetActive(false); //} //TimelineScriptObjectWhichOnShow.Clear(); //ShowTimelineItemWithChilds(); } //protected virtual void ShowTimelineItemWithChilds() //{ // ScriptableObject parent = this; // List childs = this.Childs; // if (parent is TimelineScriptObject ptso) // { // ptso.IsTimelineItemShow = true; // } // foreach (var child in childs) // { // if (child is TimelineScriptObject tso) // { // tso.IsTimelineItemShow = true; // } // } //} protected override void UpdateTicks(float currentTime, float deltaTime, TickType tickType) { base.UpdateTicks(currentTime, deltaTime, tickType); //if (IsTimelineItemShow) //{ MyTimelineItem.ResizeOnTimeline(); //} } 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); } public override IEnumerator UnloadScript() { yield return base.UnloadScript(); MyTimelineItem.RawButton.onClick.RemoveAllListeners(); MyTimelineEntry.Release(); MyTimelineEntry = null; MyTimelineItem = null; } } }