refactor: use IVirtualize*

This commit is contained in:
bd_ 2025-03-09 14:32:50 -07:00
parent e3a01ff58b
commit e91b8ab6c3
8 changed files with 126 additions and 120 deletions

View File

@ -27,6 +27,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using nadena.dev.ndmf.animator; using nadena.dev.ndmf.animator;
using UnityEditor;
using UnityEngine; using UnityEngine;
using VRC.SDK3.Avatars.Components; using VRC.SDK3.Avatars.Components;
using Object = UnityEngine.Object; using Object = UnityEngine.Object;
@ -37,6 +38,23 @@ namespace nadena.dev.modular_avatar.core.editor
{ {
private AnimatorServicesContext _asc; 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) internal void OnPreprocessAvatar(GameObject avatarGameObject, BuildContext context)
{ {
_asc = context.PluginBuildContext.Extension<AnimatorServicesContext>(); _asc = context.PluginBuildContext.Extension<AnimatorServicesContext>();
@ -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) if (merge.animator == null)
{ {
return; return;
} }
var stash = context.PluginBuildContext.GetState<RenamedMergeAnimators>();
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); var vac = context.PluginBuildContext.Extension<VirtualControllerContext>();
basePath = relativePath != "" ? relativePath + "/" : "";
var animationIndex = new AnimationIndex(new[] { clonedController });
animationIndex.RewritePaths(p => p == "" ? relativePath : basePath + p);
}
else
{
basePath = "";
}
if (!vac.Controllers.TryGetValue(merge, out var clonedController)) return;
var firstLayer = clonedController.Layers.FirstOrDefault(); var firstLayer = clonedController.Layers.FirstOrDefault();
// the first layer in an animator controller always has weight 1.0f (regardless of what is serialized) // the first layer in an animator controller always has weight 1.0f (regardless of what is serialized)
if (firstLayer != null) firstLayer.DefaultWeight = 1.0f; if (firstLayer != null) firstLayer.DefaultWeight = 1.0f;
@ -134,12 +135,13 @@ namespace nadena.dev.modular_avatar.core.editor
s.WriteDefaultValues = initialWriteDefaults.Value; 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) 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) if (existingParam.type != parameter.type)
{ {
@ -156,12 +158,12 @@ namespace nadena.dev.modular_avatar.core.editor
existingParam.type = AnimatorControllerParameterType.Float; existingParam.type = AnimatorControllerParameterType.Float;
controller.Parameters = controller.Parameters.SetItem(name, existingParam); targetController.Parameters = targetController.Parameters.SetItem(name, existingParam);
} }
continue; continue;
} }
controller.Parameters = controller.Parameters.Add(name, parameter); targetController.Parameters = targetController.Parameters.Add(name, parameter);
} }
if (merge.deleteAttachedAnimator) if (merge.deleteAttachedAnimator)
@ -169,6 +171,8 @@ namespace nadena.dev.modular_avatar.core.editor
var animator = merge.GetComponent<Animator>(); var animator = merge.GetComponent<Animator>();
if (animator != null) Object.DestroyImmediate(animator); if (animator != null) Object.DestroyImmediate(animator);
} }
Object.DestroyImmediate(merge);
} }
} }
} }

View File

@ -5,6 +5,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using nadena.dev.ndmf; using nadena.dev.ndmf;
using nadena.dev.ndmf.animator; using nadena.dev.ndmf.animator;
using UnityEditor;
using UnityEditor.Animations; using UnityEditor.Animations;
using UnityEngine; using UnityEngine;
using VRC.SDK3.Avatars.Components; using VRC.SDK3.Avatars.Components;
@ -22,6 +23,21 @@ namespace nadena.dev.modular_avatar.core.editor
private VirtualBlendTree _rootBlendTree; private VirtualBlendTree _rootBlendTree;
private HashSet<string> _parameterNames; private HashSet<string> _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) protected override void Execute(ndmf.BuildContext context)
{ {
_asc = context.Extension<AnimatorServicesContext>(); _asc = context.Extension<AnimatorServicesContext>();
@ -59,48 +75,28 @@ namespace nadena.dev.modular_avatar.core.editor
private void ProcessComponent(BuildContext context, ModularAvatarMergeBlendTree component) private void ProcessComponent(BuildContext context, ModularAvatarMergeBlendTree component)
{ {
var stash = context.PluginBuildContext.GetState<RenamedMergeAnimators>(); var virtualBlendTree = _asc.ControllerContext.GetVirtualizedMotion(component);
BlendTree componentBlendTree = component.BlendTree as BlendTree; if (virtualBlendTree == null)
if (componentBlendTree == null)
{ {
ErrorReport.ReportError(Localization.L, ErrorSeverity.NonFatal, "error.merge_blend_tree.missing_tree"); ErrorReport.ReportError(Localization.L, ErrorSeverity.NonFatal, "error.merge_blend_tree.missing_tree");
return; return;
} }
string basePath = null; 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(); var rootBlend = GetRootBlendTree();
rootBlend.Children = rootBlend.Children.Add(new() rootBlend.Children = rootBlend.Children.Add(new()
{ {
Motion = bt, Motion = virtualBlendTree,
DirectBlendParameter = ALWAYS_ONE, DirectBlendParameter = ALWAYS_ONE,
Threshold = 1, Threshold = 1,
CycleOffset = 1, CycleOffset = 1,
TimeScale = 1, TimeScale = 1,
}); });
foreach (var asset in bt.AllReachableNodes()) foreach (var asset in virtualBlendTree.AllReachableNodes())
{ {
if (asset is VirtualBlendTree bt2) if (asset is VirtualBlendTree bt2)
{ {
@ -129,6 +125,8 @@ namespace nadena.dev.modular_avatar.core.editor
} }
} }
} }
Object.DestroyImmediate(component);
} }
private VirtualBlendTree GetRootBlendTree() private VirtualBlendTree GetRootBlendTree()

