From e91b8ab6c363056ab2ce011be74ab629ccd8770e Mon Sep 17 00:00:00 2001 From: bd_ Date: Sun, 9 Mar 2025 14:32:50 -0700 Subject: [PATCH] refactor: use IVirtualize* --- Editor/MergeAnimatorProcessor.cs | 58 ++++++++-------- Editor/MergeBlendTreePass.cs | 48 +++++++------- Editor/RenameParametersHook.cs | 66 +++---------------- Runtime/AvatarTagComponent.cs | 4 +- Runtime/ModularAvatarMergeAnimator.cs | 24 ++++++- Runtime/ModularAvatarMergeBlendTree.cs | 28 +++++++- .../MergedController_AC2.prefab | 13 +++- .../SyncedLayerHandling.cs | 5 +- 8 files changed, 126 insertions(+), 120 deletions(-) diff --git a/Editor/MergeAnimatorProcessor.cs b/Editor/MergeAnimatorProcessor.cs index 0c3b208a..eb1ea84a 100644 --- a/Editor/MergeAnimatorProcessor.cs +++ b/Editor/MergeAnimatorProcessor.cs @@ -27,6 +27,7 @@ using System.Collections.Generic; using System.Linq; using nadena.dev.ndmf.animator; +using UnityEditor; using UnityEngine; using VRC.SDK3.Avatars.Components; using Object = UnityEngine.Object; @@ -37,6 +38,23 @@ namespace nadena.dev.modular_avatar.core.editor { private AnimatorServicesContext _asc; + [InitializeOnLoadMethod] + private static void Init() + { + ModularAvatarMergeAnimator.GetMotionBasePathCallback = (merge, objectBuildContext) => + { + if (merge.pathMode == MergeAnimatorPathMode.Absolute) return ""; + + var context = (ndmf.BuildContext)objectBuildContext; + + var targetObject = merge.relativePathRoot.Get(context.AvatarRootTransform); + if (targetObject == null) targetObject = merge.gameObject; + + var relativePath = RuntimeUtil.RelativePath(context.AvatarRootObject, targetObject); + return relativePath != "" ? relativePath : ""; + }; + } + internal void OnPreprocessAvatar(GameObject avatarGameObject, BuildContext context) { _asc = context.PluginBuildContext.Extension(); @@ -92,35 +110,18 @@ namespace nadena.dev.modular_avatar.core.editor } } - private void MergeSingle(BuildContext context, VirtualAnimatorController controller, ModularAvatarMergeAnimator merge, bool? initialWriteDefaults) + private void MergeSingle(BuildContext context, VirtualAnimatorController targetController, + ModularAvatarMergeAnimator merge, bool? initialWriteDefaults) { if (merge.animator == null) { return; } - - var stash = context.PluginBuildContext.GetState(); - - var clonedController = stash.Controllers.GetValueOrDefault(merge) - ?? _asc.ControllerContext.CloneContext.CloneDistinct(merge.animator); - - string basePath; - if (merge.pathMode == MergeAnimatorPathMode.Relative) - { - var targetObject = merge.relativePathRoot.Get(context.AvatarRootTransform); - if (targetObject == null) targetObject = merge.gameObject; - var relativePath = RuntimeUtil.RelativePath(context.AvatarRootObject, targetObject); - basePath = relativePath != "" ? relativePath + "/" : ""; - - var animationIndex = new AnimationIndex(new[] { clonedController }); - animationIndex.RewritePaths(p => p == "" ? relativePath : basePath + p); - } - else - { - basePath = ""; - } + var vac = context.PluginBuildContext.Extension(); + if (!vac.Controllers.TryGetValue(merge, out var clonedController)) return; + var firstLayer = clonedController.Layers.FirstOrDefault(); // the first layer in an animator controller always has weight 1.0f (regardless of what is serialized) if (firstLayer != null) firstLayer.DefaultWeight = 1.0f; @@ -134,12 +135,13 @@ namespace nadena.dev.modular_avatar.core.editor s.WriteDefaultValues = initialWriteDefaults.Value; } } - controller.AddLayer(new LayerPriority(merge.layerPriority), l); + + targetController.AddLayer(new LayerPriority(merge.layerPriority), l); } foreach (var (name, parameter) in clonedController.Parameters) { - if (controller.Parameters.TryGetValue(name, out var existingParam)) + if (targetController.Parameters.TryGetValue(name, out var existingParam)) { if (existingParam.type != parameter.type) { @@ -156,12 +158,12 @@ namespace nadena.dev.modular_avatar.core.editor existingParam.type = AnimatorControllerParameterType.Float; - controller.Parameters = controller.Parameters.SetItem(name, existingParam); + targetController.Parameters = targetController.Parameters.SetItem(name, existingParam); } continue; } - - controller.Parameters = controller.Parameters.Add(name, parameter); + + targetController.Parameters = targetController.Parameters.Add(name, parameter); } if (merge.deleteAttachedAnimator) @@ -169,6 +171,8 @@ namespace nadena.dev.modular_avatar.core.editor var animator = merge.GetComponent(); if (animator != null) Object.DestroyImmediate(animator); } + + Object.DestroyImmediate(merge); } } } diff --git a/Editor/MergeBlendTreePass.cs b/Editor/MergeBlendTreePass.cs index fa8b113e..c03b6197 100644 --- a/Editor/MergeBlendTreePass.cs +++ b/Editor/MergeBlendTreePass.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using nadena.dev.ndmf; using nadena.dev.ndmf.animator; +using UnityEditor; using UnityEditor.Animations; using UnityEngine; using VRC.SDK3.Avatars.Components; @@ -22,6 +23,21 @@ namespace nadena.dev.modular_avatar.core.editor private VirtualBlendTree _rootBlendTree; private HashSet _parameterNames; + [InitializeOnLoadMethod] + private static void Init() + { + ModularAvatarMergeBlendTree.GetMotionBasePathCallback = (mbt, objectBuildContext) => + { + if (mbt.PathMode == MergeAnimatorPathMode.Absolute) return ""; + + var buildContext = (ndmf.BuildContext)objectBuildContext; + var root = mbt.RelativePathRoot.Get(buildContext.AvatarRootTransform); + if (root == null) root = mbt.gameObject; + + return RuntimeUtil.AvatarRootPath(root); + }; + } + protected override void Execute(ndmf.BuildContext context) { _asc = context.Extension(); @@ -59,48 +75,28 @@ namespace nadena.dev.modular_avatar.core.editor private void ProcessComponent(BuildContext context, ModularAvatarMergeBlendTree component) { - var stash = context.PluginBuildContext.GetState(); - - BlendTree componentBlendTree = component.BlendTree as BlendTree; - - if (componentBlendTree == null) + var virtualBlendTree = _asc.ControllerContext.GetVirtualizedMotion(component); + + if (virtualBlendTree == null) { ErrorReport.ReportError(Localization.L, ErrorSeverity.NonFatal, "error.merge_blend_tree.missing_tree"); return; } string basePath = null; - string rootPath = null; - if (component.PathMode == MergeAnimatorPathMode.Relative) - { - var root = component.RelativePathRoot.Get(context.AvatarRootTransform); - if (root == null) root = component.gameObject; - - rootPath = RuntimeUtil.AvatarRootPath(root); - basePath = rootPath + "/"; - } - - var bt = stash.BlendTrees.GetValueOrDefault(component) - ?? _asc.ControllerContext.CloneContext.Clone(componentBlendTree); - - if (basePath != null) - { - var animationIndex = new AnimationIndex(new[] { bt }); - animationIndex.RewritePaths(p => p == "" ? rootPath : basePath + p); - } var rootBlend = GetRootBlendTree(); rootBlend.Children = rootBlend.Children.Add(new() { - Motion = bt, + Motion = virtualBlendTree, DirectBlendParameter = ALWAYS_ONE, Threshold = 1, CycleOffset = 1, TimeScale = 1, }); - foreach (var asset in bt.AllReachableNodes()) + foreach (var asset in virtualBlendTree.AllReachableNodes()) { if (asset is VirtualBlendTree bt2) { @@ -129,6 +125,8 @@ namespace nadena.dev.modular_avatar.core.editor } } } + + Object.DestroyImmediate(component); } private VirtualBlendTree GetRootBlendTree() diff --git a/Editor/RenameParametersHook.cs b/Editor/RenameParametersHook.cs index 6d100091..2e4eed2e 100644 --- a/Editor/RenameParametersHook.cs +++ b/Editor/RenameParametersHook.cs @@ -10,7 +10,6 @@ using nadena.dev.modular_avatar.editor.ErrorReporting; using nadena.dev.ndmf; using nadena.dev.ndmf.animator; using UnityEditor; -using UnityEditor.Animations; using UnityEngine; using VRC.SDK3.Avatars.Components; using VRC.SDK3.Avatars.ScriptableObjects; @@ -55,43 +54,6 @@ namespace nadena.dev.modular_avatar.core.editor public ImmutableDictionary InitialValueOverrides; } - internal class RenamedMergeAnimators - { - public AnimatorServicesContext AnimatorServices; - public Dictionary Controllers = new(); - public Dictionary BlendTrees = new(); - - public VirtualAnimatorController Clone(ModularAvatarMergeAnimator mama) - { - if (Controllers.TryGetValue(mama, out var controller)) - { - return controller; - } - - if (mama.animator == null) return null; - - var cloned = AnimatorServices.ControllerContext.CloneContext.CloneDistinct(mama.animator, mama.layerType); - Controllers[mama] = cloned; - - return cloned; - } - - public VirtualBlendTree Clone(ModularAvatarMergeBlendTree mbt) - { - if (BlendTrees.TryGetValue(mbt, out var blendTree)) - { - return blendTree; - } - - if (mbt.BlendTree is not BlendTree bt) return null; - - var cloned = (VirtualBlendTree)AnimatorServices.ControllerContext.CloneContext.Clone(bt); - BlendTrees[mbt] = cloned; - - return cloned; - } - } - internal class RenameParametersHook { private const string DEFAULT_EXP_PARAMS_ASSET_GUID = "03a6d797deb62f0429471c4e17ea99a7"; @@ -200,10 +162,6 @@ namespace nadena.dev.modular_avatar.core.editor _context = context; - var stash = _context.PluginBuildContext.GetState(); - var asc = _context.PluginBuildContext.Extension(); - stash.AnimatorServices = asc; - var syncParams = WalkTree(avatar); SetExpressionParameters(avatar, syncParams); @@ -359,6 +317,8 @@ namespace nadena.dev.modular_avatar.core.editor GameObject obj ) { + var animServices = _context.PluginBuildContext.Extension(); + var paramInfo = ndmf.ParameterInfo.ForContext(_context.PluginBuildContext); ImmutableDictionary rv = ImmutableDictionary.Empty; @@ -406,7 +366,7 @@ namespace nadena.dev.modular_avatar.core.editor break; } - case ModularAvatarMergeAnimator merger: + case IVirtualizeAnimatorController virtualized: { var mappings = paramInfo.GetParameterRemappingsAt(obj); var remap = mappings.SelectMany(item => @@ -421,15 +381,10 @@ namespace nadena.dev.modular_avatar.core.editor ); }).ToImmutableDictionary(); - if (merger.animator != null) + var controller = animServices.ControllerContext[virtualized]; + if (controller != null) { - var stash = _context.PluginBuildContext.GetState(); - - var controller = stash.Clone(merger); - ProcessVirtualAnimatorController(controller, remap); - - stash.Controllers[merger] = controller; } break; @@ -437,15 +392,10 @@ namespace nadena.dev.modular_avatar.core.editor case ModularAvatarMergeBlendTree merger: { - var bt = merger.BlendTree as BlendTree; - if (bt != null) + var motion = animServices.ControllerContext.GetVirtualizedMotion(merger); + if (motion is VirtualBlendTree bt) { - var stash = _context.PluginBuildContext.GetState(); - - var virtualbt = stash.Clone(merger); - ProcessBlendtree(virtualbt, paramInfo.GetParameterRemappingsAt(obj)); - - stash.BlendTrees[merger] = virtualbt; + ProcessBlendtree(bt, paramInfo.GetParameterRemappingsAt(obj)); } break; diff --git a/Runtime/AvatarTagComponent.cs b/Runtime/AvatarTagComponent.cs index bb9d73f9..46168a07 100644 --- a/Runtime/AvatarTagComponent.cs +++ b/Runtime/AvatarTagComponent.cs @@ -23,9 +23,9 @@ */ using System; +using nadena.dev.ndmf; using UnityEngine; #if MA_VRCSDK3_AVATARS -using VRC.SDKBase; #endif namespace nadena.dev.modular_avatar.core @@ -35,7 +35,7 @@ namespace nadena.dev.modular_avatar.core /// inherited by user classes, and will be removed in Modular Avatar 2.0. /// [DefaultExecutionOrder(-9999)] // run before av3emu - public abstract class AvatarTagComponent : MonoBehaviour, IEditorOnly + public abstract class AvatarTagComponent : MonoBehaviour, INDMFEditorOnly { internal static event Action OnChangeAction; diff --git a/Runtime/ModularAvatarMergeAnimator.cs b/Runtime/ModularAvatarMergeAnimator.cs index da3a7412..fde947d1 100644 --- a/Runtime/ModularAvatarMergeAnimator.cs +++ b/Runtime/ModularAvatarMergeAnimator.cs @@ -25,6 +25,7 @@ #if MA_VRCSDK3_AVATARS using System; +using nadena.dev.ndmf.animator; using UnityEngine; using VRC.SDK3.Avatars.Components; @@ -38,8 +39,11 @@ namespace nadena.dev.modular_avatar.core [AddComponentMenu("Modular Avatar/MA Merge Animator")] [HelpURL("https://modular-avatar.nadena.dev/docs/reference/merge-animator?lang=auto")] - public class ModularAvatarMergeAnimator : AvatarTagComponent + public class ModularAvatarMergeAnimator : AvatarTagComponent, IVirtualizeAnimatorController { + internal static Func GetMotionBasePathCallback = + (_, _) => ""; + public RuntimeAnimatorController animator; public VRCAvatarDescriptor.AnimLayerType layerType = VRCAvatarDescriptor.AnimLayerType.FX; public bool deleteAttachedAnimator; @@ -47,7 +51,7 @@ namespace nadena.dev.modular_avatar.core public bool matchAvatarWriteDefaults; public AvatarObjectReference relativePathRoot = new AvatarObjectReference(); public int layerPriority = 0; - + public override void ResolveReferences() { // no-op @@ -67,6 +71,22 @@ namespace nadena.dev.modular_avatar.core { deleteAttachedAnimator = true; } + + RuntimeAnimatorController IVirtualizeAnimatorController.AnimatorController + { + get => animator; + set => animator = value; + } + + string IVirtualizeAnimatorController.GetMotionBasePath(object ndmfBuildContext, bool clearPath = true) + { + var path = GetMotionBasePathCallback(this, ndmfBuildContext); + if (clearPath) pathMode = MergeAnimatorPathMode.Absolute; + + return path; + } + + object IVirtualizeAnimatorController.TargetControllerKey => layerType; } } diff --git a/Runtime/ModularAvatarMergeBlendTree.cs b/Runtime/ModularAvatarMergeBlendTree.cs index f845aae3..b58f721a 100644 --- a/Runtime/ModularAvatarMergeBlendTree.cs +++ b/Runtime/ModularAvatarMergeBlendTree.cs @@ -1,17 +1,41 @@ #if MA_VRCSDK3_AVATARS +using System; +using API; using UnityEngine; +using Object = UnityEngine.Object; namespace nadena.dev.modular_avatar.core { [AddComponentMenu("Modular Avatar/MA Merge Blend Tree")] [HelpURL("https://modular-avatar.nadena.dev/docs/reference/merge-blend-tree?lang=auto")] - public sealed class ModularAvatarMergeBlendTree : AvatarTagComponent + public sealed class ModularAvatarMergeBlendTree : AvatarTagComponent, IVirtualizeMotion { + internal static Func GetMotionBasePathCallback + = (_, _) => ""; + // We can't actually reference a BlendTree here because it's not available when building a player build - public UnityEngine.Object BlendTree; + public Object BlendTree; public MergeAnimatorPathMode PathMode = MergeAnimatorPathMode.Relative; public AvatarObjectReference RelativePathRoot = new AvatarObjectReference(); + + Motion IVirtualizeMotion.Motion + { + get => (Motion)BlendTree; + set => BlendTree = value; + } + + string IVirtualizeMotion.GetMotionBasePath(object ndmfBuildContext, bool clearPath = true) + { + var path = GetMotionBasePathCallback(this, ndmfBuildContext); + + if (clearPath) + { + PathMode = MergeAnimatorPathMode.Absolute; + } + + return path; + } } } diff --git a/UnitTests~/SyncedLayerHandling/MergedController_AC2.prefab b/UnitTests~/SyncedLayerHandling/MergedController_AC2.prefab index a276fb61..21fbe9fd 100644 --- a/UnitTests~/SyncedLayerHandling/MergedController_AC2.prefab +++ b/UnitTests~/SyncedLayerHandling/MergedController_AC2.prefab @@ -25,13 +25,14 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 521542568012153140} + serializedVersion: 2 m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: -1.6884441, y: 1.1147345, z: -4.914471} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: - {fileID: 6479369535091914718} m_Father: {fileID: 0} - m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!114 &8063907918941263456 MonoBehaviour: @@ -59,6 +60,7 @@ MonoBehaviour: unityVersion: portraitCameraPositionOffset: {x: 0, y: 0, z: 0} portraitCameraRotationOffset: {x: 0, y: 1, z: 0, w: -0.00000004371139} + networkIDs: [] customExpressions: 0 expressionsMenu: {fileID: 0} expressionParameters: {fileID: 0} @@ -335,12 +337,13 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 7379231148671166614} + serializedVersion: 2 m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 2616869947308232229} - m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!114 &5229589187063892731 MonoBehaviour: @@ -366,8 +369,12 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 1bb122659f724ebf85fe095ac02dc339, type: 3} m_Name: m_EditorClassIdentifier: - animator: {fileID: 9100000, guid: 22f4686ec8638e243854be6fccb38820, type: 2} + animator: {fileID: 9100000, guid: 26ef9d769605a1e46a5ad2768e28ad73, type: 2} layerType: 5 deleteAttachedAnimator: 0 pathMode: 0 matchAvatarWriteDefaults: 0 + relativePathRoot: + referencePath: + targetObject: {fileID: 0} + layerPriority: 0 diff --git a/UnitTests~/SyncedLayerHandling/SyncedLayerHandling.cs b/UnitTests~/SyncedLayerHandling/SyncedLayerHandling.cs index ccf893b4..11184ee8 100644 --- a/UnitTests~/SyncedLayerHandling/SyncedLayerHandling.cs +++ b/UnitTests~/SyncedLayerHandling/SyncedLayerHandling.cs @@ -1,7 +1,9 @@ #if MA_VRCSDK3_AVATARS +using System.Linq; using nadena.dev.modular_avatar.core.editor; using NUnit.Framework; +using UnityEditor.Animations; using VRC.SDK3.Avatars.Components; namespace modular_avatar_tests.SyncedLayerHandling @@ -102,6 +104,7 @@ namespace modular_avatar_tests.SyncedLayerHandling var prefab = CreatePrefab("MergedController_AC2.prefab"); AvatarProcessor.ProcessAvatar(prefab); + var fx = (AnimatorController) FindFxController(prefab).animatorController; var mainLayer = findFxLayer(prefab, "main"); var syncLayer = findFxLayer(prefab, "sync"); @@ -114,7 +117,7 @@ namespace modular_avatar_tests.SyncedLayerHandling var layercontrol = overrides[0] as VRCAnimatorLayerControl; Assert.NotNull(layercontrol); - Assert.AreEqual(1, layercontrol.layer); + Assert.AreEqual(layercontrol.layer, fx.layers.TakeWhile(l => l.name != "main").Count()); } } }