1017 lines
36 KiB
C#
1017 lines
36 KiB
C#
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<Match>()
|
||
.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<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.ScriptEditor;
|
||
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();
|
||
|
||
/// <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)
|
||
{
|
||
return ddt.Datas[int.Parse(match.Groups[2].Value)];
|
||
}
|
||
}
|
||
}
|
||
return float.Parse(value);
|
||
}
|
||
}
|
||
|
||
public partial class ScriptableObject
|
||
{
|
||
// 先天支持的工具函数
|
||
|
||
/// <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)
|
||
{
|
||
transform.localPosition = 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)
|
||
{
|
||
transform.localEulerAngles = 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)
|
||
{
|
||
transform.localScale = new(Parse(x), Parse(y), Parse(z));
|
||
}
|
||
|
||
/// <summary>
|
||
/// 关闭该物体,
|
||
/// 在面对如多Game场景时关闭某些GameWorld中默认存在的全局灯光等场景时非常有用
|
||
/// </summary>
|
||
[ScriptableCall(@"
|
||
<summary>
|
||
关闭该物体,
|
||
在面对如多Game场景时关闭某些GameWorld中默认存在的全局灯光等场景时非常有用
|
||
</summary>
|
||
")]
|
||
public void SetObjectDisable()
|
||
{
|
||
gameObject.SetActive(false);
|
||
}
|
||
}
|
||
|
||
/// <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 (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;
|
||
}
|
||
|
||
/// <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
|
||
}
|
||
|
||
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<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;
|
||
var downloader = new Interaction(file.GetFullPath());
|
||
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;
|
||
}
|
||
}
|
||
}
|