using System; using System.Collections; using System.Collections.Generic; using System.Data; using System.IO; using System.Linq; using System.Reflection; using System.Text.RegularExpressions; using Convention; using Convention.WindowsUI.Variant; using Demo.Game; using Flee.PublicTypes; using Sirenix.OdinInspector; 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; } } [System.AttributeUsage(AttributeTargets.Enum, Inherited = true, AllowMultiple = false)] public sealed class ScriptableEnumAttribute : Attribute { public string Description; public ScriptableEnumAttribute(string description) { this.Description = description; } } public static class ScriptCallUtility { // 解析函数调用的方法,支持 with 子句 public static (string functionName, string[] arguments, Dictionary withVariables) ParseFunctionCall(string input) { // 匹配函数名和参数部分,以及可选的 with 子句 Match match = Regex.Match(input, @"^(\w+)\s*\(\s*(.*?)\s*\)\s*(?:\s+with\s*\(\s*(.*?)\s*\))?\s*;?$"); if (!match.Success) return (null, new string[0], new Dictionary()); string functionName = match.Groups[1].Value; string argumentsString = match.Groups[2].Value; string withString = match.Groups[3].Value; // 解析参数数组 string[] arguments = ParseArguments(argumentsString); // 解析 with 子句的变量 Dictionary withVariables = new Dictionary(); if (!string.IsNullOrWhiteSpace(withString)) { withVariables = ParseWithClause(withString); } return (functionName, arguments, withVariables); } // 解析 with 子句的方法 private static Dictionary ParseWithClause(string withString) { var variables = new Dictionary(); if (string.IsNullOrWhiteSpace(withString)) return variables; int i = 0; while (i < withString.Length) { // 跳过空白字符 while (i < withString.Length && char.IsWhiteSpace(withString[i])) i++; if (i >= withString.Length) break; // 解析变量名 int nameStart = i; while (i < withString.Length && (char.IsLetterOrDigit(withString[i]) || withString[i] == '_')) i++; if (i == nameStart) break; // 没有找到有效的变量名 string varName = withString.Substring(nameStart, i - nameStart); // 跳过空白字符 while (i < withString.Length && char.IsWhiteSpace(withString[i])) i++; // 检查等号 if (i >= withString.Length || withString[i] != '=') break; // 没有找到等号 i++; // 跳过等号 // 跳过空白字符 while (i < withString.Length && char.IsWhiteSpace(withString[i])) i++; // 解析变量值 string varValue = ExtractArgument(withString, ref i); variables[varName] = varValue.Trim(); // 跳过空白字符 while (i < withString.Length && char.IsWhiteSpace(withString[i])) i++; // 检查逗号 if (i < withString.Length && withString[i] == ',') { i++; // 跳过逗号 } else if (i < withString.Length) { break; // 期望逗号但找到了其他字符 } } return variables; } // 解析参数的方法 private static string[] ParseArguments(string argumentsString) { if (string.IsNullOrWhiteSpace(argumentsString)) return new string[0]; var arguments = new List(); int i = 0; while (i < argumentsString.Length) { // 跳过空白字符 while (i < argumentsString.Length && char.IsWhiteSpace(argumentsString[i])) i++; // 越界检查 if (i >= argumentsString.Length) break; string argument = ExtractArgument(argumentsString, ref i); if (!string.IsNullOrEmpty(argument)) arguments.Add(argument.Trim()); // 跳过空白字符 while (i < argumentsString.Length && char.IsWhiteSpace(argumentsString[i])) i++; // 越界检查 if (i >= argumentsString.Length) break; // 必定是逗号, 否则异常情况 if (argumentsString[i] != ',') throw new InvalidOperationException("Parser is invalid logic"); i++; } return arguments.ToArray(); } // 提取单个参数的方法,支持花括号和字符串字面量 private static string ExtractArgument(string input, ref int index) { int start = index; if (input[index] == '"') { // 处理字符串字面量 index++; // 跳过开始的引号 while (index < input.Length) { if (input[index] == '"' && (index == 0 || input[index - 1] != '\\')) { index++; // 跳过结束的引号 break; } index++; } } else if (input[index] == '{') { // 处理花括号内的内容 int braceCount = 0; while (index < input.Length) { if (input[index] == '{') braceCount++; else if (input[index] == '}') braceCount--; index++; if (braceCount == 0) break; } } else { // 处理普通参数(直到遇到逗号) while (index < input.Length && input[index] != ',') index++; } return input[start..index]; } } [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 { [Content] public Dictionary ScriptContextSpace = new(); public bool GetCompleteScriptContext(string key, out float value) { if (ScriptContextSpace.TryGetValue(key, out value) == false) { if (Parent == null) return false; return Parent.GetCompleteScriptContext(key, out value); } return true; } public void GetCompleteScriptContext(ref Dictionary context) { var current = this; while (current != null) { foreach (var key in current.ScriptContextSpace.Keys) { context[key] = current.ScriptContextSpace[key]; } current = current.Parent; } } /// /// 设置局部上下文变量,将会传递给子物体使用 /// /// 字符串 /// 浮点数 [ScriptableCall(@" 设置局部上下文变量,将会传递给子物体使用 字符串 浮点数 ")] public void SetContext(string name, string value) { ScriptContextSpace[name] = Parse(value); } } /// /// 数值解析工具 /// public partial class ScriptableObject { private ExpressionContext ExpressionParserCreater() { ExpressionContext context = new(); context.Imports.AddType(typeof(Mathf)); Dictionary vars = new(); GetCompleteScriptContext(ref vars); foreach (var item in vars) { context.Variables[item.Key] = item.Value; } return context; } public static float OneBarTime = 1; /// /// 从时间点列表中获取 /// 或是从上下文变量中获取 /// 或是从数据驱动对象泛型中获取 /// 或是直接调用 /// /// /// public float Parse(string value) { value = value.Trim(); if(value.StartsWith("\"")&&value.EndsWith("\"")) { value = value[1..^1]; } if (TimePoints.TryGetValue(value, out var result)) return result; if (GetCompleteScriptContext(value, out result)) return result; if(value.EndsWith(']')) { { Regex regex = new(@"^(.+)\[(.+)\]$"); var match = regex.Match(value); if (match.Success) { return (FindWithPath(match.Groups[1].Value) as DDT).Datas[(int)this.Parse(match.Groups[2].Value)]; } } { Regex regex = new(@"^(.+)\[\]$"); var match = regex.Match(value); if (match.Success) { return (FindWithPath(match.Groups[1].Value) as DDT).Datas.Count; } } 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"); } try { if (float.TryParse(value, out var _result)) return _result; else return ExpressionParserCreater().CompileGeneric(value).Evaluate(); } catch(Exception ex) { throw new FormatException($"{value} is not support any Parser", ex); } } protected T ConvertValue(string str) { return ConventionUtility.convert_xvalue(str); } } /// /// 先天支持的工具函数 /// public partial class ScriptableObject { [Content, SerializeField] private Vector3 EnterGameLocalPosition = Vector3.zero, EnterGameEulerAngles = Vector3.zero, EnterGameLocalScaling = Vector3.one; [Content, SerializeField] private bool IsSetObjectDisable = false; /// /// 设置坐标 /// /// /// /// [ScriptableCall(@" 设置坐标 ")] public void SetLocalPosition(string x, string y, string z) { EnterGameLocalPosition = new(Parse(x), Parse(y), Parse(z)); } /// /// 设置欧拉角 /// /// /// /// [ScriptableCall(@" 设置欧拉角 ")] public void SetLocalEulerAngles(string x, string y, string z) { EnterGameEulerAngles = new(Parse(x), Parse(y), Parse(z)); } /// /// 设置缩放 /// /// /// /// [ScriptableCall(@" 设置缩放 ")] public void SetLocalScaling(string x, string y, string z) { EnterGameLocalScaling = new(Parse(x), Parse(y), Parse(z)); } /// /// 关闭该物体, /// 在面对如多Game场景时关闭某些GameWorld中默认存在的全局灯光等场景时非常有用 /// [ScriptableCall(@" 关闭该物体, 在面对如多Game场景时关闭某些GameWorld中默认存在的全局灯光等场景时非常有用 ")] public void SetObjectDisable() { IsSetObjectDisable = true; } private void ResetScriptableObjectEnterGameStatus() { transform.localPosition = EnterGameLocalPosition; transform.localEulerAngles = EnterGameEulerAngles; transform.localScale = EnterGameLocalScaling; if (IsSetObjectDisable) { gameObject.SetActive(false); } } } /// /// UpdatePerFrame相关 /// public partial class ScriptableObject { [Content] public int UpdatePerFrame = 1; /// /// 指定多少个状态的执行一次更新,不会影响到子物体 /// 属于性能优化的高级选项 /// /// 每frame帧更新一次, 等于0代表不会在状态运行 [ScriptableCall(@" 指定多少个Update状态的UpdateTicks执行一次更新,不会影响到子物体 属于性能优化的高级选项 每frame帧更新一次, 等于0代表不会在Update状态运行 ")] public void SetUpdatePerFrame(string frame) { UpdatePerFrame = Mathf.Max(1, int.Parse(frame)); } } /// /// EnableScript相关 /// public partial class ScriptableObject { private bool isEnableScript = false; public void EnableScript(string sourcePath, string scriptPath, string scriptType, ScriptableObject parent) { #if UNITY_EDITOR||true s_PreparePerfMarker = new(ScriptName); #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; //var parentHierarchyItem = MyHierarchyItem.GetParent(); //if (parentHierarchyItem != null) // parentHierarchyItem.GetPropertyListItem().RefreshChilds(); } public bool EnsureEnableScript() { if (isEnableScript == false) { Debug.LogError("ScriptableObject is currently disableScript", this); } return isEnableScript; } } /// /// 使用标记可编辑脚本所能够调用的函数,并附加注释 /// 使用标记派生类,并附加默认模板 /// public partial class ScriptableObject : SerializedMonoBehaviour, IHierarchyItemClickEventListener { public static Dictionary FastScriptableObjectTypen = new(); public static bool IsAutoPlay = false; public string SourcePath = ""; public string ScriptName = ""; public string ScriptPath; public string ScriptTypename; #if UNITY_EDITOR||true 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; public static PropertiesWindow.ItemEntry AllScriptableObjectCounterHierarchyItem; public static int AllScriptableObjectCounter = 0; // Cache public static Dictionary> MethodInvokerCache = new(); 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 IEnumerator LoadSubScriptAsync([In] string type, [In] string path, [Opt] Action callback) { // 判断类型是否合法 if (DefaultInstantiate.GetScriptableObjectInstantiate().TryGetValue(type, out var creater) == false) { Debug.LogError($"{type} is not exist or {type}'s Instantiater is not valid", this); callback?.Invoke(null); yield break; } // 生成对象 var child = creater(); // 路径预处理 if (path.Replace('\\', '/').ToLower().StartsWith(RootObjectQuickPath)) path = $"{new ToolFile(GetRoot().SourcePath) | path[RootObjectQuickPath.Length..]}"; // 获取文件 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); callback?.Invoke(null); yield break; } 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 yield return child.LoadScript(file.LoadAsText()); callback?.Invoke(child); } /// /// 加载子脚本 /// /// 指定类型 /// 指定脚本,可用决定路径或与当前脚本目录的相对路径 [ScriptableCall(@" 加载子脚本 指定类型 指定脚本,可用决定路径或与当前脚本目录的相对路径 ")] public IEnumerator LoadSubScript([In] string type, [In] string path) { yield return LoadSubScriptAsync(type, path, null); } private enum ParseStats { None, Continue, Break } // TODO : 过多的逻辑都挤在这里, 需要拆分 // TODO : 如何统计整个游戏关卡是否加载完成, 尤其是此处的resultEnumerator与ILoadAssetBundle, 将会同时存在多条异步加载的时间线 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, ref Dictionary withVariables) { 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, withVariables) = 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; Dictionary withVariables = new(); var stats = ParseCommandAndParamaterWords(commandIterator, ref command, ref words,ref withVariables); 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++) { // Run Stats string command = null; string[] words = null; Dictionary withVariables = new(); var stats = ParseCommandAndParamaterWords(commandIterator, ref command, ref words, ref withVariables); 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; } // Functions 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); IEnumerator resultEnumerator = null; // 处理 with 子句:保存当前变量状态并设置新变量 Dictionary originalVariables = new Dictionary(); if (withVariables.Count > 0) { foreach (var kvp in withVariables) { // 保存原始值(如果存在) if (ScriptContextSpace.TryGetValue(kvp.Key, out var originalValue)) { originalVariables[kvp.Key] = originalValue; } // 设置新值 ScriptContextSpace[kvp.Key] = Parse(kvp.Value); } } try { // 调用成功 if (ConventionUtility.TryInvokeMember(commandInfo, this, out var invokeResult, paramsList) == true) { Debug.Log($"in line \"{expr}\" of \"{ScriptPath}\", {command} is invoke succeed", this); // 尤其用于加载子类时 if (invokeResult != null && invokeResult is IEnumerator _resultEnumerator) { resultEnumerator = _resultEnumerator; } } // 调用失败 else { 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); } } 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 resultEnumerator; // 恢复 with 子句中的变量状态 if (withVariables.Count > 0) { foreach (var kvp in withVariables) { if (originalVariables.ContainsKey(kvp.Key)) { // 恢复原始值 ScriptContextSpace[kvp.Key] = originalVariables[kvp.Key]; } else { // 删除不存在的变量 ScriptContextSpace.Remove(kvp.Key); } } } } } [Content] private bool IsEnableUpdate = false; public virtual IEnumerator LoadScript(string script) { if (EnsureEnableScript() == false) { yield break; } // 增数 { AllScriptableObjectCounter++; AllScriptableObjectCounterHierarchyItem.GetHierarchyItem().text = $"ScriptableObjectCount: {AllScriptableObjectCounter}"; } 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; this.name = ""; // 清理Cache // // 减数 AllScriptableObjectCounter--; AllScriptableObjectCounterHierarchyItem.GetHierarchyItem().text = $"ScriptableObjectCount: {AllScriptableObjectCounter}"; } } } public enum TickType { Reset, Pause, Start, Update//, //LateUpdate } [Content, SerializeField] private int ScriptUpdateCounter = 0; public void ScriptUpdate(float currentTime, float deltaTime, TickType tickType) { if (IsEnableUpdate == false) return; if (gameObject.activeInHierarchy == false) return; #if UNITY_EDITOR||true s_PreparePerfMarker.Begin(this); #endif if (tickType == TickType.Reset) { ResetEnterGameStatus(); } // UpdateTicks if (UpdatePerFrame > 0) { if (ScriptUpdateCounter % UpdatePerFrame == 0) UpdateTicks(currentTime, deltaTime, tickType); ScriptUpdateCounter += tickType == TickType.Update ? 1 : 0; } // Childs UpdateTicks foreach (var child in Childs) { child.ScriptUpdate(currentTime, deltaTime, tickType); } #if UNITY_EDITOR||true s_PreparePerfMarker.End(); #endif } protected virtual void UpdateTicks(float currentTime, float deltaTime, TickType tickType) { } public virtual void ResetEnterGameStatus() { ResetScriptableObjectEnterGameStatus(); } 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) { Debug.Log($"{self.SharedInterfaceScriptObject.ScriptName}.{nameof(LoadAssetBundle)}({ab})", self.SharedInterfaceScriptObject); 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 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; } } }