using System; using System.Collections.Generic; using System.Reflection; using Ichni.RhythmGame.Beatmap; using UnityEngine; namespace Ichni.RhythmGame { public class PropertyAnimationFloat : AnimationBase { #region [暴露属性字段与关联] Exposed Fields & References public string componentName; public string propertyName; public FlexibleFloat propertyValue; private Component targetComponent; private FieldInfo targetField; private PropertyInfo targetProperty; // 我们尝试通过原生的 Action 来降低反射 SetValue 带来的性能损耗(装箱与GC) private Action floatSetterDelegate; #endregion #region [生命周期与工厂] Lifecycle & Factory public static PropertyAnimationFloat GenerateElement(string elementName, Guid id, List tags, bool isFirstGenerated, GameElement animatedObject, string componentName, string propertyName, FlexibleFloat propertyValue) { PropertyAnimationFloat animation = Instantiate(GameManager.Instance.basePrefabs.emptyObject) .AddComponent(); animation.Initialize(elementName, id, tags, isFirstGenerated, animatedObject); animation.animatedObject = animatedObject; animation.componentName = componentName; animation.propertyName = propertyName; animation.propertyValue = propertyValue; return animation; } public override void AfterInitialize() { if (animatedObject == null || string.IsNullOrEmpty(componentName)) return; Type componentType = GetTypeFromAllAssemblies(componentName); if (componentType != null) { targetComponent = animatedObject.GetComponentInChildren(componentType); } if (targetComponent != null) { InitializeReflection(); } else { Debug.LogWarning($"[PropertyAnimationFloat] Cannot find Component '{componentName}' strictly on '{animatedObject.name}'."); } base.AfterInitialize(); } private Type GetTypeFromAllAssemblies(string typeName) { // 对于跨程序集的搜索 foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()) { Type type = assembly.GetType(typeName); if (type != null) return type; } return null; } private void InitializeReflection() { Type type = targetComponent.GetType(); // 1. 尝试寻找 Property targetProperty = type.GetProperty(propertyName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); if (targetProperty != null && targetProperty.CanWrite) { MethodInfo setMethod = targetProperty.GetSetMethod(nonPublic: true); if (setMethod != null) { try { floatSetterDelegate = (Action)Delegate.CreateDelegate(typeof(Action), targetComponent, setMethod); return; // 建立快速委托成功! } catch { /* 回落 */ } } } // 2. 尝试寻找 Field (直接包含公有/私有) targetField = type.GetField(propertyName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); if (targetField == null && targetProperty == null) { Debug.LogWarning($"[PropertyAnimationFloat] Cannot find target Property or Field '{propertyName}' strictly on '{componentName}'."); } } #endregion #region [核心动画逻辑] Core Animation Logic protected override void UpdateAnimation(float songTime, bool forceUpdate) { if (targetComponent == null || propertyValue == null || propertyValue.animations.Count == 0) return; propertyValue.UpdateFlexibleFloat(songTime); float value = propertyValue.value; if (floatSetterDelegate != null) { floatSetterDelegate(value); } else if (targetProperty != null) { targetProperty.SetValue(targetComponent, value); } else if (targetField != null) { targetField.SetValue(targetComponent, value); } if (animatedObject is IHaveDirtyMarkSubmodule dirtyTarget) { dirtyTarget.dirtyMarkSubmodule.MarkDirty(propertyName); } } public override void ApplyTimeOffset(float offset) { base.ApplyTimeOffset(offset); if (propertyValue != null && propertyValue.animations != null) { foreach(var a in propertyValue.animations) { a.startTime += offset; a.endTime += offset; } } } #endregion } }