mirror of
https://github.com/bdunderscore/modular-avatar.git
synced 2025-04-04 19:49:02 +08:00
refactor: use IVirtualize*
This commit is contained in:
parent
e3a01ff58b
commit
e91b8ab6c3
@ -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<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)
|
||||
{
|
||||
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);
|
||||
basePath = relativePath != "" ? relativePath + "/" : "";
|
||||
|
||||
var animationIndex = new AnimationIndex(new[] { clonedController });
|
||||
animationIndex.RewritePaths(p => p == "" ? relativePath : basePath + p);
|
||||
}
|
||||
else
|
||||
{
|
||||
basePath = "";
|
||||
}
|
||||
var vac = context.PluginBuildContext.Extension<VirtualControllerContext>();
|
||||
|
||||
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<Animator>();
|
||||
if (animator != null) Object.DestroyImmediate(animator);
|
||||
}
|
||||
|
||||
Object.DestroyImmediate(merge);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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<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)
|
||||
{
|
||||
_asc = context.Extension<AnimatorServicesContext>();
|
||||
@ -59,48 +75,28 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
|
||||
private void ProcessComponent(BuildContext context, ModularAvatarMergeBlendTree component)
|
||||
{
|
||||
var stash = context.PluginBuildContext.GetState<RenamedMergeAnimators>();
|
||||
|
||||
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()
|
||||
|
@ -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<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
|
||||
{
|
||||
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<RenamedMergeAnimators>();
|
||||
var asc = _context.PluginBuildContext.Extension<AnimatorServicesContext>();
|
||||
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<AnimatorServicesContext>();
|
||||
|
||||
var paramInfo = ndmf.ParameterInfo.ForContext(_context.PluginBuildContext);
|
||||
|
||||
ImmutableDictionary<string, ParameterInfo> rv = ImmutableDictionary<string, ParameterInfo>.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<RenamedMergeAnimators>();
|
||||
|
||||
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<RenamedMergeAnimators>();
|
||||
|
||||
var virtualbt = stash.Clone(merger);
|
||||
ProcessBlendtree(virtualbt, paramInfo.GetParameterRemappingsAt(obj));
|
||||
|
||||
stash.BlendTrees[merger] = virtualbt;
|
||||
ProcessBlendtree(bt, paramInfo.GetParameterRemappingsAt(obj));
|
||||
}
|
||||
|
||||
break;
|
||||
|
@ -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.
|
||||
/// </summary>
|
||||
[DefaultExecutionOrder(-9999)] // run before av3emu
|
||||
public abstract class AvatarTagComponent : MonoBehaviour, IEditorOnly
|
||||
public abstract class AvatarTagComponent : MonoBehaviour, INDMFEditorOnly
|
||||
{
|
||||
internal static event Action OnChangeAction;
|
||||
|
||||
|
@ -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<ModularAvatarMergeAnimator, object, string> 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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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<ModularAvatarMergeBlendTree, object, string> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user