@@ -1,3 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using Ichni.Editor;
|
||||
using UnityEngine;
|
||||
|
||||
@@ -16,13 +19,49 @@ namespace Ichni.RhythmGame
|
||||
public override void SetUpInspector()
|
||||
{
|
||||
base.SetUpInspector();
|
||||
IHaveInspection insp = EditorManager.instance.uiManager.inspector;
|
||||
Inspector inspector = EditorManager.instance.uiManager.inspector;
|
||||
IHaveInspection insp = inspector;
|
||||
|
||||
// ── 组件下拉菜单 ──
|
||||
List<string> componentOptions = new List<string>();
|
||||
if (animatedObject != null)
|
||||
{
|
||||
var components = animatedObject.gameObject.GetComponents<Component>();
|
||||
foreach (var c in components)
|
||||
{
|
||||
string fullName = c.GetType().FullName;
|
||||
if (!componentOptions.Contains(fullName))
|
||||
componentOptions.Add(fullName);
|
||||
}
|
||||
componentOptions.Sort();
|
||||
}
|
||||
|
||||
// ── 属性/字段下拉菜单(基于当前 componentName) ──
|
||||
List<string> propertyOptions = new List<string>();
|
||||
if (!string.IsNullOrEmpty(componentName))
|
||||
{
|
||||
Type compType = ResolveComponentType(componentName);
|
||||
if (compType != null)
|
||||
{
|
||||
foreach (var prop in compType.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance))
|
||||
{
|
||||
if (prop.CanWrite && prop.PropertyType == typeof(Color))
|
||||
propertyOptions.Add(prop.Name);
|
||||
}
|
||||
foreach (var field in compType.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance))
|
||||
{
|
||||
if (field.FieldType == typeof(Color) && !propertyOptions.Contains(field.Name))
|
||||
propertyOptions.Add(field.Name);
|
||||
}
|
||||
propertyOptions.Sort();
|
||||
}
|
||||
}
|
||||
|
||||
InspectorBuilder.For(this)
|
||||
.Section("Property Animation Color")
|
||||
.InputField(nameof(componentName), "Component Name")
|
||||
.OnChanged(() => AfterInitialize())
|
||||
.InputField(nameof(propertyName), "Property Name")
|
||||
.Dropdown(nameof(componentName), componentOptions, "Component")
|
||||
.OnChanged(() => { AfterInitialize(); inspector.SetInspector(this); })
|
||||
.Dropdown(nameof(propertyName), propertyOptions, "Property")
|
||||
.OnChanged(() => AfterInitialize())
|
||||
.Button("GraphicEditor", () => insp.GenerateGraphicalFlexibleFloatWindow(this, "Property Color",
|
||||
new FlexibleFloat[] { propertyValueR, propertyValueG, propertyValueB, propertyValueA },
|
||||
@@ -33,5 +72,15 @@ namespace Ichni.RhythmGame
|
||||
.Button("Color A", () => insp.GenerateCompositeParameterWindow(this, "Color A", nameof(propertyValueA)).SetAsFlexibleFloat())
|
||||
.Build();
|
||||
}
|
||||
|
||||
private static Type ResolveComponentType(string typeName)
|
||||
{
|
||||
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
|
||||
{
|
||||
Type type = assembly.GetType(typeName);
|
||||
if (type != null) return type;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using Ichni.Editor;
|
||||
using UnityEngine;
|
||||
|
||||
@@ -15,18 +18,64 @@ namespace Ichni.RhythmGame
|
||||
public override void SetUpInspector()
|
||||
{
|
||||
base.SetUpInspector();
|
||||
IHaveInspection insp = EditorManager.instance.uiManager.inspector;
|
||||
Inspector inspector = EditorManager.instance.uiManager.inspector;
|
||||
IHaveInspection insp = inspector;
|
||||
|
||||
// ── 组件下拉菜单 ──
|
||||
List<string> componentOptions = new List<string>();
|
||||
if (animatedObject != null)
|
||||
{
|
||||
var components = animatedObject.gameObject.GetComponents<Component>();
|
||||
foreach (var c in components)
|
||||
{
|
||||
string fullName = c.GetType().FullName;
|
||||
if (!componentOptions.Contains(fullName))
|
||||
componentOptions.Add(fullName);
|
||||
}
|
||||
componentOptions.Sort();
|
||||
}
|
||||
|
||||
// ── 属性/字段下拉菜单(基于当前 componentName) ──
|
||||
List<string> propertyOptions = new List<string>();
|
||||
if (!string.IsNullOrEmpty(componentName))
|
||||
{
|
||||
Type compType = ResolveComponentType(componentName);
|
||||
if (compType != null)
|
||||
{
|
||||
foreach (var prop in compType.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance))
|
||||
{
|
||||
if (prop.CanWrite && prop.PropertyType == typeof(float))
|
||||
propertyOptions.Add(prop.Name);
|
||||
}
|
||||
foreach (var field in compType.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance))
|
||||
{
|
||||
if (field.FieldType == typeof(float) && !propertyOptions.Contains(field.Name))
|
||||
propertyOptions.Add(field.Name);
|
||||
}
|
||||
propertyOptions.Sort();
|
||||
}
|
||||
}
|
||||
|
||||
InspectorBuilder.For(this)
|
||||
.Section("Property Animation Float")
|
||||
.InputField(nameof(componentName), "Component Name")
|
||||
.OnChanged(() => AfterInitialize())
|
||||
.InputField(nameof(propertyName), "Property Name")
|
||||
.Dropdown(nameof(componentName), componentOptions, "Component")
|
||||
.OnChanged(() => { AfterInitialize(); inspector.SetInspector(this); })
|
||||
.Dropdown(nameof(propertyName), propertyOptions, "Property")
|
||||
.OnChanged(() => AfterInitialize())
|
||||
.Button("GraphicEditor", () => insp.GenerateGraphicalFlexibleFloatWindow(this, "Property Float",
|
||||
new FlexibleFloat[] { propertyValue }, new string[] { "Value" }))
|
||||
.Button("Float Value", () => insp.GenerateCompositeParameterWindow(this, "Float Value", nameof(propertyValue)).SetAsFlexibleFloat())
|
||||
.Build();
|
||||
}
|
||||
|
||||
private static Type ResolveComponentType(string typeName)
|
||||
{
|
||||
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
|
||||
{
|
||||
Type type = assembly.GetType(typeName);
|
||||
if (type != null) return type;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using Ichni.Editor;
|
||||
using UnityEngine;
|
||||
|
||||
@@ -15,13 +18,49 @@ namespace Ichni.RhythmGame
|
||||
public override void SetUpInspector()
|
||||
{
|
||||
base.SetUpInspector();
|
||||
IHaveInspection insp = EditorManager.instance.uiManager.inspector;
|
||||
Inspector inspector = EditorManager.instance.uiManager.inspector;
|
||||
IHaveInspection insp = inspector;
|
||||
|
||||
// ── 组件下拉菜单 ──
|
||||
List<string> componentOptions = new List<string>();
|
||||
if (animatedObject != null)
|
||||
{
|
||||
var components = animatedObject.gameObject.GetComponents<Component>();
|
||||
foreach (var c in components)
|
||||
{
|
||||
string fullName = c.GetType().FullName;
|
||||
if (!componentOptions.Contains(fullName))
|
||||
componentOptions.Add(fullName);
|
||||
}
|
||||
componentOptions.Sort();
|
||||
}
|
||||
|
||||
// ── 属性/字段下拉菜单(基于当前 componentName) ──
|
||||
List<string> propertyOptions = new List<string>();
|
||||
if (!string.IsNullOrEmpty(componentName))
|
||||
{
|
||||
Type compType = ResolveComponentType(componentName);
|
||||
if (compType != null)
|
||||
{
|
||||
foreach (var prop in compType.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance))
|
||||
{
|
||||
if (prop.CanWrite && prop.PropertyType == typeof(Vector3))
|
||||
propertyOptions.Add(prop.Name);
|
||||
}
|
||||
foreach (var field in compType.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance))
|
||||
{
|
||||
if (field.FieldType == typeof(Vector3) && !propertyOptions.Contains(field.Name))
|
||||
propertyOptions.Add(field.Name);
|
||||
}
|
||||
propertyOptions.Sort();
|
||||
}
|
||||
}
|
||||
|
||||
InspectorBuilder.For(this)
|
||||
.Section("Property Animation Vector3")
|
||||
.InputField(nameof(componentName), "Component Name")
|
||||
.OnChanged(() => AfterInitialize())
|
||||
.InputField(nameof(propertyName), "Property Name")
|
||||
.Dropdown(nameof(componentName), componentOptions, "Component")
|
||||
.OnChanged(() => { AfterInitialize(); inspector.SetInspector(this); })
|
||||
.Dropdown(nameof(propertyName), propertyOptions, "Property")
|
||||
.OnChanged(() => AfterInitialize())
|
||||
.Button("GraphicEditor", () => insp.GenerateGraphicalFlexibleFloatWindow(this, "Property Vector3",
|
||||
new FlexibleFloat[] { propertyValueX, propertyValueY, propertyValueZ }, new string[] { "X", "Y", "Z" }))
|
||||
@@ -30,5 +69,15 @@ namespace Ichni.RhythmGame
|
||||
.Button("Vector Z", () => insp.GenerateCompositeParameterWindow(this, "Vector Z", nameof(propertyValueZ)).SetAsFlexibleFloat())
|
||||
.Build();
|
||||
}
|
||||
|
||||
private static Type ResolveComponentType(string typeName)
|
||||
{
|
||||
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
|
||||
{
|
||||
Type type = assembly.GetType(typeName);
|
||||
if (type != null) return type;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,19 +17,19 @@ namespace Ichni.RhythmGame
|
||||
private Component targetComponent;
|
||||
private FieldInfo targetField;
|
||||
private PropertyInfo targetProperty;
|
||||
|
||||
|
||||
// 高性能赋值委托
|
||||
private Action<Color> colorSetterDelegate;
|
||||
#endregion
|
||||
|
||||
#region [生命周期与工厂] Lifecycle & Factory
|
||||
public static PropertyAnimationColor GenerateElement(string elementName, Guid id, List<string> tags, bool isFirstGenerated,
|
||||
GameElement animatedObject, string componentName, string propertyName,
|
||||
GameElement animatedObject, string componentName, string propertyName,
|
||||
FlexibleFloat propertyValueR, FlexibleFloat propertyValueG, FlexibleFloat propertyValueB, FlexibleFloat propertyValueA)
|
||||
{
|
||||
PropertyAnimationColor animation = Instantiate(EditorManager.instance.basePrefabs.emptyObject)
|
||||
.AddComponent<PropertyAnimationColor>();
|
||||
|
||||
|
||||
animation.Initialize(elementName, id, tags, isFirstGenerated, animatedObject);
|
||||
|
||||
animation.animatedObject = animatedObject;
|
||||
@@ -64,10 +64,10 @@ namespace Ichni.RhythmGame
|
||||
{
|
||||
Debug.LogWarning($"[PropertyAnimationColor] Cannot find Component '{componentName}' strictly on '{animatedObject.name}'.");
|
||||
}
|
||||
|
||||
|
||||
base.AfterInitialize();
|
||||
}
|
||||
|
||||
|
||||
private Type GetTypeFromAllAssemblies(string typeName)
|
||||
{
|
||||
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
|
||||
@@ -125,7 +125,7 @@ namespace Ichni.RhythmGame
|
||||
|
||||
if (colorSetterDelegate != null)
|
||||
{
|
||||
colorSetterDelegate(targetColor);
|
||||
colorSetterDelegate(targetColor);
|
||||
}
|
||||
else if (targetProperty != null)
|
||||
{
|
||||
@@ -135,20 +135,24 @@ namespace Ichni.RhythmGame
|
||||
{
|
||||
targetField.SetValue(targetComponent, targetColor);
|
||||
}
|
||||
|
||||
|
||||
if (animatedObject is IHaveDirtyMarkSubmodule dirtyTarget)
|
||||
{
|
||||
dirtyTarget.dirtyMarkSubmodule.MarkDirty(propertyName);
|
||||
}
|
||||
else
|
||||
{
|
||||
animatedObject.Refresh();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public override void ApplyTimeOffset(float offset)
|
||||
{
|
||||
base.ApplyTimeOffset(offset);
|
||||
void ApplyOffset(FlexibleFloat ff)
|
||||
{
|
||||
if (ff == null || ff.animations == null) return;
|
||||
foreach(var a in ff.animations) { a.startTime += offset; a.endTime += offset; }
|
||||
foreach (var a in ff.animations) { a.startTime += offset; a.endTime += offset; }
|
||||
}
|
||||
ApplyOffset(propertyValueR);
|
||||
ApplyOffset(propertyValueG);
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace Ichni.RhythmGame
|
||||
private Component targetComponent;
|
||||
private FieldInfo targetField;
|
||||
private PropertyInfo targetProperty;
|
||||
|
||||
|
||||
// 我们尝试通过原生的 Action 来降低反射 SetValue 带来的性能损耗(装箱与GC)
|
||||
private Action<float> floatSetterDelegate;
|
||||
#endregion
|
||||
@@ -28,7 +28,7 @@ namespace Ichni.RhythmGame
|
||||
{
|
||||
PropertyAnimationFloat animation = Instantiate(EditorManager.instance.basePrefabs.emptyObject)
|
||||
.AddComponent<PropertyAnimationFloat>();
|
||||
|
||||
|
||||
animation.Initialize(elementName, id, tags, isFirstGenerated, animatedObject);
|
||||
|
||||
animation.animatedObject = animatedObject;
|
||||
@@ -60,10 +60,10 @@ namespace Ichni.RhythmGame
|
||||
{
|
||||
Debug.LogWarning($"[PropertyAnimationFloat] Cannot find Component '{componentName}' strictly on '{animatedObject.name}'.");
|
||||
}
|
||||
|
||||
|
||||
base.AfterInitialize();
|
||||
}
|
||||
|
||||
|
||||
private Type GetTypeFromAllAssemblies(string typeName)
|
||||
{
|
||||
// 对于跨程序集的搜索
|
||||
@@ -125,7 +125,7 @@ namespace Ichni.RhythmGame
|
||||
|
||||
if (floatSetterDelegate != null)
|
||||
{
|
||||
floatSetterDelegate(value);
|
||||
floatSetterDelegate(value);
|
||||
}
|
||||
else if (targetProperty != null)
|
||||
{
|
||||
@@ -135,19 +135,23 @@ namespace Ichni.RhythmGame
|
||||
{
|
||||
targetField.SetValue(targetComponent, value);
|
||||
}
|
||||
|
||||
|
||||
if (animatedObject is IHaveDirtyMarkSubmodule dirtyTarget)
|
||||
{
|
||||
dirtyTarget.dirtyMarkSubmodule?.MarkDirty(propertyName);
|
||||
}
|
||||
else
|
||||
{
|
||||
animatedObject.Refresh();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public override void ApplyTimeOffset(float offset)
|
||||
{
|
||||
base.ApplyTimeOffset(offset);
|
||||
if (propertyValue != null && propertyValue.animations != null)
|
||||
{
|
||||
foreach(var a in propertyValue.animations)
|
||||
foreach (var a in propertyValue.animations)
|
||||
{
|
||||
a.startTime += offset;
|
||||
a.endTime += offset;
|
||||
|
||||
@@ -17,19 +17,19 @@ namespace Ichni.RhythmGame
|
||||
private Component targetComponent;
|
||||
private FieldInfo targetField;
|
||||
private PropertyInfo targetProperty;
|
||||
|
||||
|
||||
// 高性能赋值委托
|
||||
private Action<Vector3> vectorSetterDelegate;
|
||||
#endregion
|
||||
|
||||
#region [生命周期与工厂] Lifecycle & Factory
|
||||
public static PropertyAnimationVector3 GenerateElement(string elementName, Guid id, List<string> tags, bool isFirstGenerated,
|
||||
GameElement animatedObject, string componentName, string propertyName,
|
||||
GameElement animatedObject, string componentName, string propertyName,
|
||||
FlexibleFloat propertyValueX, FlexibleFloat propertyValueY, FlexibleFloat propertyValueZ)
|
||||
{
|
||||
PropertyAnimationVector3 animation = Instantiate(EditorManager.instance.basePrefabs.emptyObject)
|
||||
.AddComponent<PropertyAnimationVector3>();
|
||||
|
||||
|
||||
animation.Initialize(elementName, id, tags, isFirstGenerated, animatedObject);
|
||||
|
||||
animation.animatedObject = animatedObject;
|
||||
@@ -63,10 +63,10 @@ namespace Ichni.RhythmGame
|
||||
{
|
||||
Debug.LogWarning($"[PropertyAnimationVector3] Cannot find Component '{componentName}' strictly on '{animatedObject.name}'.");
|
||||
}
|
||||
|
||||
|
||||
base.AfterInitialize();
|
||||
}
|
||||
|
||||
|
||||
private Type GetTypeFromAllAssemblies(string typeName)
|
||||
{
|
||||
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
|
||||
@@ -121,7 +121,7 @@ namespace Ichni.RhythmGame
|
||||
|
||||
if (vectorSetterDelegate != null)
|
||||
{
|
||||
vectorSetterDelegate(targetVector);
|
||||
vectorSetterDelegate(targetVector);
|
||||
}
|
||||
else if (targetProperty != null)
|
||||
{
|
||||
@@ -131,20 +131,24 @@ namespace Ichni.RhythmGame
|
||||
{
|
||||
targetField.SetValue(targetComponent, targetVector);
|
||||
}
|
||||
|
||||
|
||||
if (animatedObject is IHaveDirtyMarkSubmodule dirtyTarget)
|
||||
{
|
||||
dirtyTarget.dirtyMarkSubmodule.MarkDirty(propertyName);
|
||||
}
|
||||
else
|
||||
{
|
||||
animatedObject.Refresh();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public override void ApplyTimeOffset(float offset)
|
||||
{
|
||||
base.ApplyTimeOffset(offset);
|
||||
void ApplyOffset(FlexibleFloat ff)
|
||||
{
|
||||
if (ff == null || ff.animations == null) return;
|
||||
foreach(var a in ff.animations) { a.startTime += offset; a.endTime += offset; }
|
||||
foreach (var a in ff.animations) { a.startTime += offset; a.endTime += offset; }
|
||||
}
|
||||
ApplyOffset(propertyValueX);
|
||||
ApplyOffset(propertyValueY);
|
||||
|
||||
@@ -56,6 +56,12 @@ namespace Ichni.RhythmGame
|
||||
public override void AfterInitialize()
|
||||
{
|
||||
base.AfterInitialize();
|
||||
|
||||
// 修复 A1:当 noteVisual 为空时(useNotePrefab=false 或预制体加载失败),
|
||||
// 自动从当前已加载的主题包中查找匹配的 NoteVisual 并生成。
|
||||
// 这确保即使没有预制体系统,Note 依然有视觉表现。
|
||||
EnsureNoteVisual();
|
||||
|
||||
generateEffects = GetEffectListSafe("Generate");
|
||||
generalJudgeEffects = GetEffectListSafe("GeneralJudge");
|
||||
perfectEffects = GetEffectListSafe("Perfect");
|
||||
@@ -67,6 +73,35 @@ namespace Ichni.RhythmGame
|
||||
AddinNoteManager();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 当 noteVisual 为空时,从已加载的主题包中自动查找并生成匹配的默认 NoteVisual。
|
||||
/// 修复 useNotePrefab=false 或预制体加载失败导致 Note 无视觉表现的问题。
|
||||
/// </summary>
|
||||
private void EnsureNoteVisual()
|
||||
{
|
||||
if (noteVisual != null) return;
|
||||
if (!isOnTrack || track == null) return;
|
||||
if (ThemeBundleManager.instance.loadedThemeBundleList.Count == 0) return;
|
||||
|
||||
foreach (var bundle in ThemeBundleManager.instance.loadedThemeBundleList)
|
||||
{
|
||||
foreach (var go in bundle.assetList_GameObject)
|
||||
{
|
||||
NoteVisualBase visual = go.GetComponent<NoteVisualBase>();
|
||||
if (visual == null) continue;
|
||||
|
||||
// Tap/Stay/Flick 使用非 Hold 视觉;Hold 使用 Hold 视觉
|
||||
if (this is Tap or Stay or Flick && visual is NoteVisualBaseHold) continue;
|
||||
if (this is Hold && visual is not NoteVisualBaseHold) continue;
|
||||
|
||||
// 通过工厂方法创建 NoteVisual(isFirstGenerated=true 确保效果注册和 AfterInitialize)
|
||||
SubstantialObject.GenerateElement("NoteVisual", Guid.NewGuid(),
|
||||
new List<string>(), true, bundle.themeBundleName, go.name, this);
|
||||
return; // 使用查找到的第一个匹配视觉
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void SetDefaultSubmodules()
|
||||
{
|
||||
timeDurationSubmodule ??= new TimeDurationSubmodule(this);
|
||||
|
||||
@@ -48,11 +48,21 @@ namespace Ichni.RhythmGame
|
||||
/// <summary>
|
||||
/// NoteVisual 初始化完毕后,通知父 Note 重新缓存效果列表。
|
||||
/// 解决手动生成 NoteVisual 时,父 Note 的 generateEffects 等缓存为 null 导致特效不被驱动的问题。
|
||||
/// 同时在此延迟创建 effectSubmodule(当 BM/预制体数据缺少效果子模块时),
|
||||
/// 避免在 GenerateElement 工厂中预创建导致与后续 Submodule_BM 恢复冲突的重复断言。
|
||||
/// </summary>
|
||||
public override void AfterInitialize()
|
||||
{
|
||||
base.AfterInitialize();
|
||||
|
||||
// 延迟初始化:在 BM 加载管线全部执行完毕后,
|
||||
// 如果 effectSubmodule 仍为 null(缺少 BM 数据),则使用空预设创建,
|
||||
// 确保后续 GetEffectListSafe 返回空列表而非 null。
|
||||
if (effectSubmodule == null)
|
||||
{
|
||||
effectSubmodule = new EffectSubmodule(this, EffectSubmodule.EffectSubmodulePreset.Note);
|
||||
}
|
||||
|
||||
if (note != null)
|
||||
{
|
||||
note.RefreshNoteVisualCaches();
|
||||
|
||||
@@ -140,6 +140,7 @@ namespace Ichni.RhythmGame
|
||||
public override void Refresh()
|
||||
{
|
||||
base.Refresh();
|
||||
SetShape();
|
||||
ParticleSystemRenderer particleSystemRenderer = particle.GetComponent<ParticleSystemRenderer>();
|
||||
particleSystemRenderer.material.SetColor("_BaseColor", colorSubmodule.currentBaseColor);
|
||||
if (colorSubmodule.emissionEnabled)
|
||||
|
||||
Reference in New Issue
Block a user