View File

@ -10,7 +10,6 @@ using nadena.dev.modular_avatar.editor.ErrorReporting;
using nadena.dev.ndmf; using nadena.dev.ndmf;
using nadena.dev.ndmf.animator; using nadena.dev.ndmf.animator;
using UnityEditor; using UnityEditor;
using UnityEditor.Animations;
using UnityEngine; using UnityEngine;
using VRC.SDK3.Avatars.Components; using VRC.SDK3.Avatars.Components;
using VRC.SDK3.Avatars.ScriptableObjects; using VRC.SDK3.Avatars.ScriptableObjects;
@ -55,43 +54,6 @@ namespace nadena.dev.modular_avatar.core.editor
public ImmutableDictionary<string, float> InitialValueOverrides; public ImmutableDictionary<string, float> InitialValueOverrides;
} }
internal class RenamedMergeAnimators
{
public AnimatorServicesContext AnimatorServices;
public Dictionary<ModularAvatarMergeAnimator, VirtualAnimatorController> Controllers = new();
public Dictionary<ModularAvatarMergeBlendTree, VirtualBlendTree> 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 internal class RenameParametersHook
{ {
private const string DEFAULT_EXP_PARAMS_ASSET_GUID = "03a6d797deb62f0429471c4e17ea99a7"; private const string DEFAULT_EXP_PARAMS_ASSET_GUID = "03a6d797deb62f0429471c4e17ea99a7";
@ -200,10 +162,6 @@ namespace nadena.dev.modular_avatar.core.editor
_context = context; _context = context;
var stash = _context.PluginBuildContext.GetState<RenamedMergeAnimators>();
var asc = _context.PluginBuildContext.Extension<AnimatorServicesContext>();
stash.AnimatorServices = asc;
var syncParams = WalkTree(avatar); var syncParams = WalkTree(avatar);
SetExpressionParameters(avatar, syncParams); SetExpressionParameters(avatar, syncParams);
@ -359,6 +317,8 @@ namespace nadena.dev.modular_avatar.core.editor
GameObject obj GameObject obj
) )
{ {
var animServices = _context.PluginBuildContext.Extension<AnimatorServicesContext>();
var paramInfo = ndmf.ParameterInfo.ForContext(_context.PluginBuildContext); var paramInfo = ndmf.ParameterInfo.ForContext(_context.PluginBuildContext);
ImmutableDictionary<string, ParameterInfo> rv = ImmutableDictionary<string, ParameterInfo>.Empty; ImmutableDictionary<string, ParameterInfo> rv = ImmutableDictionary<string, ParameterInfo>.Empty;
@ -406,7 +366,7 @@ namespace nadena.dev.modular_avatar.core.editor
break; break;
} }
case ModularAvatarMergeAnimator merger: case IVirtualizeAnimatorController virtualized:
{ {
var mappings = paramInfo.GetParameterRemappingsAt(obj); var mappings = paramInfo.GetParameterRemappingsAt(obj);
var remap = mappings.SelectMany(item => var remap = mappings.SelectMany(item =>
@ -421,15 +381,10 @@ namespace nadena.dev.modular_avatar.core.editor
); );
}).ToImmutableDictionary(); }).ToImmutableDictionary();
if (merger.animator != null) var controller = animServices.ControllerContext[virtualized];
if (controller != null)
{ {
var stash = _context.PluginBuildContext.GetState<RenamedMergeAnimators>();
var controller = stash.Clone(merger);
ProcessVirtualAnimatorController(controller, remap); ProcessVirtualAnimatorController(controller, remap);
stash.Controllers[merger] = controller;
} }
break; break;
@ -437,15 +392,10 @@ namespace nadena.dev.modular_avatar.core.editor
case ModularAvatarMergeBlendTree merger: case ModularAvatarMergeBlendTree merger:
{ {
var bt = merger.BlendTree as BlendTree; var motion = animServices.ControllerContext.GetVirtualizedMotion(merger);
if (bt != null) if (motion is VirtualBlendTree bt)
{ {
var stash = _context.PluginBuildContext.GetState<RenamedMergeAnimators>(); ProcessBlendtree(bt, paramInfo.GetParameterRemappingsAt(obj));
var virtualbt = stash.Clone(merger);
ProcessBlendtree(virtualbt, paramInfo.GetParameterRemappingsAt(obj));
stash.BlendTrees[merger] = virtualbt;
} }
break; break;

View File

@ -23,9 +23,9 @@
*/ */
using System; using System;
using nadena.dev.ndmf;
using UnityEngine; using UnityEngine;
#if MA_VRCSDK3_AVATARS #if MA_VRCSDK3_AVATARS
using VRC.SDKBase;
#endif #endif
namespace nadena.dev.modular_avatar.core 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. /// inherited by user classes, and will be removed in Modular Avatar 2.0.
/// </summary> /// </summary>
[DefaultExecutionOrder(-9999)] // run before av3emu [DefaultExecutionOrder(-9999)] // run before av3emu
public abstract class AvatarTagComponent : MonoBehaviour, IEditorOnly public abstract class AvatarTagComponent : MonoBehaviour, INDMFEditorOnly
{ {
internal static event Action OnChangeAction; internal static event Action OnChangeAction;

View File

@ -25,6 +25,7 @@
#if MA_VRCSDK3_AVATARS #if MA_VRCSDK3_AVATARS
using System; using System;
using nadena.dev.ndmf.animator;
using UnityEngine; using UnityEngine;
using VRC.SDK3.Avatars.Components; using VRC.SDK3.Avatars.Components;
@ -38,8 +39,11 @@ namespace nadena.dev.modular_avatar.core
[AddComponentMenu("Modular Avatar/MA Merge Animator")] [AddComponentMenu("Modular Avatar/MA Merge Animator")]
[HelpURL("https://modular-avatar.nadena.dev/docs/reference/merge-animator?lang=auto")] [HelpURL("https://modular-avatar.nadena.dev/docs/reference/merge-animator?lang=auto")]
public class ModularAvatarMergeAnimator : AvatarTagComponent public class ModularAvatarMergeAnimator : AvatarTagComponent, IVirtualizeAnimatorController
{ {
internal static Func<ModularAvatarMergeAnimator, object, string> GetMotionBasePathCallback =
(_, _) => "";
public RuntimeAnimatorController animator; public RuntimeAnimatorController animator;
public VRCAvatarDescriptor.AnimLayerType layerType = VRCAvatarDescriptor.AnimLayerType.FX; public VRCAvatarDescriptor.AnimLayerType layerType = VRCAvatarDescriptor.AnimLayerType.FX;
public bool deleteAttachedAnimator; public bool deleteAttachedAnimator;
@ -47,7 +51,7 @@ namespace nadena.dev.modular_avatar.core
public bool matchAvatarWriteDefaults; public bool matchAvatarWriteDefaults;
public AvatarObjectReference relativePathRoot = new AvatarObjectReference(); public AvatarObjectReference relativePathRoot = new AvatarObjectReference();
public int layerPriority = 0; public int layerPriority = 0;
public override void ResolveReferences() public override void ResolveReferences()
{ {
// no-op // no-op
@ -67,6 +71,22 @@ namespace nadena.dev.modular_avatar.core
{ {
deleteAttachedAnimator = true; 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;
} }
} }

View File

@ -1,17 +1,41 @@
#if MA_VRCSDK3_AVATARS #if MA_VRCSDK3_AVATARS
using System;
using API;
using UnityEngine; using UnityEngine;
using Object = UnityEngine.Object;
namespace nadena.dev.modular_avatar.core namespace nadena.dev.modular_avatar.core
{ {
[AddComponentMenu("Modular Avatar/MA Merge Blend Tree")] [AddComponentMenu("Modular Avatar/MA Merge Blend Tree")]
[HelpURL("https://modular-avatar.nadena.dev/docs/reference/merge-blend-tree?lang=auto")] [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<ModularAvatarMergeBlendTree, object, string> GetMotionBasePathCallback
= (_, _) => "";
// We can't actually reference a BlendTree here because it's not available when building a player build // 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 MergeAnimatorPathMode PathMode = MergeAnimatorPathMode.Relative;
public AvatarObjectReference RelativePathRoot = new AvatarObjectReference(); 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;
}
} }
} }

View File

@ -25,13 +25,14 @@ Transform:
m_PrefabInstance: {fileID: 0} m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0} m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 521542568012153140} m_GameObject: {fileID: 521542568012153140}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: -1.6884441, y: 1.1147345, z: -4.914471} m_LocalPosition: {x: -1.6884441, y: 1.1147345, z: -4.914471}
m_LocalScale: {x: 1, y: 1, z: 1} m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: m_Children:
- {fileID: 6479369535091914718} - {fileID: 6479369535091914718}
m_Father: {fileID: 0} m_Father: {fileID: 0}
m_RootOrder: 0
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!114 &8063907918941263456 --- !u!114 &8063907918941263456
MonoBehaviour: MonoBehaviour:
@ -59,6 +60,7 @@ MonoBehaviour:
unityVersion: unityVersion:
portraitCameraPositionOffset: {x: 0, y: 0, z: 0} portraitCameraPositionOffset: {x: 0, y: 0, z: 0}
portraitCameraRotationOffset: {x: 0, y: 1, z: 0, w: -0.00000004371139} portraitCameraRotationOffset: {x: 0, y: 1, z: 0, w: -0.00000004371139}
networkIDs: []
customExpressions: 0 customExpressions: 0
expressionsMenu: {fileID: 0} expressionsMenu: {fileID: 0}
expressionParameters: {fileID: 0} expressionParameters: {fileID: 0}
@ -335,12 +337,13 @@ Transform:
m_PrefabInstance: {fileID: 0} m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0} m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7379231148671166614} m_GameObject: {fileID: 7379231148671166614}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1} m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: [] m_Children: []
m_Father: {fileID: 2616869947308232229} m_Father: {fileID: 2616869947308232229}
m_RootOrder: 0
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!114 &5229589187063892731 --- !u!114 &5229589187063892731
MonoBehaviour: MonoBehaviour:
@ -366,8 +369,12 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: 1bb122659f724ebf85fe095ac02dc339, type: 3} m_Script: {fileID: 11500000, guid: 1bb122659f724ebf85fe095ac02dc339, type: 3}
m_Name: m_Name:
m_EditorClassIdentifier: m_EditorClassIdentifier:
animator: {fileID: 9100000, guid: 22f4686ec8638e243854be6fccb38820, type: 2} animator: {fileID: 9100000, guid: 26ef9d769605a1e46a5ad2768e28ad73, type: 2}
layerType: 5 layerType: 5
deleteAttachedAnimator: 0 deleteAttachedAnimator: 0
pathMode: 0 pathMode: 0
matchAvatarWriteDefaults: 0 matchAvatarWriteDefaults: 0
relativePathRoot:
referencePath:
targetObject: {fileID: 0}
layerPriority: 0

