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.RScript; using Convention.WindowsUI.Variant; using Demo.Game; using Flee.PublicTypes; using Sirenix.OdinInspector; using Unity.Profiling; using UnityEngine; namespace Demo { public static class ScriptUtility { 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函数时使用的单位值 /// 初始化时间 [Convention.RScript.Variable.Attr.Method] public void ResetTimePoint(string id, string delta, string value) { TimePointDelta[id] = float.Parse(delta); TimePoints[id] = float.Parse(value); } /// /// 推动时间线前进 /// /// 时间线ID /// 前进次数,最终时间的增量为前进次数乘该时间线的单位值 [Convention.RScript.Variable.Attr.Method] public void NextTimePoint(string id, string times) { TimePoints[id] += TimePointDelta[id] * float.Parse(times); } /// /// 设置时间线的值 /// /// 时间线ID /// 次数,时间线的值将被设置为次数乘该时间线的单位值 [Convention.RScript.Variable.Attr.Method] 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; } } /// /// 设置局部上下文变量,将会传递给子物体使用 /// /// 字符串 /// 浮点数 [Convention.RScript.Variable.Attr.Method] 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; /// /// 从字符串解析为浮点数 /// 从时间点列表中获取 /// 或是从上下文变量中获取 /// 或是从数据驱动对象中获取 /// 或是通过计算表达式值获取 /// 或是直接调用 /// /// /// [Convention.RScript.Variable.Attr.Method] 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; /// /// 设置坐标 /// /// /// /// [Convention.RScript.Variable.Attr.Method] public void SetLocalPosition(string x, string y, string z) { EnterGameLocalPosition = new(Parse(x), Parse(y), Parse(z)); } /// /// 设置欧拉角 /// /// /// /// [Convention.RScript.Variable.Attr.Method] public void SetLocalEulerAngles(string x, string y, string z) { EnterGameEulerAngles = new(Parse(x), Parse(y), Parse(z)); } /// /// 设置缩放 /// /// /// /// [Convention.RScript.Variable.Attr.Method] public void SetLocalScaling(string x, string y, string z) { EnterGameLocalScaling = new(Parse(x), Parse(y), Parse(z)); } /// /// 关闭该物体, /// 在面对如多Game场景时关闭某些GameWorld中默认存在的全局灯光等场景时非常有用 /// [Convention.RScript.Variable.Attr.Method] 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代表不会在状态运行 [Convention.RScript.Variable.Attr.Method] public void SetUpdatePerFrame(string frame) { UpdatePerFrame = Mathf.Max(1, int.Parse(frame)); } } /// /// EnableScript相关 /// public partial class ScriptableObject { private bool isEnableScript = false; public string SourcePath = ""; public string ScriptName = ""; public string ScriptPath; public string ScriptTypename; // Hierarchy public PropertiesWindow.ItemEntry MyHierarchyItem; public static PropertiesWindow.ItemEntry AllScriptableObjectCounterHierarchyItem; public void EnableScript(string sourcePath, string scriptPath, string scriptType, ScriptableObject parent) { #if UNITY_EDITOR||Using_ProfilerMarker 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; } } /// /// 脚本解析与Update执行 /// public partial class ScriptableObject { public static Dictionary FastScriptableObjectTypen = new(); public static int AllScriptableObjectCounter = 0; #if UNITY_EDITOR||Using_ProfilerMarker public ProfilerMarker s_PreparePerfMarker; #endif #region LoadSubScript private static ScriptableObject LastLoadedScriptableObject; public IEnumerator DoLoadSubScriptAsync([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()); LastLoadedScriptableObject = child; callback?.Invoke(child); } public IEnumerator DoGenerateSubScriptAsync([In] string type, string name, [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(); child.ScriptName = name; child.transform.SetParent(this.transform); child.transform.SetLocalPositionAndRotation(Vector3.zero, Quaternion.identity); child.transform.localScale = Vector3.one; child.EnableScript("", "", type, this); // Add Child Childs.Add(child); // Load Child Script yield return child.LoadScript(""); LastLoadedScriptableObject = child; callback?.Invoke(child); } public ScriptableObject DoGenerateSubScript([In] string type, string name, [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); 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("", "", type, this); // Add Child Childs.Add(child); // Load Child Script ConventionUtility.StartCoroutine(child.LoadScript("")); LastLoadedScriptableObject = child; callback?.Invoke(child); return child; } /// /// 创建不需要脚本语句的子脚本对象 /// /// 指定类型 public ScriptableObject NewSubScript([In] string type) { return DoGenerateSubScript(type, $"New {type}", null); } /// /// 加载子脚本 /// /// 指定类型 /// 指定脚本,可用决定路径或与当前脚本目录的相对路径 [Convention.RScript.Variable.Attr.Method] public IEnumerator LoadSubScript([In] string type, [In] string path) { return DoLoadSubScriptAsync(type, path, null); } /// /// 获取已加载的脚本对象 /// /// 无法找到时将返回空 [Convention.RScript.Variable.Attr.Method] public ScriptableObject GetLoadedObject(string path) { return FindWithPath(path, false); } /// /// 获取父脚本对象 /// /// [Convention.RScript.Variable.Attr.Method] public ScriptableObject GetParentObject() { return Parent; } /// /// 获取上一个加载的脚本对象 /// /// [Convention.RScript.Variable.Attr.Method] public ScriptableObject GetLastLoadedScriptableObject() { return LastLoadedScriptableObject; } #endregion private IEnumerator ParseScript2Expr(string script) { RScriptEngine engine = new(); RScriptImportClass importClass = new() { typeof(Mathf), }; RScriptVariables variables = new() { { "this", new() { data = this, type = this.GetType() } }, { "self", new() { data = this, type = this.GetType() } } }; foreach (var type in DefaultInstantiate.GetScriptableObjectInstantiate().Keys) { variables.Add(type, new(type.GetType(), type)); } return engine.RunAsync(script, importClass, variables); } [Content] private bool IsEnableUpdate = false; public enum TickType { Reset, Pause, Start, Update } [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||Using_ProfilerMarker 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||Using_ProfilerMarker s_PreparePerfMarker.End(); #endif } protected virtual void UpdateTicks(float currentTime, float deltaTime, TickType tickType) { } } /// /// 使用标记可编辑脚本所能够调用的函数,并附加注释 /// 使用标记派生类,并附加默认模板 /// public partial class ScriptableObject : SerializedMonoBehaviour, IHierarchyItemClickEventListener { public static bool IsAutoPlay = false; public ScriptableObject Parent; /// /// 获取根脚本对象 /// /// [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 List Childs = new(); // Cache 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 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 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); } } public override void OnHierarchyItemClick(HierarchyItem item) { } //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); { 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; } } }