diff --git a/Editor/Animation/AnimationServicesContext.cs b/Editor/Animation/AnimationServicesContext.cs index 57ad5dd0..642f7573 100644 --- a/Editor/Animation/AnimationServicesContext.cs +++ b/Editor/Animation/AnimationServicesContext.cs @@ -2,13 +2,9 @@ using System; using System.Collections.Generic; -using System.Linq; using nadena.dev.ndmf; -using UnityEditor; -using UnityEditor.Animations; using UnityEngine; #if MA_VRCSDK3_AVATARS -using VRC.SDK3.Avatars.Components; #endif #endregion @@ -32,7 +28,6 @@ namespace nadena.dev.modular_avatar.animation private BuildContext _context; private AnimationDatabase _animationDatabase; private PathMappings _pathMappings; - private ReadableProperty _readableProperty; private Dictionary _selfProxies = new(); @@ -45,8 +40,6 @@ namespace nadena.dev.modular_avatar.animation _pathMappings = new PathMappings(); _pathMappings.OnActivate(context, _animationDatabase); - - _readableProperty = new ReadableProperty(_context, _animationDatabase, this); } public void OnDeactivate(BuildContext context) @@ -85,41 +78,5 @@ namespace nadena.dev.modular_avatar.animation return _pathMappings; } } - - public IEnumerable<(EditorCurveBinding, string)> BoundReadableProperties => _readableProperty.BoundProperties; - - // HACK: This is a temporary crutch until we rework the entire animator services system - public void AddPropertyDefinition(AnimatorControllerParameter paramDef) - { -#if MA_VRCSDK3_AVATARS - if (!_context.AvatarDescriptor) return; - - var fx = (AnimatorController) - _context.AvatarDescriptor.baseAnimationLayers - .First(l => l.type == VRCAvatarDescriptor.AnimLayerType.FX) - .animatorController; - - fx.parameters = fx.parameters.Concat(new[] { paramDef }).ToArray(); -#endif - } - - public string GetActiveSelfProxy(GameObject obj) - { - if (_selfProxies.TryGetValue(obj, out var paramName) && !string.IsNullOrEmpty(paramName)) return paramName; - - var path = PathMappings.GetObjectIdentifier(obj); - - paramName = _readableProperty.ForActiveSelf(path); - _selfProxies[obj] = paramName; - - return paramName; - } - - public bool ObjectHasAnimations(GameObject obj) - { - var path = PathMappings.GetObjectIdentifier(obj); - var clips = AnimationDatabase.ClipsForPath(path); - return clips != null && !clips.IsEmpty; - } } } \ No newline at end of file diff --git a/Editor/Animation/GameObjectDisableDelayPass.cs b/Editor/Animation/GameObjectDisableDelayPass.cs index 87692657..33e5b37f 100644 --- a/Editor/Animation/GameObjectDisableDelayPass.cs +++ b/Editor/Animation/GameObjectDisableDelayPass.cs @@ -2,6 +2,7 @@ using System.Linq; using nadena.dev.modular_avatar.core.editor; using nadena.dev.ndmf; +using nadena.dev.ndmf.animator; using UnityEditor; using UnityEditor.Animations; using UnityEngine; @@ -18,14 +19,13 @@ namespace nadena.dev.modular_avatar.animation { protected override void Execute(BuildContext context) { - var asc = context.Extension(); - if (!asc.BoundReadableProperties.Any()) return; - - var fx = (AnimatorController)context.AvatarDescriptor.baseAnimationLayers - .FirstOrDefault(l => l.type == VRCAvatarDescriptor.AnimLayerType.FX).animatorController; + var asc = context.Extension(); + var activeProxies = context.GetState().proxyProps; + if (activeProxies.Count == 0) return; + var fx = asc.ControllerContext[VRCAvatarDescriptor.AnimLayerType.FX]; if (fx == null) return; - + var nullMotion = new AnimationClip(); nullMotion.name = "NullMotion"; @@ -33,48 +33,31 @@ namespace nadena.dev.modular_avatar.animation blendTree.blendType = BlendTreeType.Direct; blendTree.useAutomaticThresholds = false; - blendTree.children = asc.BoundReadableProperties - .Select(prop => GenerateDelayChild(nullMotion, prop)) + blendTree.children = activeProxies + .Select(prop => GenerateDelayChild(nullMotion, (prop.Key, prop.Value))) .ToArray(); - var asm = new AnimatorStateMachine(); - var state = new AnimatorState(); - state.name = "DelayDisable"; - state.motion = blendTree; - state.writeDefaultValues = true; + var layer = fx.AddLayer(LayerPriority.Default, "DelayDisable"); + var state = layer.StateMachine.AddState("DelayDisable"); + layer.StateMachine.DefaultState = state; - asm.defaultState = state; - asm.states = new[] - { - new ChildAnimatorState - { - state = state, - position = Vector3.zero - } - }; - - fx.layers = fx.layers.Append(new AnimatorControllerLayer - { - name = "DelayDisable", - stateMachine = asm, - defaultWeight = 1, - blendingMode = AnimatorLayerBlendingMode.Override - }).ToArray(); + state.WriteDefaultValues = true; + state.Motion = asc.ControllerContext.Clone(blendTree); // Ensure the initial state of readable props matches the actual state of the gameobject - var parameters = fx.parameters; - var paramToIndex = parameters.Select((p, i) => (p, i)).ToDictionary(x => x.p.name, x => x.i); - foreach (var (binding, prop) in asc.BoundReadableProperties) + foreach (var controller in asc.ControllerContext.GetAllControllers()) { - var obj = asc.PathMappings.PathToObject(binding.path); - - if (obj != null && paramToIndex.TryGetValue(prop, out var index)) + foreach (var (binding, prop) in activeProxies) { - parameters[index].defaultFloat = obj.activeSelf ? 1 : 0; + var obj = asc.ObjectPathRemapper.GetObjectForPath(binding.path); + + if (obj != null && controller.Parameters.TryGetValue(prop, out var p)) + { + p.defaultFloat = obj.activeSelf ? 1 : 0; + controller.Parameters = controller.Parameters.SetItem(prop, p); + } } } - - fx.parameters = parameters; } private ChildMotion GenerateDelayChild(Motion nullMotion, (EditorCurveBinding, string) binding) diff --git a/Editor/Animation/ReadableProperty.cs b/Editor/Animation/ReadableProperty.cs deleted file mode 100644 index 5931f6b6..00000000 --- a/Editor/Animation/ReadableProperty.cs +++ /dev/null @@ -1,147 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using nadena.dev.ndmf; -using UnityEditor; -using UnityEditor.Animations; -using UnityEngine; -using Object = UnityEngine.Object; - -namespace nadena.dev.modular_avatar.animation -{ - internal class ReadableProperty - { - private readonly BuildContext _context; - private readonly AnimationDatabase _animDB; - private readonly AnimationServicesContext _asc; - private readonly Dictionary _alreadyBound = new(); - private long _nextIndex; - - public ReadableProperty(BuildContext context, AnimationDatabase animDB, AnimationServicesContext asc) - { - _context = context; - _animDB = animDB; - _asc = asc; - } - - public IEnumerable<(EditorCurveBinding, string)> BoundProperties => - _alreadyBound.Select(kv => (kv.Key, kv.Value)); - - /// - /// Creates an animator parameter which tracks the effective value of a property on a component. This only - /// tracks FX layer properties. - /// - /// - /// - public string ForBinding(string path, Type componentType, string property) - { - var ecb = new EditorCurveBinding - { - path = path, - type = componentType, - propertyName = property - }; - - if (_alreadyBound.TryGetValue(ecb, out var reader)) - { - return reader; - } - - var lastComponent = path.Split("/")[^1]; - var emuPropName = $"__MA/ReadableProp/{lastComponent}/{componentType}/{property}#{_nextIndex++}"; - - float initialValue = 0; - var gameObject = _asc.PathMappings.PathToObject(path); - Object component = componentType == typeof(GameObject) - ? gameObject - : gameObject?.GetComponent(componentType); - if (component != null) - { - var so = new SerializedObject(component); - var prop = so.FindProperty(property); - if (prop != null) - switch (prop.propertyType) - { - case SerializedPropertyType.Boolean: - initialValue = prop.boolValue ? 1 : 0; - break; - case SerializedPropertyType.Float: - initialValue = prop.floatValue; - break; - case SerializedPropertyType.Integer: - initialValue = prop.intValue; - break; - default: throw new NotImplementedException($"Property type {prop.type} not supported"); - } - } - - - _asc.AddPropertyDefinition(new AnimatorControllerParameter - { - defaultFloat = initialValue, - name = emuPropName, - type = AnimatorControllerParameterType.Float - }); - - BindProperty(ecb, emuPropName); - - _alreadyBound[ecb] = emuPropName; - - return emuPropName; - } - - private void BindProperty(EditorCurveBinding ecb, string propertyName) - { - var boundProp = new EditorCurveBinding - { - path = "", - type = typeof(Animator), - propertyName = propertyName - }; - - foreach (var clip in _animDB.ClipsForPath(ecb.path)) ProcessAnyClip(clip); - - void ProcessBlendTree(BlendTree blendTree) - { - foreach (var child in blendTree.children) - switch (child.motion) - { - case AnimationClip animationClip: - ProcessAnimationClip(animationClip); - break; - - case BlendTree subBlendTree: - ProcessBlendTree(subBlendTree); - break; - } - } - - void ProcessAnimationClip(AnimationClip animationClip) - { - var curve = AnimationUtility.GetEditorCurve(animationClip, ecb); - if (curve == null) return; - - AnimationUtility.SetEditorCurve(animationClip, boundProp, curve); - } - - void ProcessAnyClip(AnimationDatabase.ClipHolder clip) - { - switch (clip.CurrentClip) - { - case AnimationClip animationClip: - ProcessAnimationClip(animationClip); - break; - - case BlendTree blendTree: - ProcessBlendTree(blendTree); - break; - } - } - } - - public string ForActiveSelf(string path) - { - return ForBinding(path, typeof(GameObject), "m_IsActive"); - } - } -} \ No newline at end of file diff --git a/Editor/Animation/ReadableProperty.cs.meta b/Editor/Animation/ReadableProperty.cs.meta deleted file mode 100644 index e4399110..00000000 --- a/Editor/Animation/ReadableProperty.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: 1074339e2a59465ba585cb8cbbc4a88c -timeCreated: 1719195449 \ No newline at end of file diff --git a/Editor/Animation/ReadablePropertyExtension.cs b/Editor/Animation/ReadablePropertyExtension.cs new file mode 100644 index 00000000..8df3e333 --- /dev/null +++ b/Editor/Animation/ReadablePropertyExtension.cs @@ -0,0 +1,82 @@ +#nullable enable + +using System; +using System.Collections.Generic; +using System.Linq; +using nadena.dev.ndmf; +using nadena.dev.ndmf.animator; +using UnityEditor; +using UnityEngine; + +namespace nadena.dev.modular_avatar.animation +{ + [DependsOnContext(typeof(AnimatorServicesContext))] + internal class ReadablePropertyExtension : IExtensionContext + { + // This is a temporary hack for GameObjectDelayDisablePass + public class Retained + { + public Dictionary proxyProps = new(); + } + + private AnimatorServicesContext? _asc; + private Retained _retained; + + private AnimatorServicesContext asc => + _asc ?? throw new InvalidOperationException("ActiveSelfProxyExtension is not active"); + + private Dictionary proxyProps => _retained.proxyProps; + private int index; + + public IEnumerable<(EditorCurveBinding, string)> ActiveProxyProps => + proxyProps.Select(kvp => (kvp.Key, kvp.Value)); + + public string GetActiveSelfProxy(GameObject obj) + { + var path = asc.ObjectPathRemapper.GetVirtualPathForObject(obj); + var ecb = EditorCurveBinding.FloatCurve(path, typeof(GameObject), "m_IsActive"); + + if (proxyProps.TryGetValue(ecb, out var prop)) return prop; + + prop = $"__MA/ActiveSelfProxy/{obj.name}##{index++}"; + proxyProps[ecb] = prop; + + // Add prop to all animators + foreach (var animator in asc.ControllerContext.GetAllControllers()) + { + animator.Parameters = animator.Parameters.SetItem( + prop, + new AnimatorControllerParameter + { + name = prop, + type = AnimatorControllerParameterType.Float, + defaultFloat = obj.activeSelf ? 1 : 0 + } + ); + } + + return prop; + } + + public void OnActivate(BuildContext context) + { + _asc = context.Extension(); + _retained = context.GetState(); + } + + public void OnDeactivate(BuildContext context) + { + asc.AnimationIndex.EditClipsByBinding(proxyProps.Keys, clip => + { + foreach (var b in clip.GetFloatCurveBindings().ToList()) + { + if (proxyProps.TryGetValue(b, out var proxyProp)) + { + var curve = clip.GetFloatCurve(b); + clip.SetFloatCurve("", typeof(Animator), proxyProp, curve); + } + } + }); + } + } +} \ No newline at end of file diff --git a/Editor/Animation/ReadablePropertyExtension.cs.meta b/Editor/Animation/ReadablePropertyExtension.cs.meta new file mode 100644 index 00000000..93a503c5 --- /dev/null +++ b/Editor/Animation/ReadablePropertyExtension.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 511cbc0373a2469192e0351e2222a203 +timeCreated: 1732496091 \ No newline at end of file diff --git a/Editor/PluginDefinition/PluginDefinition.cs b/Editor/PluginDefinition/PluginDefinition.cs index 4a31f17a..4637429b 100644 --- a/Editor/PluginDefinition/PluginDefinition.cs +++ b/Editor/PluginDefinition/PluginDefinition.cs @@ -6,6 +6,7 @@ using nadena.dev.modular_avatar.core.ArmatureAwase; using nadena.dev.modular_avatar.core.editor.plugin; using nadena.dev.modular_avatar.editor.ErrorReporting; using nadena.dev.ndmf; +using nadena.dev.ndmf.animator; using nadena.dev.ndmf.fluent; using UnityEngine; using Object = UnityEngine.Object; @@ -55,12 +56,22 @@ namespace nadena.dev.modular_avatar.core.editor.plugin seq.Run(MergeAnimatorPluginPass.Instance); seq.Run(ApplyAnimatorDefaultValuesPass.Instance); #endif + seq.WithRequiredExtension(typeof(AnimatorServicesContext), _s2 => + { +#if MA_VRCSDK3_AVATARS + seq.WithRequiredExtension(typeof(ReadablePropertyExtension), _s3 => + { + seq.Run("Shape Changer", ctx => new ReactiveObjectPass(ctx).Execute()) + .PreviewingWith(new ShapeChangerPreview(), new ObjectSwitcherPreview(), + new MaterialSetterPreview()); + }); + seq.Run(GameObjectDelayDisablePass.Instance); +#endif + }); + seq.WithRequiredExtension(typeof(AnimationServicesContext), _s2 => { #if MA_VRCSDK3_AVATARS - seq.Run("Shape Changer", ctx => new ReactiveObjectPass(ctx).Execute()) - .PreviewingWith(new ShapeChangerPreview(), new ObjectSwitcherPreview(), new MaterialSetterPreview()); - // TODO: We currently run this above MergeArmaturePlugin, because Merge Armature might destroy // game objects which contain Menu Installers. It'd probably be better however to teach Merge Armature // to retain those objects? maybe? @@ -78,7 +89,7 @@ namespace nadena.dev.modular_avatar.core.editor.plugin seq.Run(ReplaceObjectPluginPass.Instance); #if MA_VRCSDK3_AVATARS seq.Run(BlendshapeSyncAnimationPluginPass.Instance); - seq.Run(GameObjectDelayDisablePass.Instance); + // seq.Run(GameObjectDelayDisablePass.Instance); - TODO, move back here #endif seq.Run(ConstraintConverterPass.Instance); }); diff --git a/Editor/ReactiveObjects/AnimationGeneration/ReactiveObjectAnalyzer.LocateReactions.cs b/Editor/ReactiveObjects/AnimationGeneration/ReactiveObjectAnalyzer.LocateReactions.cs index 7cc2e403..c428d2c3 100644 --- a/Editor/ReactiveObjects/AnimationGeneration/ReactiveObjectAnalyzer.LocateReactions.cs +++ b/Editor/ReactiveObjects/AnimationGeneration/ReactiveObjectAnalyzer.LocateReactions.cs @@ -31,7 +31,7 @@ namespace nadena.dev.modular_avatar.core.editor { if (_asc != null) { - return _asc.GetActiveSelfProxy(obj); + return _rpe.GetActiveSelfProxy(obj); } else { diff --git a/Editor/ReactiveObjects/AnimationGeneration/ReactiveObjectAnalyzer.cs b/Editor/ReactiveObjects/AnimationGeneration/ReactiveObjectAnalyzer.cs index 228f215c..986499fe 100644 --- a/Editor/ReactiveObjects/AnimationGeneration/ReactiveObjectAnalyzer.cs +++ b/Editor/ReactiveObjects/AnimationGeneration/ReactiveObjectAnalyzer.cs @@ -4,6 +4,7 @@ using System.Collections.Immutable; using System.Linq; using nadena.dev.modular_avatar.animation; using nadena.dev.modular_avatar.core.editor.Simulator; +using nadena.dev.ndmf.animator; using nadena.dev.ndmf.preview; using UnityEngine; @@ -17,7 +18,9 @@ namespace nadena.dev.modular_avatar.core.editor { private readonly ComputeContext _computeContext; private readonly ndmf.BuildContext _context; - private readonly AnimationServicesContext _asc; + private readonly AnimatorServicesContext _asc; + private readonly ReadablePropertyExtension _rpe; + private Dictionary _simulationInitialStates; public const string BlendshapePrefix = "blendShape."; @@ -34,7 +37,8 @@ namespace nadena.dev.modular_avatar.core.editor { _computeContext = ComputeContext.NullContext; _context = context; - _asc = context.Extension(); + _asc = context.Extension(); + _rpe = context.Extension(); _simulationInitialStates = null; } @@ -145,7 +149,7 @@ namespace nadena.dev.modular_avatar.core.editor /// private void AnalyzeConstants(Dictionary shapes) { - var asc = _context?.Extension(); + var asc = _context?.Extension(); HashSet toggledObjects = new(); if (asc == null) return; @@ -160,7 +164,10 @@ namespace nadena.dev.modular_avatar.core.editor { foreach (var condition in actionGroup.ControllingConditions) if (condition.ReferenceObject != null && !toggledObjects.Contains(condition.ReferenceObject)) - condition.IsConstant = asc.AnimationDatabase.ClipsForPath(asc.PathMappings.GetObjectIdentifier(condition.ReferenceObject)).IsEmpty; + condition.IsConstant = !asc.AnimationIndex.GetClipsForObjectPath( + asc.ObjectPathRemapper.GetVirtualPathForObject(condition.ReferenceObject) ?? + "___NONEXISTENT___" + ).Any(); // Remove redundant active conditions. actionGroup.ControllingConditions.RemoveAll(c => c.IsConstant && c.InitiallyActive); @@ -187,7 +194,7 @@ namespace nadena.dev.modular_avatar.core.editor /// private void ResolveToggleInitialStates(Dictionary groups) { - var asc = _context?.Extension(); + var asc = _context?.Extension(); Dictionary propStates = new(); Dictionary nextPropStates = new(); diff --git a/Editor/ReactiveObjects/AnimationGeneration/ReactiveObjectPass.cs b/Editor/ReactiveObjects/AnimationGeneration/ReactiveObjectPass.cs index 2d73a816..1c0f3fb2 100644 --- a/Editor/ReactiveObjects/AnimationGeneration/ReactiveObjectPass.cs +++ b/Editor/ReactiveObjects/AnimationGeneration/ReactiveObjectPass.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Linq; using nadena.dev.modular_avatar.animation; +using nadena.dev.ndmf.animator; using UnityEditor; using UnityEditor.Animations; using UnityEngine; @@ -23,8 +24,8 @@ namespace nadena.dev.modular_avatar.core.editor // Properties that are being driven, either by foreign animations or Object Toggles private HashSet activeProps = new(); - - private AnimationClip _initialStateClip; + + private VirtualClip _initialStateClip; private bool _writeDefaults; public ReactiveObjectPass(ndmf.BuildContext context) @@ -60,7 +61,7 @@ namespace nadena.dev.modular_avatar.core.editor private void GenerateActiveSelfProxies(Dictionary shapes) { - var asc = context.Extension(); + var rpe = context.Extension(); foreach (var prop in shapes.Keys) { @@ -68,7 +69,7 @@ namespace nadena.dev.modular_avatar.core.editor { // Ensure a proxy exists for each object we're going to be toggling. // TODO: is this still needed? - asc.GetActiveSelfProxy(go); + rpe.GetActiveSelfProxy(go); } } } @@ -91,19 +92,19 @@ namespace nadena.dev.modular_avatar.core.editor private void ProcessInitialStates(Dictionary initialStates, Dictionary shapes) { - var asc = context.Extension(); + var asc = context.Extension(); + var rpe = context.Extension(); // We need to track _two_ initial states: the initial state we'll apply at build time (which applies // when animations are disabled) and the animation base state. Confusingly, the animation base state // should be the state that is currently applied to the object... - - var clips = context.Extension().AnimationDatabase; - var initialStateHolder = clips.ClipsForPath(ReactiveObjectPrepass.TAG_PATH).FirstOrDefault(); - if (initialStateHolder == null) return; - _initialStateClip = new AnimationClip(); - _initialStateClip.name = "Reactive Component Defaults"; - initialStateHolder.CurrentClip = _initialStateClip; + var clips = asc.AnimationIndex; + _initialStateClip = clips.GetClipsForObjectPath(ReactiveObjectPrepass.TAG_PATH).FirstOrDefault(); + + if (_initialStateClip == null) return; + + _initialStateClip.Name = "Reactive Component Defaults"; foreach (var (key, initialState) in initialStates) { @@ -186,17 +187,17 @@ namespace nadena.dev.modular_avatar.core.editor curve.AddKey(0, f); curve.AddKey(1, f); - AnimationUtility.SetEditorCurve(_initialStateClip, binding, curve); + _initialStateClip.SetFloatCurve(binding, curve); if (componentType == typeof(GameObject) && key.PropertyName == "m_IsActive") { binding = EditorCurveBinding.FloatCurve( "", typeof(Animator), - asc.GetActiveSelfProxy((GameObject)key.TargetObject) + rpe.GetActiveSelfProxy((GameObject)key.TargetObject) ); - AnimationUtility.SetEditorCurve(_initialStateClip, binding, curve); + _initialStateClip.SetFloatCurve(binding, curve); } } else if (animBaseState is Object obj) @@ -206,8 +207,8 @@ namespace nadena.dev.modular_avatar.core.editor componentType, key.PropertyName ); - - AnimationUtility.SetObjectReferenceCurve(_initialStateClip, binding, new [] + + _initialStateClip.SetObjectCurve(binding, new[] { new ObjectReferenceKeyframe() { @@ -299,7 +300,7 @@ namespace nadena.dev.modular_avatar.core.editor private AnimatorStateMachine GenerateStateMachine(AnimatedProperty info) { - var asc = context.Extension(); + var asc = context.Extension(); var asm = new AnimatorStateMachine(); // Workaround for the warning: "'.' is not allowed in State name" @@ -326,7 +327,6 @@ namespace nadena.dev.modular_avatar.core.editor position = new Vector3(x, y), state = initialState }); - asc.AnimationDatabase.RegisterState(states[^1].state); var lastConstant = info.actionGroups.FindLastIndex(agk => agk.IsConstant); var transitionBuffer = new List<(AnimatorState, List)>(); @@ -350,7 +350,7 @@ namespace nadena.dev.modular_avatar.core.editor clip.name = "Property Overlay controlled by " + group.ControllingConditions[0].DebugName + " " + group.Value; - var conditions = GetTransitionConditions(asc, group); + var conditions = GetTransitionConditions(group); foreach (var (st, transitions) in transitionBuffer) { @@ -394,7 +394,6 @@ namespace nadena.dev.modular_avatar.core.editor position = new Vector3(x, y), state = state }); - asc.AnimationDatabase.RegisterState(states[^1].state); var transitionList = new List(); transitionBuffer.Add((state, transitionList)); @@ -466,7 +465,7 @@ namespace nadena.dev.modular_avatar.core.editor }; } - private AnimatorCondition[] GetTransitionConditions(AnimationServicesContext asc, ReactionRule group) + private AnimatorCondition[] GetTransitionConditions(ReactionRule group) { var conditions = new List(); @@ -552,8 +551,8 @@ namespace nadena.dev.modular_avatar.core.editor if (key.TargetObject is GameObject targetObject && key.PropertyName == "m_IsActive") { - var asc = context.Extension(); - var propName = asc.GetActiveSelfProxy(targetObject); + var rpe = context.Extension(); + var propName = rpe.GetActiveSelfProxy(targetObject); binding = EditorCurveBinding.FloatCurve("", typeof(Animator), propName); AnimationUtility.SetEditorCurve(clip, binding, curve); } @@ -564,47 +563,29 @@ namespace nadena.dev.modular_avatar.core.editor private void ApplyController(AnimatorStateMachine asm, string layerName) { - var fx = FindFxController(); - - if (fx.animatorController == null) + var asc = context.Extension(); + var fx = asc.ControllerContext[ + VRCAvatarDescriptor.AnimLayerType.FX + ]; + + if (fx == null) { throw new InvalidOperationException("No FX layer found"); } - - if (!context.IsTemporaryAsset(fx.animatorController)) - { - throw new InvalidOperationException("FX layer is not a temporary asset"); - } - if (!(fx.animatorController is AnimatorController animController)) + foreach (var paramName in initialValues.Keys.Except(fx.Parameters.Keys)) { - throw new InvalidOperationException("FX layer is not an animator controller"); - } - - var paramList = animController.parameters.ToList(); - var paramSet = paramList.Select(p => p.name).ToHashSet(); - - foreach (var paramName in initialValues.Keys.Except(paramSet)) - { - paramList.Add(new AnimatorControllerParameter() + var parameter = new AnimatorControllerParameter { name = paramName, type = AnimatorControllerParameterType.Float, defaultFloat = initialValues[paramName], // TODO - }); - paramSet.Add(paramName); + }; + fx.Parameters = fx.Parameters.SetItem(paramName, parameter); } - animController.parameters = paramList.ToArray(); - - animController.layers = animController.layers.Append( - new AnimatorControllerLayer - { - stateMachine = asm, - name = "RC " + layerName, - defaultWeight = 1 - } - ).ToArray(); + fx.AddLayer(LayerPriority.Default, "RC " + layerName).StateMachine = + asc.ControllerContext.Clone(asm); } private VRCAvatarDescriptor.CustomAnimLayer FindFxController()