View File

@ -1,7 +1,9 @@
#if MA_VRCSDK3_AVATARS #if MA_VRCSDK3_AVATARS
using System.Linq;
using nadena.dev.modular_avatar.core.editor; using nadena.dev.modular_avatar.core.editor;
using NUnit.Framework; using NUnit.Framework;
using UnityEditor.Animations;
using VRC.SDK3.Avatars.Components; using VRC.SDK3.Avatars.Components;
namespace modular_avatar_tests.SyncedLayerHandling namespace modular_avatar_tests.SyncedLayerHandling
@ -102,6 +104,7 @@ namespace modular_avatar_tests.SyncedLayerHandling
var prefab = CreatePrefab("MergedController_AC2.prefab"); var prefab = CreatePrefab("MergedController_AC2.prefab");
AvatarProcessor.ProcessAvatar(prefab); AvatarProcessor.ProcessAvatar(prefab);
var fx = (AnimatorController) FindFxController(prefab).animatorController;
var mainLayer = findFxLayer(prefab, "main"); var mainLayer = findFxLayer(prefab, "main");
var syncLayer = findFxLayer(prefab, "sync"); var syncLayer = findFxLayer(prefab, "sync");
@ -114,7 +117,7 @@ namespace modular_avatar_tests.SyncedLayerHandling
var layercontrol = overrides[0] as VRCAnimatorLayerControl; var layercontrol = overrides[0] as VRCAnimatorLayerControl;
Assert.NotNull(layercontrol); Assert.NotNull(layercontrol);
Assert.AreEqual(1, layercontrol.layer); Assert.AreEqual(layercontrol.layer, fx.layers.TakeWhile(l => l.name != "main").Count());
} }
} }
} }