using System; using System.Collections.Generic; using System.Linq; using UnityEditor; using UnityEditor.Animations; using UnityEngine; using Random = UnityEngine.Random; // ReSharper disable once CheckNamespace namespace AnimatorAsCode.V1 { public static class AacV1 { public static AacFlBase Create(AacConfiguration configuration) { return new AacFlBase(configuration); } internal static AnimatorController NewAnimatorController(AacConfiguration component, string suffix) { return RegisterAnimatorController(component, suffix, new AnimatorController()); } internal static AnimatorController RegisterAnimatorController(AacConfiguration component, string suffix, AnimatorController animatorController) { animatorController.name = "zAutogenerated__" + component.AssetKey + "__" + suffix + "_" + Random.Range(0, Int32.MaxValue); // FIXME animation name conflict animatorController.hideFlags = HideFlags.None; AssetDatabase.AddObjectToAsset(animatorController, component.AssetContainer); return animatorController; } internal static AnimationClip NewClip(AacConfiguration component, string suffix) { return RegisterClip(component, suffix, new AnimationClip()); } internal static AnimationClip RegisterClip(AacConfiguration component, string suffix, AnimationClip clip) { clip.name = "zAutogenerated__" + component.AssetKey + "__" + suffix + "_" + Random.Range(0, Int32.MaxValue); // FIXME animation name conflict clip.hideFlags = HideFlags.None; AssetDatabase.AddObjectToAsset(clip, component.AssetContainer); return clip; } internal static BlendTree NewBlendTreeAsRaw(AacConfiguration component, string suffix) { var clip = new BlendTree(); clip.name = "zAutogenerated__" + component.AssetKey + "__" + suffix + "_" + Random.Range(0, Int32.MaxValue); // FIXME animation name conflict clip.hideFlags = HideFlags.None; AssetDatabase.AddObjectToAsset(clip, component.AssetContainer); return clip; } internal static EditorCurveBinding Binding(AacConfiguration component, Type type, Transform transform, string propertyName) { return new EditorCurveBinding { path = ResolveRelativePath(component.AnimatorRoot, transform), type = type, propertyName = propertyName }; } internal static AnimationCurve OneFrame(float desiredValue) { return AnimationCurve.Constant(0f, 1 / 60f, desiredValue); } internal static AnimationCurve ConstantSeconds(float seconds, float desiredValue) { return AnimationCurve.Constant(0f, seconds, desiredValue); } internal static string ResolveRelativePath(Transform avatar, Transform item) { if (item.parent != avatar && item.parent != null) { return ResolveRelativePath(avatar, item.parent) + "/" + item.name; } return item.name; } internal static EditorCurveBinding ToSubBinding(EditorCurveBinding binding, string suffix) { return new EditorCurveBinding {path = binding.path, type = binding.type, propertyName = binding.propertyName + "." + suffix}; } } public struct AacConfiguration { public string SystemName; // Please consult "MigratingFromV0ToV1.md" on how to migrate this property // public VRCAvatarDescriptor AvatarDescriptor; public Transform AnimatorRoot; public Transform DefaultValueRoot; public AnimatorController AssetContainer; public string AssetKey; public IAacDefaultsProvider DefaultsProvider; public Dictionary AdditionalData; // Nullable public AacConfiguration WithAdditonalData(Type key, object value) { var conf = this; if (AdditionalData == null) conf.AdditionalData = new Dictionary(); conf.AdditionalData[key] = value; return conf; } public bool TryGetAdditionalData(Type key, out object value) { if (AdditionalData != null && AdditionalData.TryGetValue(key, out var result)) { value = result; return true; } value = null; return false; } } public struct AacFlLayer { private readonly AnimatorController _animatorController; private readonly AacConfiguration _configuration; private readonly string _fullLayerName; private readonly AacFlStateMachine _stateMachine; internal AacFlLayer(AnimatorController animatorController, AacConfiguration configuration, AacFlStateMachine stateMachine, string fullLayerName) { _animatorController = animatorController; _configuration = configuration; _fullLayerName = fullLayerName; _stateMachine = stateMachine; } public AacFlState NewState(string name) { var lastState = _stateMachine.LastNodePosition(); var state = _stateMachine.NewState(name, 0, 0).Shift(lastState, 0, 1); return state; } public AacFlState NewState(string name, int x, int y) { return _stateMachine.NewState(name, x, y); } public AacFlStateMachine NewSubStateMachine(string name) { return _stateMachine.NewSubStateMachine(name); } public AacFlStateMachine NewSubStateMachine(string name, int x, int y) { return _stateMachine.NewSubStateMachine(name, x, y); } public AacFlTransition AnyTransitionsTo(AacFlState destination) { return _stateMachine.AnyTransitionsTo(destination); } public AacFlEntryTransition EntryTransitionsTo(AacFlState destination) { return _stateMachine.EntryTransitionsTo(destination); } public AacFlEntryTransition EntryTransitionsTo(AacFlStateMachine destination) { return _stateMachine.EntryTransitionsTo(destination); } public AacFlBoolParameter BoolParameter(string parameterName) => _stateMachine.InternalBackingAnimator().BoolParameter(parameterName); public AacFlBoolParameter TriggerParameterAsBool(string parameterName) => _stateMachine.InternalBackingAnimator().TriggerParameter(parameterName); public AacFlFloatParameter FloatParameter(string parameterName) => _stateMachine.InternalBackingAnimator().FloatParameter(parameterName); public AacFlIntParameter IntParameter(string parameterName) => _stateMachine.InternalBackingAnimator().IntParameter(parameterName); public AacFlBoolParameterGroup BoolParameters(params string[] parameterNames) => _stateMachine.InternalBackingAnimator().BoolParameters(parameterNames); public AacFlBoolParameterGroup TriggerParametersAsBools(params string[] parameterNames) => _stateMachine.InternalBackingAnimator().TriggerParameters(parameterNames); public AacFlFloatParameterGroup FloatParameters(params string[] parameterNames) => _stateMachine.InternalBackingAnimator().FloatParameters(parameterNames); public AacFlIntParameterGroup IntParameters(params string[] parameterNames) => _stateMachine.InternalBackingAnimator().IntParameters(parameterNames); public AacFlBoolParameterGroup BoolParameters(params AacFlBoolParameter[] parameters) => _stateMachine.InternalBackingAnimator().BoolParameters(parameters); public AacFlBoolParameterGroup TriggerParametersAsBools(params AacFlBoolParameter[] parameters) => _stateMachine.InternalBackingAnimator().TriggerParameters(parameters); public AacFlFloatParameterGroup FloatParameters(params AacFlFloatParameter[] parameters) => _stateMachine.InternalBackingAnimator().FloatParameters(parameters); public AacFlIntParameterGroup IntParameters(params AacFlIntParameter[] parameters) => _stateMachine.InternalBackingAnimator().IntParameters(parameters); public AacFlLayer OverrideValue(AacFlBoolParameter toBeForced, bool value) { var parameters = _animatorController.parameters; foreach (var param in parameters) { if (param.name == toBeForced.Name) { param.defaultBool = value; } } _animatorController.parameters = parameters; return this; } public AacFlLayer OverrideValue(AacFlFloatParameter toBeForced, float value) { var parameters = _animatorController.parameters; foreach (var param in parameters) { if (param.name == toBeForced.Name) { param.defaultFloat = value; } } _animatorController.parameters = parameters; return this; } public AacFlLayer OverrideValue(AacFlIntParameter toBeForced, int value) { var parameters = _animatorController.parameters; foreach (var param in parameters) { if (param.name == toBeForced.Name) { param.defaultInt = value; } } _animatorController.parameters = parameters; return this; } public AacFlLayer WithAvatarMask(AvatarMask avatarMask) { var finalFullLayerName = _fullLayerName; _animatorController.layers = _animatorController.layers .Select(layer => { if (layer.name == finalFullLayerName) { layer.avatarMask = avatarMask; } return layer; }) .ToArray(); return this; } public AacFlLayer WithAvatarMaskNoTransforms() { ResolveAvatarMask(new Transform[0]); return this; } public AacFlLayer ResolveAvatarMask(Transform[] paths) { // FIXME: Fragile var avatarMask = new AvatarMask(); avatarMask.name = "zAutogenerated__" + _configuration.AssetKey + "_" + _fullLayerName + "__AvatarMask"; avatarMask.hideFlags = HideFlags.None; if (paths.Length == 0) { avatarMask.transformCount = 1; avatarMask.SetTransformActive(0, false); avatarMask.SetTransformPath(0, "_ignored"); } else { avatarMask.transformCount = paths.Length; for (var index = 0; index < paths.Length; index++) { var transform = paths[index]; avatarMask.SetTransformActive(index, true); avatarMask.SetTransformPath(index, AacV1.ResolveRelativePath(_configuration.AnimatorRoot, transform)); } } for (int i = 0; i < (int) AvatarMaskBodyPart.LastBodyPart; i++) { avatarMask.SetHumanoidBodyPartActive((AvatarMaskBodyPart) i, false); } AssetDatabase.AddObjectToAsset(avatarMask, _animatorController); WithAvatarMask(avatarMask); return this; } public AacFlLayer WithDefaultState(AacFlState newDefaultState) { _stateMachine.WithDefaultState(newDefaultState); return this; } public AacFlStateMachine InternalStateMachine() { return _stateMachine; } } public class AacFlBase { private readonly AacConfiguration _configuration; public AacConfiguration InternalConfiguration() { return _configuration; } internal AacFlBase(AacConfiguration configuration) { _configuration = configuration; } public AacFlClip NewClip() { var clip = AacV1.NewClip(_configuration, Guid.NewGuid().ToString()); return new AacFlClip(_configuration, clip); } public AacFlClip CopyClip(AnimationClip originalClip) { var newClip = UnityEngine.Object.Instantiate(originalClip); var clip = AacV1.RegisterClip(_configuration, Guid.NewGuid().ToString(), newClip); return new AacFlClip(_configuration, clip); } public AacFlNonInitializedBlendTree NewBlendTree() { return new AacFlNonInitializedBlendTree(AacV1.NewBlendTreeAsRaw(_configuration, Guid.NewGuid().ToString())); } public BlendTree NewBlendTreeAsRaw() { return AacV1.NewBlendTreeAsRaw(_configuration, Guid.NewGuid().ToString()); } public AacFlClip NewClip(string name) { var clip = AacV1.NewClip(_configuration, name); return new AacFlClip(_configuration, clip); } public AacFlClip DummyClipLasting(float numberOf, AacFlUnit unit) { var dummyClip = AacV1.NewClip(_configuration, $"D({numberOf} {Enum.GetName(typeof(AacFlUnit), unit)})"); return new AacFlClip(_configuration, dummyClip) .Animating(clip => clip.Animates("_ignored", typeof(GameObject), "m_IsActive") .WithUnit(unit, keyframes => keyframes.Constant(0, 0f).Constant(numberOf, 0f))); } // For backwards compatibility, generates an animation with 2 keyframes that are at a distance of 1 / 60 of 1 frame, // that is 1 / 3600 seconds. // This was due to DummyClipLasting returning a clip with unit conversion applied twice, // resulting in an "undesired" animation which was functional anyways. // Since this dummy clip is used internally on all uninitalized states, // preserve this behaviour so that existing animators don't break due to this change. protected AacFlClip AnomalousSingleKeyframeClip() { var numberOf = 1f; var unit = AacFlUnit.Frames; var dummyClip = AacV1.NewClip(_configuration, $"D({numberOf} {Enum.GetName(typeof(AacFlUnit), unit)})"); var duration = unit == AacFlUnit.Frames ? numberOf / 60f : numberOf; return new AacFlClip(_configuration, dummyClip) .Animating(clip => clip.Animates("_ignored", typeof(GameObject), "m_IsActive") .WithUnit(unit, keyframes => keyframes.Constant(0, 0f).Constant(duration, 0f))); } public AacFlController NewAnimatorController() { var animatorController = AacV1.NewAnimatorController(_configuration, Guid.NewGuid().ToString()); return new AacFlController(_configuration, animatorController, this); } public AacFlController NewAnimatorController(string name) { var animatorController = AacV1.NewAnimatorController(_configuration, name); return new AacFlController(_configuration, animatorController, this); } public AacFlLayer CreateMainArbitraryControllerLayer(AnimatorController controller) => InternalDoCreateLayer(controller, _configuration.DefaultsProvider.ConvertLayerName(_configuration.SystemName)); public AacFlLayer CreateSupportingArbitraryControllerLayer(AnimatorController controller, string suffix) => InternalDoCreateLayer(controller, _configuration.DefaultsProvider.ConvertLayerNameWithSuffix(_configuration.SystemName, suffix)); public AacFlLayer CreateFirstArbitraryControllerLayer(AnimatorController controller) => InternalDoCreateLayer(controller, controller.layers[0].name); public AacFlLayer InternalDoCreateLayer(AnimatorController animator, string layerName) { var ag = new AacAnimatorGenerator(animator, CreateEmptyClip().Clip, _configuration.DefaultsProvider); var machine = ag.CreateOrClearLayerAtSameIndex(layerName, 1f); return new AacFlLayer(animator, _configuration, machine, layerName); } internal AacFlLayer DoCreateLayerWithoutDeleting(AnimatorController animator, string layerName) { var ag = new AacAnimatorGenerator(animator, CreateEmptyClip().Clip, _configuration.DefaultsProvider); var machine = ag.CreateOrClearLayerAtSameIndex(layerName, 1f, null, false); return new AacFlLayer(animator, _configuration, machine, layerName); } private AacFlClip CreateEmptyClip() { var emptyClip = AnomalousSingleKeyframeClip(); return emptyClip; } public void ClearPreviousAssets() { var allSubAssets = AssetDatabase.LoadAllAssetsAtPath(AssetDatabase.GetAssetPath(_configuration.AssetContainer)); foreach (var subAsset in allSubAssets) { if (subAsset.name.StartsWith($"zAutogenerated__{_configuration.AssetKey}__") && (subAsset is AnimationClip || subAsset is BlendTree || subAsset is AvatarMask)) { AssetDatabase.RemoveObjectFromAsset(subAsset); } } } public AacFlNoAnimator NoAnimator() { return new AacFlNoAnimator(); } } public class AacFlNoAnimator { public AacFlFloatParameter FloatParameter(string parameterName) => AacFlFloatParameter.Internally(parameterName); public AacFlIntParameter IntParameter(string parameterName) => AacFlIntParameter.Internally(parameterName); public AacFlBoolParameter BoolParameter(string parameterName) => AacFlBoolParameter.Internally(parameterName); } public class AacFlBlendTree { protected AacFlBlendTree(BlendTree blendTree) { BlendTree = blendTree; } public BlendTree BlendTree { get; } } public class AacFlNonInitializedBlendTree : AacFlBlendTree { public AacFlNonInitializedBlendTree(BlendTree blendTree) : base(blendTree) { } public AacFlBlendTree2D FreeformCartesian(AacFlFloatParameter parameterX, AacFlFloatParameter parameterY) { BlendTree.blendType = BlendTreeType.FreeformCartesian2D; BlendTree.blendParameter = parameterX.Name; BlendTree.blendParameterY = parameterY.Name; return new AacFlBlendTree2D(BlendTree); } public AacFlBlendTree2D FreeformDirectional(AacFlFloatParameter parameterX, AacFlFloatParameter parameterY) { BlendTree.blendType = BlendTreeType.FreeformDirectional2D; BlendTree.blendParameter = parameterX.Name; BlendTree.blendParameterY = parameterY.Name; return new AacFlBlendTree2D(BlendTree); } public AacFlBlendTree2D SimpleDirectional(AacFlFloatParameter parameterX, AacFlFloatParameter parameterY) { BlendTree.blendType = BlendTreeType.SimpleDirectional2D; BlendTree.blendParameter = parameterX.Name; BlendTree.blendParameterY = parameterY.Name; return new AacFlBlendTree2D(BlendTree); } public AacFlBlendTree1D Simple(AacFlFloatParameter parameter) { BlendTree.blendType = BlendTreeType.Simple1D; BlendTree.blendParameter = parameter.Name; BlendTree.useAutomaticThresholds = false; return new AacFlBlendTree1D(BlendTree); } public AacFlBlendTreeDirect Direct() { BlendTree.blendType = BlendTreeType.Direct; return new AacFlBlendTreeDirect(BlendTree); } } public class AacFlBlendTree2D : AacFlBlendTree { public AacFlBlendTree2D(BlendTree blendTree) : base(blendTree) { } public AacFlBlendTree2D WithAnimation(AacFlBlendTree blendTree, Vector2 pos) { return WithAnimation(blendTree.BlendTree, pos); } public AacFlBlendTree2D WithAnimation(AacFlBlendTree blendTree, float x, float y) { return WithAnimation(blendTree.BlendTree, x, y); } public AacFlBlendTree2D WithAnimation(AacFlClip clip, Vector2 pos) { return WithAnimation(clip.Clip, pos); } public AacFlBlendTree2D WithAnimation(AacFlClip clip, float x, float y) { return WithAnimation(clip.Clip, x, y); } public AacFlBlendTree2D WithAnimation(Motion motion, Vector2 pos) { return WithAnimation(motion, pos.x, pos.y); } public AacFlBlendTree2D WithAnimation(Motion motion, float x, float y) { var children = BlendTree.children ?? new ChildMotion[0]; var childrenList = children.ToList(); childrenList.Add(new ChildMotion { motion = motion, position = new Vector2(x, y), timeScale = 1f }); BlendTree.children = childrenList.ToArray(); return this; } } public class AacFlBlendTree1D : AacFlBlendTree { public AacFlBlendTree1D(BlendTree blendTree) : base(blendTree) { } public AacFlBlendTree1D WithAnimation(AacFlClip clip, float threshold) { return WithAnimation(clip.Clip, threshold); } public AacFlBlendTree1D WithAnimation(AacFlBlendTree blendTree, float threshold) { return WithAnimation(blendTree.BlendTree, threshold); } public AacFlBlendTree1D WithAnimation(Motion motion, float threshold) { var children = BlendTree.children ?? new ChildMotion[0]; var childrenList = children.ToList(); childrenList.Add(new ChildMotion { motion = motion, threshold = threshold, timeScale = 1f }); BlendTree.children = childrenList.ToArray(); return this; } } public class AacFlBlendTreeDirect : AacFlBlendTree { public AacFlBlendTreeDirect(BlendTree blendTree) : base(blendTree) { } public AacFlBlendTreeDirect WithAnimation(AacFlClip clip, AacFlFloatParameter parameter) { return WithAnimation(clip.Clip, parameter); } public AacFlBlendTreeDirect WithAnimation(AacFlBlendTree blendTree, AacFlFloatParameter parameter) { return WithAnimation(blendTree.BlendTree, parameter); } public AacFlBlendTreeDirect WithAnimation(Motion motion, AacFlFloatParameter parameter) { var children = BlendTree.children ?? new ChildMotion[0]; var childrenList = children.ToList(); childrenList.Add(new ChildMotion { motion = motion, directBlendParameter = parameter.Name, timeScale = 1f }); BlendTree.children = childrenList.ToArray(); return this; } } public class AacFlController { public AnimatorController AnimatorController; private readonly AacConfiguration _configuration; private readonly AacFlBase _base; public AacFlController(AacConfiguration configuration, AnimatorController animatorController, AacFlBase originalBase) { AnimatorController = animatorController; _configuration = configuration; _base = originalBase; } public AacFlLayer CreateLayer(string suffix) => _base.DoCreateLayerWithoutDeleting(AnimatorController, _configuration.DefaultsProvider.ConvertLayerNameWithSuffix(_configuration.SystemName, suffix)); public AacFlLayer CreateLayer() => _base.DoCreateLayerWithoutDeleting(AnimatorController, _configuration.SystemName); } public class AacAnimatorRemoval { private readonly AnimatorController _animatorController; public AacAnimatorRemoval(AnimatorController animatorController) { _animatorController = animatorController; } public void RemoveLayer(string layerName) { var index = FindIndexOf(layerName); if (index == -1) return; _animatorController.RemoveLayer(index); } private int FindIndexOf(string layerName) { return _animatorController.layers.ToList().FindIndex(layer => layer.name == layerName); } } public class AacAnimatorGenerator { private readonly AnimatorController _animatorController; private readonly AnimationClip _emptyClip; private readonly IAacDefaultsProvider _defaultsProvider; internal AacAnimatorGenerator(AnimatorController animatorController, AnimationClip emptyClip, IAacDefaultsProvider defaultsProvider) { _animatorController = animatorController; _emptyClip = emptyClip; _defaultsProvider = defaultsProvider; } internal void CreateParamsAsNeeded(params AacFlParameter[] parameters) { foreach (var parameter in parameters) { switch (parameter) { case AacFlIntParameter _: CreateParamIfNotExists(parameter.Name, AnimatorControllerParameterType.Int); break; case AacFlFloatParameter _: CreateParamIfNotExists(parameter.Name, AnimatorControllerParameterType.Float); break; case AacFlBoolParameter _: CreateParamIfNotExists(parameter.Name, AnimatorControllerParameterType.Bool); break; } } } internal void CreateTriggerParamsAsNeeded(params AacFlBoolParameter[] parameters) { foreach (var parameter in parameters) { CreateParamIfNotExists(parameter.Name, AnimatorControllerParameterType.Trigger); } } private void CreateParamIfNotExists(string paramName, AnimatorControllerParameterType type) { if (_animatorController.parameters.FirstOrDefault(param => param.name == paramName) == null) { _animatorController.AddParameter(paramName, type); } } // DEPRECATED: This causes the editor window to glitch by deselecting, which is jarring for experimentation internal AacFlStateMachine CreateOrRemakeLayerAtSameIndex(string layerName, float weightWhenCreating, AvatarMask maskWhenCreating = null) { var originalIndexToPreserveOrdering = FindIndexOf(layerName); if (originalIndexToPreserveOrdering != -1) { _animatorController.RemoveLayer(originalIndexToPreserveOrdering); } AddLayerWithWeight(layerName, weightWhenCreating, maskWhenCreating); if (originalIndexToPreserveOrdering != -1) { var items = _animatorController.layers.ToList(); var last = items[items.Count - 1]; items.RemoveAt(items.Count - 1); items.Insert(originalIndexToPreserveOrdering, last); _animatorController.layers = items.ToArray(); } var layer = TryGetLayer(layerName); var machinist = new AacFlStateMachine(layer.stateMachine, _emptyClip, new AacBackingAnimator(this), _defaultsProvider); return machinist .WithAnyStatePosition(0, 7) .WithEntryPosition(0, -1) .WithExitPosition(7, -1); } internal AacFlStateMachine CreateOrClearLayerAtSameIndex(string layerName, float weightWhenCreating, AvatarMask maskWhenCreating = null, bool allowDeletion = true) { var originalIndexToPreserveOrdering = FindIndexOf(layerName); if (originalIndexToPreserveOrdering != -1) { if (!allowDeletion) { throw new InvalidOperationException($"Cannot create layer with name {layerName} as it already exists. When creating layers using NewAnimatorController, you may only use unique names."); } RecursivelyClearChildrenMachines(_animatorController.layers[originalIndexToPreserveOrdering].stateMachine); _animatorController.layers[originalIndexToPreserveOrdering].stateMachine.stateMachines = new ChildAnimatorStateMachine[0]; _animatorController.layers[originalIndexToPreserveOrdering].stateMachine.states = new ChildAnimatorState[0]; _animatorController.layers[originalIndexToPreserveOrdering].stateMachine.entryTransitions = new AnimatorTransition[0]; _animatorController.layers[originalIndexToPreserveOrdering].stateMachine.anyStateTransitions = new AnimatorStateTransition[0]; } else { _animatorController.AddLayer(_animatorController.MakeUniqueLayerName(layerName)); originalIndexToPreserveOrdering = _animatorController.layers.Length - 1; } var layers = _animatorController.layers; layers[originalIndexToPreserveOrdering].avatarMask = maskWhenCreating; layers[originalIndexToPreserveOrdering].defaultWeight = weightWhenCreating; _animatorController.layers = layers; var layer = TryGetLayer(layerName); var machinist = new AacFlStateMachine(layer.stateMachine, _emptyClip, new AacBackingAnimator(this), _defaultsProvider); _defaultsProvider.ConfigureStateMachine(layer.stateMachine); return machinist; } private void RecursivelyClearChildrenMachines(AnimatorStateMachine parentMachine) { // TODO: RemoveStateMachine might already be recursive foreach (var childStateMachineHolder in parentMachine.stateMachines) { RecursivelyClearChildrenMachines(childStateMachineHolder.stateMachine); parentMachine.RemoveStateMachine(childStateMachineHolder.stateMachine); } } private int FindIndexOf(string layerName) { return _animatorController.layers.ToList().FindIndex(layer1 => layer1.name == layerName); } private AnimatorControllerLayer TryGetLayer(string layerName) { return _animatorController.layers.FirstOrDefault(it => it.name == layerName); } private void AddLayerWithWeight(string layerName, float weightWhenCreating, AvatarMask maskWhenCreating) { _animatorController.AddLayer(_animatorController.MakeUniqueLayerName(layerName)); var mutatedLayers = _animatorController.layers; mutatedLayers[mutatedLayers.Length - 1].defaultWeight = weightWhenCreating; mutatedLayers[mutatedLayers.Length - 1].avatarMask = maskWhenCreating; _animatorController.layers = mutatedLayers; } } }