1017 lines
38 KiB
C#
1017 lines
38 KiB
C#
using System;
|
||
using System.Collections;
|
||
using System.Collections.Generic;
|
||
using System.IO;
|
||
using System.Linq;
|
||
using System.Text.RegularExpressions;
|
||
using Convention;
|
||
using Convention.RScript;
|
||
using Convention.WindowsUI.Variant;
|
||
using Demo.Game;
|
||
using Dreamteck.Splines;
|
||
using Unity.VisualScripting;
|
||
using UnityEngine;
|
||
|
||
namespace Demo
|
||
{
|
||
public interface IScriptableObject
|
||
{
|
||
ScriptableObject SharedInterfaceScriptObject { get; }
|
||
}
|
||
|
||
#region ScriptableObject Inside
|
||
|
||
namespace PrivateType
|
||
{
|
||
public class EaseCurveTypeInstance
|
||
{
|
||
public static EaseCurveTypeInstance instance = new();
|
||
public MathExtension.EaseCurveType Linear => MathExtension.EaseCurveType.Linear;
|
||
public MathExtension.EaseCurveType InQuad => MathExtension.EaseCurveType.InQuad;
|
||
public MathExtension.EaseCurveType OutQuad => MathExtension.EaseCurveType.OutQuad;
|
||
public MathExtension.EaseCurveType InOutQuad => MathExtension.EaseCurveType.InOutQuad;
|
||
public MathExtension.EaseCurveType InCubic => MathExtension.EaseCurveType.InCubic;
|
||
public MathExtension.EaseCurveType OutCubic => MathExtension.EaseCurveType.OutCubic;
|
||
public MathExtension.EaseCurveType InOutCubic => MathExtension.EaseCurveType.InOutCubic;
|
||
public MathExtension.EaseCurveType InQuart => MathExtension.EaseCurveType.InQuart;
|
||
public MathExtension.EaseCurveType OutQuart => MathExtension.EaseCurveType.OutQuart;
|
||
public MathExtension.EaseCurveType InOutQuart => MathExtension.EaseCurveType.InOutQuart;
|
||
public MathExtension.EaseCurveType InQuint => MathExtension.EaseCurveType.InQuint;
|
||
public MathExtension.EaseCurveType OutQuint => MathExtension.EaseCurveType.OutQuint;
|
||
public MathExtension.EaseCurveType InOutQuint => MathExtension.EaseCurveType.InOutQuint;
|
||
public MathExtension.EaseCurveType InSine => MathExtension.EaseCurveType.InSine;
|
||
public MathExtension.EaseCurveType OutSine => MathExtension.EaseCurveType.OutSine;
|
||
public MathExtension.EaseCurveType InOutSine => MathExtension.EaseCurveType.InOutSine;
|
||
public MathExtension.EaseCurveType InExpo => MathExtension.EaseCurveType.InExpo;
|
||
public MathExtension.EaseCurveType OutExpo => MathExtension.EaseCurveType.OutExpo;
|
||
public MathExtension.EaseCurveType InOutExpo => MathExtension.EaseCurveType.InOutExpo;
|
||
public MathExtension.EaseCurveType InCirc => MathExtension.EaseCurveType.InCirc;
|
||
public MathExtension.EaseCurveType OutCirc => MathExtension.EaseCurveType.OutCirc;
|
||
public MathExtension.EaseCurveType InOutCirc => MathExtension.EaseCurveType.InOutCirc;
|
||
public MathExtension.EaseCurveType InBounce => MathExtension.EaseCurveType.InBounce;
|
||
public MathExtension.EaseCurveType OutBounce => MathExtension.EaseCurveType.OutBounce;
|
||
public MathExtension.EaseCurveType InOutBounce => MathExtension.EaseCurveType.InOutBounce;
|
||
public MathExtension.EaseCurveType InElastic => MathExtension.EaseCurveType.InElastic;
|
||
public MathExtension.EaseCurveType OutElastic => MathExtension.EaseCurveType.OutElastic;
|
||
public MathExtension.EaseCurveType InOutElastic => MathExtension.EaseCurveType.InOutElastic;
|
||
public MathExtension.EaseCurveType InBack => MathExtension.EaseCurveType.InBack;
|
||
public MathExtension.EaseCurveType OutBack => MathExtension.EaseCurveType.OutBack;
|
||
public MathExtension.EaseCurveType InOutBack => MathExtension.EaseCurveType.InOutBack;
|
||
public MathExtension.EaseCurveType Custom => MathExtension.EaseCurveType.Custom;
|
||
}
|
||
|
||
public class SplineComputerSampleModeInstance
|
||
{
|
||
public static SplineComputerSampleModeInstance instance = new();
|
||
public SplineComputer.SampleMode Default => SplineComputer.SampleMode.Default;
|
||
public SplineComputer.SampleMode Uniform => SplineComputer.SampleMode.Uniform;
|
||
public SplineComputer.SampleMode Optimized => SplineComputer.SampleMode.Optimized;
|
||
}
|
||
|
||
public class SplineTypeInstance
|
||
{
|
||
public static SplineTypeInstance instance = new();
|
||
public Spline.Type Linear => Spline.Type.Linear;
|
||
public Spline.Type BSpline => Spline.Type.BSpline;
|
||
public Spline.Type CatmullRom => Spline.Type.CatmullRom;
|
||
public Spline.Type Bezier => Spline.Type.Bezier;
|
||
}
|
||
|
||
public class IEffectHookObjectInstance
|
||
{
|
||
public static IEffectHookObjectInstance instance = new();
|
||
public IEffectHookObject.InteractiveEffectType VisibleDuration => IEffectHookObject.InteractiveEffectType.VisibleDuration;
|
||
public IEffectHookObject.InteractiveEffectType InteractiveDuration => IEffectHookObject.InteractiveEffectType.InteractiveDuration;
|
||
public IEffectHookObject.InteractiveEffectType InteractableScoreInterval => IEffectHookObject.InteractiveEffectType.InteractableScoreInterval;
|
||
public IEffectHookObject.InteractiveEffectType InteractableIntervalThatCanScoreBest => IEffectHookObject.InteractiveEffectType.InteractableIntervalThatCanScoreBest;
|
||
}
|
||
}
|
||
|
||
public partial class ScriptableObject : IScriptableObject
|
||
{
|
||
/// <summary>
|
||
/// 用于开放给倒置接口
|
||
/// </summary>
|
||
public ScriptableObject SharedInterfaceScriptObject => this;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 初始化与对象设置
|
||
/// </summary>
|
||
public partial class ScriptableObject
|
||
{
|
||
[Content, SerializeField]
|
||
private Vector3
|
||
EnterGameLocalPosition = Vector3.zero,
|
||
EnterGameEulerAngles = Vector3.zero,
|
||
EnterGameLocalScaling = Vector3.one;
|
||
[Content, SerializeField] private bool IsSetObjectDisable = false;
|
||
[Content] public int UpdatePerFrame = 1;
|
||
|
||
[Content, SerializeField, Header("UpdateMode")]
|
||
protected UpdateMode _updateMode = UpdateMode.Permanent;
|
||
|
||
[Content, SerializeField]
|
||
protected float _activeStartTime = 0f;
|
||
|
||
[Content, SerializeField]
|
||
protected float _activeEndTime = float.MaxValue;
|
||
|
||
[Content, SerializeField]
|
||
private bool _useUpdateScheduler = false; // 控制是否使用新的UpdateScheduler
|
||
|
||
/// <summary>
|
||
/// 设置坐标
|
||
/// </summary>
|
||
/// <param name="x"></param>
|
||
/// <param name="y"></param>
|
||
/// <param name="z"></param>
|
||
[Convention.RScript.Variable.Attr.Method]
|
||
public void SetLocalPosition(float x, float y, float z)
|
||
{
|
||
EnterGameLocalPosition = new(x, y, z);
|
||
}
|
||
/// <summary>
|
||
/// 设置欧拉角
|
||
/// </summary>
|
||
/// <param name="x"></param>
|
||
/// <param name="y"></param>
|
||
/// <param name="z"></param>
|
||
[Convention.RScript.Variable.Attr.Method]
|
||
public void SetLocalEulerAngles(float x, float y, float z)
|
||
{
|
||
EnterGameEulerAngles = new(x, y, z);
|
||
}
|
||
/// <summary>
|
||
/// 设置缩放
|
||
/// </summary>
|
||
/// <param name="x"></param>
|
||
/// <param name="y"></param>
|
||
/// <param name="z"></param>
|
||
[Convention.RScript.Variable.Attr.Method]
|
||
public void SetLocalScaling(float x, float y, float z)
|
||
{
|
||
EnterGameLocalScaling = new(x, y, z);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 关闭该物体,
|
||
/// 在面对如多Game场景时关闭某些GameWorld中默认存在的全局灯光等场景时非常有用
|
||
/// </summary>
|
||
[Convention.RScript.Variable.Attr.Method]
|
||
public void SetObjectDisable()
|
||
{
|
||
IsSetObjectDisable = true;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 指定多少个<see cref="TickType.Update"/>状态的<see cref="UpdateTicks(float, float, TickType)"/>执行一次更新,不会影响到子物体
|
||
/// 属于性能优化的高级选项
|
||
/// </summary>
|
||
/// <param name="frame">每frame帧更新一次, 等于0代表不会在<see cref="TickType.Update"/>状态运行</param>
|
||
[Convention.RScript.Variable.Attr.Method]
|
||
public void SetUpdatePerFrame(int frame)
|
||
{
|
||
UpdatePerFrame = Mathf.Max(1, frame);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 启用新的UpdateScheduler(用于逐步迁移)
|
||
/// </summary>
|
||
[Convention.RScript.Variable.Attr.Method]
|
||
public void EnableUpdateScheduler()
|
||
{
|
||
_useUpdateScheduler = true;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 子类可重写,定义自己的Update模式
|
||
/// </summary>
|
||
protected virtual UpdateMode GetUpdateMode() => UpdateMode.Permanent;
|
||
|
||
/// <summary>
|
||
/// 子类可重写,定义自己的活跃时间范围
|
||
/// </summary>
|
||
protected virtual (float start, float end) GetActiveTimeRange()
|
||
=> (0f, float.MaxValue);
|
||
|
||
private void ScriptableObjectDoReset()
|
||
{
|
||
transform.localPosition = EnterGameLocalPosition;
|
||
transform.localEulerAngles = EnterGameEulerAngles;
|
||
transform.localScale = EnterGameLocalScaling;
|
||
gameObject.SetActive(IsSetObjectDisable == false);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 跨脚本上下文变量
|
||
/// </summary>
|
||
public partial class ScriptableObject
|
||
{
|
||
public Dictionary<string, float> ScriptableObjectContents = new();
|
||
|
||
[Convention.RScript.Variable.Attr.Method]
|
||
public float GetContent(string key)
|
||
{
|
||
if (ScriptableObjectContents.TryGetValue(key, out var result))
|
||
{
|
||
return result;
|
||
}
|
||
if (Parent != null)
|
||
{
|
||
return Parent.GetContent(key);
|
||
}
|
||
throw new InvalidOperationException($"Key {key} is not find in contnet");
|
||
}
|
||
|
||
[Convention.RScript.Variable.Attr.Method]
|
||
public float SetContent(string key, float value)
|
||
{
|
||
ScriptableObjectContents[key] = value;
|
||
return value;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 基础信息, 父子关系与EnableScript
|
||
/// </summary>
|
||
public partial class ScriptableObject
|
||
{
|
||
public static bool IsAutoPlay = false;
|
||
public static float OneBarTime = 60;
|
||
|
||
private bool isEnableScript = false;
|
||
public string ScriptName = "";
|
||
|
||
public ScriptableObject Parent;
|
||
public readonly List<ScriptableObject> Childs = new();
|
||
[Content, SerializeField] private List<ScriptableObject> UpdateChilds = new();
|
||
|
||
/// <summary>
|
||
/// 获取根脚本对象
|
||
/// </summary>
|
||
/// <returns></returns>
|
||
[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 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 void EnableScript(ScriptableObject parent)
|
||
{
|
||
if (isEnableScript)
|
||
{
|
||
Debug.LogError($"ScriptableObject is currently enableScript, start coroutine {nameof(UnloadScript)} to disable", this);
|
||
return;
|
||
}
|
||
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 + $"<{this.GetType()}>";
|
||
MyHierarchyItem.GetHierarchyItem().target = this;
|
||
MyHierarchyItem.GetHierarchyItem().ButtonGameObject.GetComponent<Editor.UI.RightClick>().ScriptObjectMenu = OnHierarchyItemRightClick;
|
||
}
|
||
|
||
#region Hierarchy
|
||
|
||
public PropertiesWindow.ItemEntry MyHierarchyItem;
|
||
public static PropertiesWindow.ItemEntry AllScriptableObjectCounterHierarchyItem;
|
||
|
||
public bool EnsureEnableScript()
|
||
{
|
||
if (isEnableScript == false)
|
||
{
|
||
Debug.LogError("ScriptableObject is currently disableScript", this);
|
||
}
|
||
return isEnableScript;
|
||
}
|
||
|
||
#endregion
|
||
}
|
||
|
||
/// <summary>
|
||
/// 脚本解析与Update执行
|
||
/// </summary>
|
||
public partial class ScriptableObject
|
||
{
|
||
public static Dictionary<string, Type> FastScriptableObjectTypen = new();
|
||
public static int AllScriptableObjectCounter = 0;
|
||
|
||
public bool IsParseScript2Expr { get; private set; } = false;
|
||
protected virtual bool IsSelfEnableUpdate { get => true; }
|
||
public bool IsEnableUpdate { get; private set; } = true;
|
||
|
||
#region LoadSubScript
|
||
|
||
/// <summary>
|
||
/// 创建基于子脚本的实例对象
|
||
/// </summary>
|
||
/// <param name="type"></param>
|
||
/// <param name="name"></param>
|
||
/// <returns></returns>
|
||
[Convention.RScript.Variable.Attr.Method]
|
||
public ScriptableObject LoadSubScript([In] string type, [In] string name, [In] 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();
|
||
// 获取文件
|
||
var file = new ToolFile(GetRoot().SourcePath);
|
||
file = file | path;
|
||
// 找不到脚本
|
||
if (file.Exists() == false)
|
||
{
|
||
Debug.LogError($"{file}<{path}> is not found", this);
|
||
return null;
|
||
}
|
||
child.ScriptName = name;
|
||
child.transform.SetParent(this.transform);
|
||
child.transform.SetLocalPositionAndRotation(Vector3.zero, Quaternion.identity);
|
||
child.transform.localScale = Vector3.one;
|
||
child.EnableScript(this);
|
||
|
||
// Add Child
|
||
Childs.Add(child);
|
||
UpdateChilds.Add(child);
|
||
|
||
// Load Child Script
|
||
ConventionUtility.StartCoroutine(child.ParseScript2Expr(file.LoadAsText()));
|
||
|
||
return child;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 创建不基于子脚本的实例对象
|
||
/// </summary>
|
||
/// <param name="type"></param>
|
||
/// <param name="name"></param>
|
||
/// <returns></returns>
|
||
[Convention.RScript.Variable.Attr.Method]
|
||
public ScriptableObject NewSubScript([In] string type, string name)
|
||
{
|
||
// 判断类型是否合法
|
||
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();
|
||
child.ScriptName = name;
|
||
child.transform.SetParent(this.transform);
|
||
child.transform.SetLocalPositionAndRotation(Vector3.zero, Quaternion.identity);
|
||
child.transform.localScale = Vector3.one;
|
||
child.EnableScript(this);
|
||
|
||
// Add Child
|
||
Childs.Add(child);
|
||
UpdateChilds.Add(child);
|
||
|
||
return child;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取已加载的脚本对象
|
||
/// </summary>
|
||
/// <returns>无法找到时将返回空</returns>
|
||
[Convention.RScript.Variable.Attr.Method]
|
||
public ScriptableObject GetLoadedObject(string path)
|
||
{
|
||
return FindWithPath(path, false);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取父脚本对象
|
||
/// </summary>
|
||
/// <returns></returns>
|
||
[Convention.RScript.Variable.Attr.Method]
|
||
public ScriptableObject GetParentObject()
|
||
{
|
||
return Parent;
|
||
}
|
||
|
||
#endregion
|
||
|
||
public static class RandomTool
|
||
{
|
||
public static float Random(float min, float max)
|
||
{
|
||
return UnityEngine.Random.Range(min, max);
|
||
}
|
||
|
||
public static float Random(double min, double max)
|
||
{
|
||
return UnityEngine.Random.Range((float)min, (float)max);
|
||
}
|
||
}
|
||
|
||
public class ConsoleTool
|
||
{
|
||
private GameObject gameObject;
|
||
public ConsoleTool(GameObject gameObject)
|
||
{
|
||
this.gameObject = gameObject;
|
||
}
|
||
|
||
public void Log(object obj)
|
||
{
|
||
Debug.Log(obj, gameObject);
|
||
}
|
||
}
|
||
|
||
public IEnumerator ParseScript2Expr(string script)
|
||
{
|
||
IsParseScript2Expr = true;
|
||
try
|
||
{
|
||
RScriptEngine engine = new();
|
||
RScriptImportClass importClass = new()
|
||
{
|
||
typeof(Mathf),
|
||
typeof(RandomTool),
|
||
};
|
||
RScriptVariables variables = new()
|
||
{
|
||
{ "this", new() { data = this, type = this.GetType() } },
|
||
{ "self", new() { data = this, type = this.GetType() } },
|
||
{ "console", new() { data = new ConsoleTool(gameObject), type = typeof(ConsoleTool) } },
|
||
{ nameof(MathExtension.EaseCurveType), new() { data = PrivateType.EaseCurveTypeInstance.instance, type = typeof(PrivateType.EaseCurveTypeInstance) } },
|
||
{ $"Spline{nameof(SplineComputer.SampleMode)}",
|
||
new() { data = PrivateType.SplineComputerSampleModeInstance.instance, type = typeof(PrivateType.SplineComputerSampleModeInstance)} },
|
||
{ $"Spline{nameof(Spline.Type)}",
|
||
new() { data = PrivateType.SplineTypeInstance.instance, type = typeof(PrivateType.SplineTypeInstance)} },
|
||
{ nameof(IEffectHookObject.InteractiveEffectType),
|
||
new() { data = PrivateType.IEffectHookObjectInstance.instance, type = typeof(PrivateType.IEffectHookObjectInstance)} }
|
||
};
|
||
|
||
foreach (var ir in engine.RunAsync(script, importClass, variables).Yield())
|
||
{
|
||
yield return ir;
|
||
}
|
||
}
|
||
finally
|
||
{
|
||
IsParseScript2Expr = false;
|
||
}
|
||
}
|
||
|
||
public enum TickType
|
||
{
|
||
Reset,
|
||
Pause,
|
||
Start,
|
||
Update
|
||
}
|
||
|
||
[Content, SerializeField] private int ScriptUpdateCounter = 0;
|
||
public void ScriptUpdate(float currentTime, float deltaTime, TickType tickType)
|
||
{
|
||
if (IsScriptApply == false)
|
||
return;
|
||
if (gameObject.activeInHierarchy == false)
|
||
return;
|
||
|
||
using (Profiler.BeginZone($"{m_GetTypeName}.ScriptUpdate"))
|
||
{
|
||
if (tickType == TickType.Reset)
|
||
{
|
||
ResetEnterGameStatus();
|
||
// Childs UpdateTicks
|
||
foreach (var child in Childs)
|
||
{
|
||
child.ScriptUpdate(currentTime, deltaTime, tickType);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
// UpdateTicks
|
||
if (this.IsSelfEnableUpdate && UpdatePerFrame > 0)
|
||
{
|
||
if (ScriptUpdateCounter % UpdatePerFrame == 0)
|
||
using (Profiler.BeginZone($"{this.ScriptName}.UpdateTicks"))
|
||
UpdateTicks(currentTime, deltaTime, tickType);
|
||
ScriptUpdateCounter += tickType == TickType.Update ? 1 : 0;
|
||
}
|
||
// Childs UpdateTicks
|
||
foreach (var child in UpdateChilds)
|
||
{
|
||
child.ScriptUpdate(currentTime, deltaTime, tickType);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
protected virtual void UpdateTicks(float currentTime, float deltaTime, TickType tickType)
|
||
{
|
||
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// <para>使用<see cref="Convention.RScript.Variable.Attr.MethodAttribute"/>标记可编辑脚本所能够调用的函数</para>
|
||
/// <para>使用<see cref="Convention.RScript.Variable.Attr.DefaultAttribute"/>标记派生类,并附加默认模板</para>
|
||
/// </summary>
|
||
public partial class ScriptableObject :
|
||
#if ENABLE_SerializedMonoBehaviour_CLASS
|
||
SerializedMonoBehaviour,
|
||
#else
|
||
MonoBehaviour,
|
||
#endif
|
||
IHierarchyItemClickEventListener
|
||
{
|
||
protected virtual IEnumerator DoSomethingDuringApplyScript()
|
||
{
|
||
return null;
|
||
}
|
||
|
||
[Content]
|
||
public bool IsScriptApply { get; private set; } = false;
|
||
|
||
/// <summary>
|
||
/// 确认脚本已经完全执行完成, 允许其进入工作阶段
|
||
/// <b>注意不要使用this.ApplyScript</b>, 将会导致
|
||
/// <code>
|
||
/// while (this.IsParseScript2Expr)
|
||
/// {
|
||
/// yield return null;
|
||
/// }
|
||
/// </code>
|
||
/// 永远等待
|
||
/// </summary>
|
||
/// <returns></returns>
|
||
[Convention.RScript.Variable.Attr.Method]
|
||
public IEnumerator ApplyScript()
|
||
{
|
||
if (EnsureEnableScript() == false)
|
||
{
|
||
yield break;
|
||
}
|
||
// 等待自身脚本解析完毕
|
||
while (this.IsParseScript2Expr)
|
||
{
|
||
yield return null;
|
||
}
|
||
yield return DoSomethingDuringApplyScript();
|
||
// 增数
|
||
{
|
||
AllScriptableObjectCounter++;
|
||
AllScriptableObjectCounterHierarchyItem.GetHierarchyItem().text = $"ScriptableObjectCount: {AllScriptableObjectCounter}";
|
||
}
|
||
// 统计更新能力
|
||
{
|
||
IsEnableUpdate = this.IsSelfEnableUpdate;
|
||
foreach (var child in this.Childs)
|
||
{
|
||
if (IsEnableUpdate)
|
||
break;
|
||
IsEnableUpdate |= child.IsEnableUpdate;
|
||
}
|
||
if (IsEnableUpdate == false && Parent != null)
|
||
Parent.UpdateChilds.Remove(this);
|
||
// 当只存在一个需要更新的子物体并且自身不需要更新时, 直接简化调用
|
||
if (this.IsSelfEnableUpdate == false && this.UpdateChilds.Count == 1)
|
||
{
|
||
Parent.UpdateChilds.Remove(this);
|
||
var child = this.UpdateChilds[0];
|
||
this.UpdateChilds.Clear();
|
||
Parent.UpdateChilds.Add(child);
|
||
IsEnableUpdate = false;
|
||
}
|
||
}
|
||
|
||
// 可选的调度器注册
|
||
if (_useUpdateScheduler && (IsSelfEnableUpdate || IsEnableUpdate))
|
||
{
|
||
RegisterToScheduler();
|
||
}
|
||
|
||
IsScriptApply = true;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 注册到UpdateScheduler
|
||
/// </summary>
|
||
private void RegisterToScheduler()
|
||
{
|
||
// 获取Update模式
|
||
_updateMode = GetUpdateMode();
|
||
|
||
// 获取时间范围
|
||
if (_updateMode == UpdateMode.TimeBound)
|
||
{
|
||
(_activeStartTime, _activeEndTime) = GetActiveTimeRange();
|
||
}
|
||
|
||
// 注册到调度器
|
||
try
|
||
{
|
||
GetRoot()?.Scheduler?.Register(this, _updateMode, _activeStartTime, _activeEndTime);
|
||
Debug.Log($"[ScriptableObject] 已注册到调度器: {GetUpdateName()}, Mode={_updateMode}");
|
||
}
|
||
catch (System.Exception ex)
|
||
{
|
||
Debug.LogError($"[ScriptableObject] 注册到调度器失败: {ex.Message}", this);
|
||
}
|
||
}
|
||
|
||
public virtual IEnumerator UnloadScript()
|
||
{
|
||
if (EnsureEnableScript())
|
||
{
|
||
this.name = "<Unloading>";
|
||
try
|
||
{
|
||
foreach (var child in Childs)
|
||
{
|
||
yield return child.UnloadScript();
|
||
}
|
||
}
|
||
finally
|
||
{
|
||
this.isEnableScript = false;
|
||
this.Parent = null;
|
||
this.name = "<Unload>";
|
||
if (IsScriptApply)
|
||
{
|
||
// 清理各种状态
|
||
IsScriptApply = false;
|
||
// 清理Cache
|
||
//
|
||
// 减数
|
||
AllScriptableObjectCounter--;
|
||
AllScriptableObjectCounterHierarchyItem.GetHierarchyItem().text = $"ScriptableObjectCount: {AllScriptableObjectCounter}";
|
||
}
|
||
if (MyHierarchyItem != null)
|
||
{
|
||
// 卸载UI
|
||
MyHierarchyItem.Release();
|
||
MyHierarchyItem = null;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
public virtual void ResetEnterGameStatus()
|
||
{
|
||
ScriptableObjectDoReset();
|
||
}
|
||
|
||
public virtual void OnHierarchyItemRightClick(RectTransform item)
|
||
{
|
||
DefaultInstantiate.OpenInstantiateMenu(this, item);
|
||
}
|
||
|
||
public virtual void OnHierarchyItemClick(HierarchyItem item)
|
||
{
|
||
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// IUpdateable接口实现
|
||
/// </summary>
|
||
public partial class ScriptableObject : IUpdateable
|
||
{
|
||
/// <summary>
|
||
/// IUpdateable.FlatOptimizationUpdate实现 - 扁平化优化Update入口,直接更新对象自身(无递归遍历子对象)
|
||
/// </summary>
|
||
public void FlatOptimizationUpdate(float currentTime, float deltaTime, TickType tickType)
|
||
{
|
||
if (IsScriptApply == false)
|
||
return;
|
||
if (gameObject.activeInHierarchy == false)
|
||
return;
|
||
|
||
// 只更新自己,不递归子节点
|
||
if (this.IsSelfEnableUpdate && UpdatePerFrame > 0)
|
||
{
|
||
if (ScriptUpdateCounter % UpdatePerFrame == 0)
|
||
{
|
||
using (Profiler.BeginZone($"{this.ScriptName}.UpdateTicks"))
|
||
{
|
||
UpdateTicks(currentTime, deltaTime, tickType);
|
||
}
|
||
}
|
||
ScriptUpdateCounter += tickType == TickType.Update ? 1 : 0;
|
||
}
|
||
}
|
||
|
||
private string c_GetTypeName;
|
||
private string m_GetTypeName
|
||
{
|
||
get
|
||
{
|
||
if (string.IsNullOrEmpty(c_GetTypeName))
|
||
{
|
||
c_GetTypeName = GetType().Name;
|
||
}
|
||
return c_GetTypeName;
|
||
}
|
||
}
|
||
|
||
|
||
/// <summary>
|
||
/// IUpdateable.GetUpdateName实现
|
||
/// </summary>
|
||
public string GetUpdateName()
|
||
{
|
||
return $"{ScriptName}<{m_GetTypeName}>";
|
||
}
|
||
|
||
/// <summary>
|
||
/// IUpdateable.IsUpdateReady实现
|
||
/// </summary>
|
||
public bool IsUpdateReady => IsScriptApply;
|
||
}
|
||
|
||
#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 LoadAssetBundleAsync(string ab, Action<AssetBundle> 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<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?.Invoke(x);
|
||
});
|
||
AssetBundleLoading.Add(ab, ir);
|
||
yield return ir;
|
||
AssetBundleLoading.Remove(ab);
|
||
}
|
||
|
||
public static IEnumerator LoadAssetBundle(this IAssetBundleLoader self, string ab, Action<AssetBundle> callback)
|
||
{
|
||
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<TimelineScriptObject> TimelineScriptObjectWhichOnShow = new();
|
||
|
||
private static UnityEngine.UI.Image CacheLastFocusImage;
|
||
private static Color CacheLastFocusImageOriginColor = new(1, 1, 1, 0.01f);
|
||
private static Color FocusImageColor = new(1f, 47f / 51f, 0.0156862754f, 0.1f);
|
||
|
||
protected override IEnumerator DoSomethingDuringApplyScript()
|
||
{
|
||
yield return base.DoSomethingDuringApplyScript();
|
||
if (MyTimelineEntry == null)
|
||
{
|
||
MyTimelineEntry = TimelineWindow.CreateRootItemEntries(1)[0];
|
||
MyTimelineItem = MyTimelineEntry.ref_value.GetComponent<Editor.UI.TimelineItem>();
|
||
}
|
||
MyTimelineItem.title = ScriptName;
|
||
MyTimelineItem.RawButton.onClick.RemoveAllListeners();
|
||
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);
|
||
}
|
||
}
|
||
|
||
public override IEnumerator UnloadScript()
|
||
{
|
||
yield return base.UnloadScript();
|
||
// 这里的两处判空是因为如果抛出错误就会打断了逻辑, 所以这里需要判断
|
||
if (MyTimelineItem != null)
|
||
{
|
||
MyTimelineItem.RawButton.onClick.RemoveAllListeners();
|
||
MyTimelineItem = null;
|
||
}
|
||
if (MyTimelineEntry != null)
|
||
{
|
||
MyTimelineEntry.Release();
|
||
MyTimelineEntry = null;
|
||
}
|
||
}
|
||
|
||
public override void OnHierarchyItemClick(HierarchyItem item)
|
||
{
|
||
|
||
}
|
||
|
||
private float UIResizeOnTimelineCount = 0;
|
||
protected override void UpdateTicks(float currentTime, float deltaTime, TickType tickType)
|
||
{
|
||
base.UpdateTicks(currentTime, deltaTime, tickType);
|
||
// 存在严重的性能开销, 在解决之前将不会允许其快速自动更新
|
||
using (Profiler.BeginZone($"{nameof(TimelineScriptObject)}.{nameof(MyTimelineItem.ResizeOnTimeline)}"))
|
||
{
|
||
if (UIResizeOnTimelineCount > 0.1 || tickType != TickType.Update)
|
||
{
|
||
UIResizeOnTimelineCount = 0;
|
||
MyTimelineItem.ResizeOnTimeline();
|
||
}
|
||
UIResizeOnTimelineCount += deltaTime;
|
||
}
|
||
}
|
||
|
||
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);
|
||
}
|
||
}
|
||
}
|