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

1162 lines
42 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

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

using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.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)
{
// 匹配函数名和参数部分
Match match = Regex.Match(input, @"^(\w+)\s*\(\s*(.*?)\s*\)\s*;?$");
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];
var arguments = new List<string>();
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<DefaultScriptAttribute>();
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<ScriptableCallAttribute>(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
{
/// <summary>
/// 用于开放给倒置接口
/// </summary>
public ScriptableObject SharedInterfaceScriptObject => this;
}
public partial class ScriptableObject
{
// 时间点系统
public static Dictionary<string, float> TimePointDelta = new();
public static Dictionary<string, float> TimePoints = new();
/// <summary>
/// 重设指定时间线
/// </summary>
/// <param name="id">时间线ID若不存在则创建</param>
/// <param name="delta">当每次调用NextTimePoint函数时使用的单位值</param>
/// <param name="value">初始化时间</param>
[ScriptableCall(@"
<summary>
重设指定时间线
</summary>
<param name=""id"">时间线ID若不存在则创建</param>
<param name=""delta"">当每次调用NextTimePoint函数时使用的单位值</param>
<param name=""value"">初始化时间</param>
")]
public void ResetTimePoint(string id, string delta, string value)
{
TimePointDelta[id] = float.Parse(delta);
TimePoints[id] = float.Parse(value);
}
/// <summary>
/// 推动时间线前进
/// </summary>
/// <param name="id">时间线ID</param>
/// <param name="times">前进次数,最终时间的增量为前进次数乘该时间线的单位值</param>
[ScriptableCall(@"
<summary>
推动时间线前进
</summary>
<param name=""id"">时间线ID</param>
<param name=""times"">前进次数,最终时间的增量为前进次数乘该时间线的单位值</param>
")]
public void NextTimePoint(string id, string times)
{
TimePoints[id] += TimePointDelta[id] * float.Parse(times);
}
/// <summary>
/// 设置时间线的值
/// </summary>
/// <param name="id">时间线ID</param>
/// <param name="value">次数,时间线的值将被设置为次数乘该时间线的单位值</param>
[ScriptableCall(@"
<summary>
设置时间线的值
</summary>
<param name=""id"">时间线ID</param>
<param name=""value"">次数,时间线的值将被设置为次数乘该时间线的单位值</param>
")]
public void SetTimePoint(string id, string value)
{
TimePoints[id] = TimePointDelta[id] * float.Parse(value);
}
}
public partial class ScriptableObject
{
// 上下文系统
public readonly Dictionary<string, float> ScriptContextSpace = new();
public Dictionary<string, float> GetCompleteScriptContext()
{
if (Parent != null)
{
Dictionary<string, float> context = Parent != null ? Parent.GetCompleteScriptContext() : new();
foreach (var (name, value) in ScriptContextSpace)
{
context[name] = value;
}
return context;
}
else
{
return new(ScriptContextSpace);
}
}
/// <summary>
/// 设置局部上下文变量,将会传递给子物体使用
/// </summary>
/// <param name="name">字符串</param>
/// <param name="value">浮点数</param>
[ScriptableCall(@"
<summary>
设置局部上下文变量,将会传递给子物体使用
</summary>
<param name=""name"">字符串</param>
<param name=""value"">浮点数</param>
")]
public void SetContext(string name, string value)
{
ScriptContextSpace[name] = float.Parse(value);
}
}
public partial class ScriptableObject
{
// 数值解析工具
private readonly Dictionary<string, DDT> DDTCache = new();
public static float OneBarTime = 1;
/// <summary>
/// <list type="bullet">从时间点列表<see cref="TimePoints"/>中获取</list>
/// <list type="bullet">或是从上下文变量<see cref="GetCompleteScriptContext"/>中获取</list>
/// <list type="bullet">或是从数据驱动对象<see cref="DDT{float}"/>的<see cref="float"/>泛型中获取</list>
/// <list type="bullet">或是直接调用<see cref="float.Parse(string)"/></list>
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
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
{
// 先天支持的工具函数
[Content, SerializeField] private Vector3
EnterGameLocalPosition = Vector3.zero,
EnterGameEulerAngles = Vector3.zero,
EnterGameLocalScaling = Vector3.one;
/// <summary>
/// 设置坐标
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
/// <param name="z"></param>
[ScriptableCall(@"
<summary>
设置坐标
</summary>
<param name=""result""></param>
<param name=""y""></param>
<param name=""z""></param>
")]
public void SetLocalPosition(string x, string y, string z)
{
EnterGameLocalPosition = new(Parse(x), Parse(y), Parse(z));
}
/// <summary>
/// 设置欧拉角
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
/// <param name="z"></param>
[ScriptableCall(@"
<summary>
设置欧拉角
</summary>
<param name=""result""></param>
<param name=""y""></param>
<param name=""z""></param>
")]
public void SetLocalEulerAngles(string x, string y, string z)
{
EnterGameEulerAngles = new(Parse(x), Parse(y), Parse(z));
}
/// <summary>
/// 设置缩放
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
/// <param name="z"></param>
[ScriptableCall(@"
<summary>
设置缩放
</summary>
<param name=""result""></param>
<param name=""y""></param>
<param name=""z""></param>
")]
public void SetLocalScaling(string x, string y, string z)
{
EnterGameLocalScaling = new(Parse(x), Parse(y), Parse(z));
}
public virtual void ResetEnterGameStatus()
{
transform.localPosition = EnterGameLocalPosition;
transform.localEulerAngles = EnterGameEulerAngles;
transform.localScale = EnterGameLocalScaling;
}
/// <summary>
/// 关闭该物体,
/// 在面对如多Game场景时关闭某些GameWorld中默认存在的全局灯光等场景时非常有用
/// </summary>
[ScriptableCall(@"
<summary>
关闭该物体,
在面对如多Game场景时关闭某些GameWorld中默认存在的全局灯光等场景时非常有用
</summary>
")]
public void SetObjectDisable()
{
gameObject.SetActive(false);
}
}
public partial class ScriptableObject
{
[Content] public int UpdatePerFrame = 1;
/// <summary>
/// 指定多少个<see cref="TickType.Update"/>状态的<see cref="UpdateTicks(float, float, TickType)"/>执行一次更新,不会影响到子物体
/// 属于性能优化的高级选项
/// </summary>
/// <param name="frame">每frame帧更新一次, 等于0代表不会在<see cref="TickType.Update"/>状态运行</param>
[ScriptableCall(@"
<summary>
指定多少个Update状态的UpdateTicks执行一次更新,不会影响到子物体
属于性能优化的高级选项
</summary>
<param name=""frame"">每frame帧更新一次, 等于0代表不会在Update状态运行</param>
")]
public void SetUpdatePerFrame(string frame)
{
UpdatePerFrame = Mathf.Max(1, int.Parse(frame));
}
}
/// <summary>
/// <para>使用<see cref="ScriptableCallAttribute"/>标记可编辑脚本所能够调用的函数,并附加注释</para>
/// <para>使用<see cref="DefaultScriptAttribute"/>标记派生类,并附加默认模板</para>
/// </summary>
public partial class ScriptableObject : MonoBehaviour, IHierarchyItemClickEventListener
{
public static Dictionary<string, Type> 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<ScriptableObject> Childs = new();
// Hierarchy
public PropertiesWindow.ItemEntry MyHierarchyItem;
// Cache
public static Dictionary<Type, Dictionary<string, MemberInfo>> 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<Editor.UI.RightClick>().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 (path[0] == '/' || path[0]=='\\')
{
result = GetRoot();
}
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;
}
/// <summary>
/// 加载子脚本
/// </summary>
/// <param name="type">指定类型</param>
/// <param name="path">指定脚本,可用决定路径或与当前脚本目录的相对路径</param>
[ScriptableCall(@"
<summary>
加载子脚本
</summary>
<param name=""type"">指定类型</param>
<param name=""path"">指定脚本,可用决定路径或与当前脚本目录的相对路径</param>
")]
[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<MonoBehaviour>().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<string, int> 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<ScriptableCallAttribute>();
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 = "<Unloading>";
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 = "<Unload>";
// 清理Cache
DDTCache.Clear();
}
}
}
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
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
s_PreparePerfMarker.End();
#endif
}
protected virtual void UpdateTicks(float currentTime, float deltaTime, TickType tickType)
{
}
protected T ConvertValue<T>(string str)
{
return ConventionUtility.convert_xvalue<T>(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<string, Entry> AssetBundleCounter = new();
private static Dictionary<string, IEnumerator> AssetBundleLoading = new();
private static PropertiesWindow.ItemEntry AssetBundlesItemEntry = null;
public static IEnumerator LoadAssetBundle(this IAssetBundleLoader self, string ab, Action<AssetBundle> 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<HierarchyItem>().title = nameof(AssetBundlesLoadHelper);
}
var abLoadingItem = AssetBundlesItemEntry.ref_value.GetComponent<HierarchyItem>().CreateSubPropertyItem(1)[0];
var loadingHierarchyItem = abLoadingItem.ref_value.GetComponent<HierarchyItem>();
loadingHierarchyItem.title = $"{ab}<Loading>";
var ir = downloader.LoadAsAssetBundle(p =>
{
loadingHierarchyItem.title = $"{ab}<Loading{(int)(p * 100)}%>";
}, x =>
{
if (x != null)
{
loadingHierarchyItem.title = $"{ab}";
var assets = x.GetAllAssetNames();
var subAssetsItems = loadingHierarchyItem.CreateSubPropertyItem(assets.Length);
for (int i = 0, e = assets.Length; i < e; i++)
{
subAssetsItems[i].ref_value.GetComponent<HierarchyItem>().title = Path.GetFileName(assets[i]);
}
AssetBundleCounter[ab] = new(x, 1, abLoadingItem);
}
else
{
loadingHierarchyItem.title = $"{ab}<Failed>";
}
AssetBundleLoading.Remove(ab);
callback(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<TimelineScriptObject> 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<Editor.UI.TimelineItem>();
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<UnityEngine.UI.Image>();
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<ScriptableObject> 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;
}
}
}