feat: add merge blend tree component (#572)
* chore: refactor state machine clone logic out as a separate class * feat: add layer priority and relative path root options to Merge Animator * feat: add Merge Blend Tree component * chore: adjust NDMF dependency * docs: update merge-animator docs * docs: merge blend tree docs
4
.github/ProjectRoot/vpm-manifest-2019.json
vendored
@ -4,7 +4,7 @@
|
||||
"version": "3.4.2"
|
||||
},
|
||||
"nadena.dev.ndmf": {
|
||||
"version": "1.3.0-alpha.0"
|
||||
"version": "1.3.0-alpha.1"
|
||||
}
|
||||
},
|
||||
"locked": {
|
||||
@ -19,7 +19,7 @@
|
||||
"dependencies": {}
|
||||
},
|
||||
"nadena.dev.ndmf": {
|
||||
"version": "1.3.0-alpha.0"
|
||||
"version": "1.3.0-alpha.1"
|
||||
}
|
||||
}
|
||||
}
|
4
.github/ProjectRoot/vpm-manifest-2022.json
vendored
@ -4,7 +4,7 @@
|
||||
"version": "3.5.0"
|
||||
},
|
||||
"nadena.dev.ndmf": {
|
||||
"version": "1.3.0-alpha.0"
|
||||
"version": "1.3.0-alpha.1"
|
||||
}
|
||||
},
|
||||
"locked": {
|
||||
@ -19,7 +19,7 @@
|
||||
"dependencies": {}
|
||||
},
|
||||
"nadena.dev.ndmf": {
|
||||
"version": "1.3.0-alpha.0"
|
||||
"version": "1.3.0-alpha.1"
|
||||
}
|
||||
}
|
||||
}
|
210
Editor/Animation/DeepClone.cs
Normal file
@ -0,0 +1,210 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using nadena.dev.modular_avatar.core.editor;
|
||||
using nadena.dev.ndmf;
|
||||
using UnityEditor;
|
||||
using UnityEditor.Animations;
|
||||
using UnityEngine;
|
||||
using BuildContext = nadena.dev.ndmf.BuildContext;
|
||||
|
||||
namespace nadena.dev.modular_avatar.animation
|
||||
{
|
||||
using UnityObject = UnityEngine.Object;
|
||||
|
||||
internal class DeepClone
|
||||
{
|
||||
private bool _isSaved;
|
||||
private UnityObject _combined;
|
||||
|
||||
public AnimatorOverrideController OverrideController { get; set; }
|
||||
|
||||
public DeepClone(BuildContext context)
|
||||
{
|
||||
_isSaved = context.AssetContainer != null;
|
||||
_combined = context.AssetContainer;
|
||||
}
|
||||
|
||||
public T DoClone<T>(T original,
|
||||
string basePath = null,
|
||||
Dictionary<UnityObject, UnityObject> cloneMap = null
|
||||
) where T : UnityObject
|
||||
{
|
||||
if (original == null) return null;
|
||||
if (cloneMap == null) cloneMap = new Dictionary<UnityObject, UnityObject>();
|
||||
|
||||
Func<UnityObject, UnityObject> visitor = null;
|
||||
if (basePath != null)
|
||||
{
|
||||
visitor = o => CloneWithPathMapping(o, basePath);
|
||||
}
|
||||
|
||||
// We want to avoid trying to copy assets not part of the animation system (eg - textures, meshes,
|
||||
// MonoScripts...), so check for the types we care about here
|
||||
switch (original)
|
||||
{
|
||||
// Any object referenced by an animator that we intend to mutate needs to be listed here.
|
||||
case Motion _:
|
||||
case AnimatorController _:
|
||||
case AnimatorState _:
|
||||
case AnimatorStateMachine _:
|
||||
case AnimatorTransitionBase _:
|
||||
case StateMachineBehaviour _:
|
||||
break; // We want to clone these types
|
||||
|
||||
// Leave textures, materials, and script definitions alone
|
||||
case Texture2D _:
|
||||
case MonoScript _:
|
||||
case Material _:
|
||||
return original;
|
||||
|
||||
// Also avoid copying unknown scriptable objects.
|
||||
// This ensures compatibility with e.g. avatar remote, which stores state information in a state
|
||||
// behaviour referencing a custom ScriptableObject
|
||||
case ScriptableObject _:
|
||||
return original;
|
||||
|
||||
default:
|
||||
throw new Exception($"Unknown type referenced from animator: {original.GetType()}");
|
||||
}
|
||||
|
||||
// When using AnimatorOverrideController, replace the original AnimationClip based on AnimatorOverrideController.
|
||||
if (OverrideController != null && original is AnimationClip srcClip)
|
||||
{
|
||||
T overrideClip = OverrideController[srcClip] as T;
|
||||
if (overrideClip != null)
|
||||
{
|
||||
original = overrideClip;
|
||||
}
|
||||
}
|
||||
|
||||
if (cloneMap.ContainsKey(original))
|
||||
{
|
||||
return (T) cloneMap[original];
|
||||
}
|
||||
|
||||
var obj = visitor?.Invoke(original);
|
||||
if (obj != null)
|
||||
{
|
||||
cloneMap[original] = obj;
|
||||
if (obj != original)
|
||||
{
|
||||
ObjectRegistry.RegisterReplacedObject(original, obj);
|
||||
}
|
||||
return (T) obj;
|
||||
}
|
||||
|
||||
var ctor = original.GetType().GetConstructor(Type.EmptyTypes);
|
||||
if (ctor == null || original is ScriptableObject)
|
||||
{
|
||||
obj = UnityObject.Instantiate(original);
|
||||
}
|
||||
else
|
||||
{
|
||||
obj = (T) ctor.Invoke(Array.Empty<object>());
|
||||
EditorUtility.CopySerialized(original, obj);
|
||||
}
|
||||
|
||||
cloneMap[original] = obj;
|
||||
ObjectRegistry.RegisterReplacedObject(original, obj);
|
||||
|
||||
if (_isSaved)
|
||||
{
|
||||
AssetDatabase.AddObjectToAsset(obj, _combined);
|
||||
}
|
||||
|
||||
SerializedObject so = new SerializedObject(obj);
|
||||
SerializedProperty prop = so.GetIterator();
|
||||
|
||||
bool enterChildren = true;
|
||||
while (prop.Next(enterChildren))
|
||||
{
|
||||
enterChildren = true;
|
||||
switch (prop.propertyType)
|
||||
{
|
||||
case SerializedPropertyType.ObjectReference:
|
||||
{
|
||||
var newObj = DoClone(prop.objectReferenceValue, basePath, cloneMap);
|
||||
prop.objectReferenceValue = newObj;
|
||||
break;
|
||||
}
|
||||
// Iterating strings can get super slow...
|
||||
case SerializedPropertyType.String:
|
||||
enterChildren = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
so.ApplyModifiedPropertiesWithoutUndo();
|
||||
|
||||
return (T) obj;
|
||||
}
|
||||
|
||||
private UnityObject CloneWithPathMapping(UnityObject o, string basePath)
|
||||
{
|
||||
if (o is AnimationClip clip)
|
||||
{
|
||||
// We'll always rebase if the asset is non-persistent, because we can't reference a nonpersistent asset
|
||||
// from a persistent asset. If the asset is persistent, skip cases where path editing isn't required,
|
||||
// or where this is one of the special VRC proxy animations.
|
||||
if (EditorUtility.IsPersistent(o) && (basePath == "" || Util.IsProxyAnimation(clip))) return clip;
|
||||
|
||||
AnimationClip newClip = new AnimationClip();
|
||||
newClip.name = "rebased " + clip.name;
|
||||
if (_isSaved)
|
||||
{
|
||||
AssetDatabase.AddObjectToAsset(newClip, _combined);
|
||||
}
|
||||
|
||||
foreach (var binding in AnimationUtility.GetCurveBindings(clip))
|
||||
{
|
||||
var newBinding = binding;
|
||||
newBinding.path = MapPath(binding, basePath);
|
||||
newClip.SetCurve(newBinding.path, newBinding.type, newBinding.propertyName,
|
||||
AnimationUtility.GetEditorCurve(clip, binding));
|
||||
}
|
||||
|
||||
foreach (var objBinding in AnimationUtility.GetObjectReferenceCurveBindings(clip))
|
||||
{
|
||||
var newBinding = objBinding;
|
||||
newBinding.path = MapPath(objBinding, basePath);
|
||||
AnimationUtility.SetObjectReferenceCurve(newClip, newBinding,
|
||||
AnimationUtility.GetObjectReferenceCurve(clip, objBinding));
|
||||
}
|
||||
|
||||
newClip.wrapMode = clip.wrapMode;
|
||||
newClip.legacy = clip.legacy;
|
||||
newClip.frameRate = clip.frameRate;
|
||||
newClip.localBounds = clip.localBounds;
|
||||
AnimationUtility.SetAnimationClipSettings(newClip, AnimationUtility.GetAnimationClipSettings(clip));
|
||||
|
||||
return newClip;
|
||||
}
|
||||
else if (o is Texture)
|
||||
{
|
||||
return o;
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static string MapPath(EditorCurveBinding binding, string basePath)
|
||||
{
|
||||
if (binding.type == typeof(Animator) && binding.path == "")
|
||||
{
|
||||
return "";
|
||||
}
|
||||
else
|
||||
{
|
||||
var newPath = binding.path == "" ? basePath : basePath + binding.path;
|
||||
if (newPath.EndsWith("/"))
|
||||
{
|
||||
newPath = newPath.Substring(0, newPath.Length - 1);
|
||||
}
|
||||
|
||||
return newPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
3
Editor/Animation/DeepClone.cs.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b33090a3e763464ab05f3efe07e0cbd3
|
||||
timeCreated: 1703148770
|
@ -25,6 +25,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using nadena.dev.modular_avatar.animation;
|
||||
using nadena.dev.modular_avatar.editor.ErrorReporting;
|
||||
using UnityEditor;
|
||||
using UnityEditor.Animations;
|
||||
@ -43,6 +44,8 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
private readonly AnimatorController _combined;
|
||||
private bool isSaved;
|
||||
|
||||
private DeepClone _deepClone;
|
||||
|
||||
private AnimatorOverrideController _overrideController;
|
||||
|
||||
private List<AnimatorControllerLayer> _layers = new List<AnimatorControllerLayer>();
|
||||
@ -65,6 +68,8 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
_combined = context.CreateAnimator();
|
||||
isSaved = !string.IsNullOrEmpty(AssetDatabase.GetAssetPath(_combined));
|
||||
_combined.name = assetName;
|
||||
|
||||
_deepClone = new DeepClone(context.PluginBuildContext);
|
||||
}
|
||||
|
||||
public AnimatorController Finish()
|
||||
@ -74,7 +79,7 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
return _combined;
|
||||
}
|
||||
|
||||
public void AddController(string basePath, AnimatorController controller, bool? writeDefaults)
|
||||
public void AddController(string basePath, AnimatorController controller, bool? writeDefaults, bool forceFirstLayerWeight = false)
|
||||
{
|
||||
controllerBaseLayer = _layers.Count;
|
||||
_cloneMap = new Dictionary<Object, Object>();
|
||||
@ -103,6 +108,11 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
foreach (var layer in layers)
|
||||
{
|
||||
insertLayer(basePath, layer, first, writeDefaults, layers);
|
||||
if (first && forceFirstLayerWeight)
|
||||
{
|
||||
_layers[_layers.Count - 1].defaultWeight = 1;
|
||||
}
|
||||
|
||||
first = false;
|
||||
}
|
||||
}
|
||||
@ -112,7 +122,7 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
{
|
||||
AnimatorController controller = overrideController.runtimeAnimatorController as AnimatorController;
|
||||
if (controller == null) return;
|
||||
_overrideController = overrideController;
|
||||
_deepClone.OverrideController = overrideController;
|
||||
try
|
||||
{
|
||||
this.AddController(basePath, controller, writeDefaults);
|
||||
@ -163,8 +173,7 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
{
|
||||
for (int i = 0; i < overrideBehaviors.Length; i++)
|
||||
{
|
||||
overrideBehaviors[i] = deepClone(overrideBehaviors[i], x => x,
|
||||
new Dictionary<Object, Object>());
|
||||
overrideBehaviors[i] = _deepClone.DoClone(overrideBehaviors[i]);
|
||||
AdjustBehavior(overrideBehaviors[i]);
|
||||
}
|
||||
|
||||
@ -242,7 +251,7 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
return asm;
|
||||
}
|
||||
|
||||
asm = deepClone(layerStateMachine, (obj) => customClone(obj, basePath), _cloneMap);
|
||||
asm = _deepClone.DoClone(layerStateMachine, basePath, _cloneMap);
|
||||
|
||||
foreach (var state in WalkAllStates(asm))
|
||||
{
|
||||
@ -271,170 +280,5 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
private static string MapPath(EditorCurveBinding binding, string basePath)
|
||||
{
|
||||
if (binding.type == typeof(Animator) && binding.path == "")
|
||||
{
|
||||
return "";
|
||||
}
|
||||
else
|
||||
{
|
||||
var newPath = binding.path == "" ? basePath : basePath + binding.path;
|
||||
if (newPath.EndsWith("/"))
|
||||
{
|
||||
newPath = newPath.Substring(0, newPath.Length - 1);
|
||||
}
|
||||
|
||||
return newPath;
|
||||
}
|
||||
}
|
||||
|
||||
private Object customClone(Object o, string basePath)
|
||||
{
|
||||
if (o is AnimationClip clip)
|
||||
{
|
||||
if (basePath == "" || Util.IsProxyAnimation(clip)) return clip;
|
||||
|
||||
AnimationClip newClip = new AnimationClip();
|
||||
newClip.name = "rebased " + clip.name;
|
||||
if (isSaved)
|
||||
{
|
||||
AssetDatabase.AddObjectToAsset(newClip, _combined);
|
||||
}
|
||||
|
||||
foreach (var binding in AnimationUtility.GetCurveBindings(clip))
|
||||
{
|
||||
var newBinding = binding;
|
||||
newBinding.path = MapPath(binding, basePath);
|
||||
newClip.SetCurve(newBinding.path, newBinding.type, newBinding.propertyName,
|
||||
AnimationUtility.GetEditorCurve(clip, binding));
|
||||
}
|
||||
|
||||
foreach (var objBinding in AnimationUtility.GetObjectReferenceCurveBindings(clip))
|
||||
{
|
||||
var newBinding = objBinding;
|
||||
newBinding.path = MapPath(objBinding, basePath);
|
||||
AnimationUtility.SetObjectReferenceCurve(newClip, newBinding,
|
||||
AnimationUtility.GetObjectReferenceCurve(clip, objBinding));
|
||||
}
|
||||
|
||||
newClip.wrapMode = clip.wrapMode;
|
||||
newClip.legacy = clip.legacy;
|
||||
newClip.frameRate = clip.frameRate;
|
||||
newClip.localBounds = clip.localBounds;
|
||||
AnimationUtility.SetAnimationClipSettings(newClip, AnimationUtility.GetAnimationClipSettings(clip));
|
||||
|
||||
return newClip;
|
||||
}
|
||||
else if (o is Texture)
|
||||
{
|
||||
return o;
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private T deepClone<T>(T original,
|
||||
Func<Object, Object> visitor,
|
||||
Dictionary<Object, Object> cloneMap
|
||||
) where T : Object
|
||||
{
|
||||
if (original == null) return null;
|
||||
|
||||
// We want to avoid trying to copy assets not part of the animation system (eg - textures, meshes,
|
||||
// MonoScripts...), so check for the types we care about here
|
||||
switch (original)
|
||||
{
|
||||
// Any object referenced by an animator that we intend to mutate needs to be listed here.
|
||||
case Motion _:
|
||||
case AnimatorController _:
|
||||
case AnimatorState _:
|
||||
case AnimatorStateMachine _:
|
||||
case AnimatorTransitionBase _:
|
||||
case StateMachineBehaviour _:
|
||||
break; // We want to clone these types
|
||||
|
||||
// Leave textures, materials, and script definitions alone
|
||||
case Texture2D _:
|
||||
case MonoScript _:
|
||||
case Material _:
|
||||
return original;
|
||||
|
||||
// Also avoid copying unknown scriptable objects.
|
||||
// This ensures compatibility with e.g. avatar remote, which stores state information in a state
|
||||
// behaviour referencing a custom ScriptableObject
|
||||
case ScriptableObject _:
|
||||
return original;
|
||||
|
||||
default:
|
||||
throw new Exception($"Unknown type referenced from animator: {original.GetType()}");
|
||||
}
|
||||
|
||||
// When using AnimatorOverrideController, replace the original AnimationClip based on AnimatorOverrideController.
|
||||
if (_overrideController != null && original is AnimationClip srcClip)
|
||||
{
|
||||
T overrideClip = _overrideController[srcClip] as T;
|
||||
if (overrideClip != null)
|
||||
{
|
||||
original = overrideClip;
|
||||
}
|
||||
}
|
||||
|
||||
if (cloneMap.ContainsKey(original))
|
||||
{
|
||||
return (T) cloneMap[original];
|
||||
}
|
||||
|
||||
var obj = visitor(original);
|
||||
if (obj != null)
|
||||
{
|
||||
cloneMap[original] = obj;
|
||||
return (T) obj;
|
||||
}
|
||||
|
||||
var ctor = original.GetType().GetConstructor(Type.EmptyTypes);
|
||||
if (ctor == null || original is ScriptableObject)
|
||||
{
|
||||
obj = Object.Instantiate(original);
|
||||
}
|
||||
else
|
||||
{
|
||||
obj = (T) ctor.Invoke(Array.Empty<object>());
|
||||
EditorUtility.CopySerialized(original, obj);
|
||||
}
|
||||
|
||||
cloneMap[original] = obj;
|
||||
|
||||
if (isSaved)
|
||||
{
|
||||
AssetDatabase.AddObjectToAsset(obj, _combined);
|
||||
}
|
||||
|
||||
SerializedObject so = new SerializedObject(obj);
|
||||
SerializedProperty prop = so.GetIterator();
|
||||
|
||||
bool enterChildren = true;
|
||||
while (prop.Next(enterChildren))
|
||||
{
|
||||
enterChildren = true;
|
||||
switch (prop.propertyType)
|
||||
{
|
||||
case SerializedPropertyType.ObjectReference:
|
||||
prop.objectReferenceValue = deepClone(prop.objectReferenceValue, visitor, cloneMap);
|
||||
break;
|
||||
// Iterating strings can get super slow...
|
||||
case SerializedPropertyType.String:
|
||||
enterChildren = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
so.ApplyModifiedPropertiesWithoutUndo();
|
||||
|
||||
return (T) obj;
|
||||
}
|
||||
}
|
||||
}
|
@ -18,7 +18,9 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
prop_layerType,
|
||||
prop_deleteAttachedAnimator,
|
||||
prop_pathMode,
|
||||
prop_matchAvatarWriteDefaults;
|
||||
prop_matchAvatarWriteDefaults,
|
||||
prop_relativePathRoot,
|
||||
prop_layerPriority;
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
@ -29,6 +31,9 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
prop_pathMode = serializedObject.FindProperty(nameof(ModularAvatarMergeAnimator.pathMode));
|
||||
prop_matchAvatarWriteDefaults =
|
||||
serializedObject.FindProperty(nameof(ModularAvatarMergeAnimator.matchAvatarWriteDefaults));
|
||||
prop_relativePathRoot =
|
||||
serializedObject.FindProperty(nameof(ModularAvatarMergeAnimator.relativePathRoot));
|
||||
prop_layerPriority = serializedObject.FindProperty(nameof(ModularAvatarMergeAnimator.layerPriority));
|
||||
}
|
||||
|
||||
protected override void OnInnerInspectorGUI()
|
||||
@ -39,6 +44,9 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
EditorGUILayout.PropertyField(prop_layerType, G("merge_animator.layer_type"));
|
||||
EditorGUILayout.PropertyField(prop_deleteAttachedAnimator, G("merge_animator.delete_attached_animator"));
|
||||
EditorGUILayout.PropertyField(prop_pathMode, G("merge_animator.path_mode"));
|
||||
if (prop_pathMode.enumValueIndex == (int) MergeAnimatorPathMode.Relative)
|
||||
EditorGUILayout.PropertyField(prop_relativePathRoot, G("merge_animator.relative_path_root"));
|
||||
EditorGUILayout.PropertyField(prop_layerPriority, G("merge_animator.layer_priority"));
|
||||
EditorGUILayout.PropertyField(prop_matchAvatarWriteDefaults,
|
||||
G("merge_animator.match_avatar_write_defaults"));
|
||||
|
||||
|
37
Editor/Inspector/MergeBlendTreeEditor.cs
Normal file
@ -0,0 +1,37 @@
|
||||
using UnityEditor;
|
||||
using UnityEditor.Animations;
|
||||
using static nadena.dev.modular_avatar.core.editor.Localization;
|
||||
|
||||
namespace nadena.dev.modular_avatar.core.editor
|
||||
{
|
||||
[CustomEditor(typeof(ModularAvatarMergeBlendTree))]
|
||||
internal class MergeBlendTreeEditor : MAEditorBase
|
||||
{
|
||||
private SerializedProperty _blendTree;
|
||||
private SerializedProperty _pathMode;
|
||||
private SerializedProperty _relativePathRoot;
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
_blendTree = serializedObject.FindProperty(nameof(ModularAvatarMergeBlendTree.BlendTree));
|
||||
_pathMode = serializedObject.FindProperty(nameof(ModularAvatarMergeBlendTree.PathMode));
|
||||
_relativePathRoot = serializedObject.FindProperty(nameof(ModularAvatarMergeBlendTree.RelativePathRoot));
|
||||
}
|
||||
|
||||
protected override void OnInnerInspectorGUI()
|
||||
{
|
||||
serializedObject.Update();
|
||||
|
||||
EditorGUILayout.ObjectField(_blendTree, typeof(BlendTree), G("merge_blend_tree.blend_tree"));
|
||||
EditorGUILayout.PropertyField(_pathMode, G("merge_blend_tree.path_mode"));
|
||||
if (_pathMode.enumValueIndex == (int) MergeAnimatorPathMode.Relative)
|
||||
{
|
||||
EditorGUILayout.PropertyField(_relativePathRoot, G("merge_blend_tree.relative_path_root"));
|
||||
}
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
|
||||
ShowLanguageUI();
|
||||
}
|
||||
}
|
||||
}
|
3
Editor/Inspector/MergeBlendTreeEditor.cs.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e58c72a0021d43e1a17f551d7d0de677
|
||||
timeCreated: 1703156816
|
@ -51,7 +51,11 @@
|
||||
"merge_animator.path_mode.tooltip": "How to interpret paths in animations. Using relative mode lets you record animations from an animator on this object.",
|
||||
"merge_animator.match_avatar_write_defaults": "Match Avatar Write Defaults",
|
||||
"merge_animator.match_avatar_write_defaults.tooltip": "Match the write defaults setting used on the avatar's animator. If the avatar's write defaults settings are inconsistent, the settings on the animator will be left alone.",
|
||||
|
||||
"merge_animator.relative_path_root": "Relative Path Root",
|
||||
"merge_animator.relative_path_root.tooltip": "The root object to use when interpreting relative paths. If not specified, the object this component is attached to will be used.",
|
||||
"merge_animator.layer_priority": "Layer Priority",
|
||||
"merge_animator.layer_priority.tooltip": "Controls the order in which layers are merged into the animator - lower to higher. Negative values are merged before the original layer on the avatar descriptor, while zero and positive numbers are merged after.",
|
||||
|
||||
"merge_armature.lockmode": "Position sync mode",
|
||||
"merge_armature.lockmode.not_locked.title": "Not locked",
|
||||
"merge_armature.lockmode.not_locked.body": "Merged armature does not sync its position with the base avatar.",
|
||||
@ -67,6 +71,12 @@
|
||||
"merge_armature.reset_pos.heuristic_scale": "Adjust outfit overall scale to match base avatar",
|
||||
"merge_armature.reset_pos.heuristic_scale.tooltip": "Will set the overall scale of the outfit as a whole based on armspan measurements. Recommended for setting up outfits.",
|
||||
|
||||
"merge_blend_tree.blend_tree": "Blend Tree",
|
||||
"merge_blend_tree.path_mode": "Path Mode",
|
||||
"merge_blend_tree.path_mode.tooltip": "How to interpret paths in animations. Using relative mode lets you record animations from an animator on this object.",
|
||||
"merge_blend_tree.relative_path_root": "Relative Path Root",
|
||||
"merge_blend_tree.relative_path_root.tooltip": "The root object to use when interpreting relative paths. If not specified, the object this component is attached to will be used.",
|
||||
|
||||
"worldfixed.quest": "This component is not compatible with the standalone Oculus Quest and will have no effect.",
|
||||
"worldfixed.normal": "This object will be fixed to world unless you fixed to avatar with constraint.",
|
||||
"fpvisible.normal": "This object will be visible in your first person view.",
|
||||
|
@ -55,7 +55,12 @@
|
||||
"merge_armature.reset_pos.heuristic_scale": "衣装の全体的なスケールをアバターに合わせる",
|
||||
"merge_armature.reset_pos.heuristic_scale.tooltip": "腕の長さを参考に、衣装全体のスケールをアバターに合わせます。非対応衣装を導入するときは推奨です。",
|
||||
|
||||
|
||||
"merge_blend_tree.blend_tree": "ブレンドツリー",
|
||||
"merge_blend_tree.path_mode": "パスモード",
|
||||
"merge_blend_tree.path_mode.tooltip": "アニメーション内のパスを解釈するモード。相対的にすると、このオブジェクトについているアニメーターでアニメーション編集できます",
|
||||
"merge_blend_tree.relative_path_root": "相対的パースのルート",
|
||||
"merge_blend_tree.relative_path_root.tooltip": "相対的パースはこのオブジェクトを基準に解釈されます。指定がない場合は、このコンポーネントがついているオブジェクトを基準とします。",
|
||||
|
||||
"path_mode.Relative": "相対的(このオブジェクトからのパスを使用)",
|
||||
"path_mode.Absolute": "絶対的(アバタールートからのパスを使用)",
|
||||
"merge_animator.animator": "統合されるアニメーター",
|
||||
@ -66,6 +71,11 @@
|
||||
"merge_animator.path_mode.tooltip": "アニメーション内のパスを解釈するモード。相対的にすると、このオブジェクトについているアニメーターでアニメーション編集できます",
|
||||
"merge_animator.match_avatar_write_defaults": "アバターのWrite Defaults設定に合わせる",
|
||||
"merge_animator.match_avatar_write_defaults.tooltip": "アバターの該当アニメーターのWrite Defaults設定に合わせます。アバター側の設定が矛盾する場合は、統合されるアニメーターのWD値がそのまま採用されます。",
|
||||
"merge_animator.relative_path_root": "相対的パスのルート",
|
||||
"merge_animator.relative_path_root.tooltip": "相対的パスはこのオブジェクトを基準に解釈されます。指定がない場合は、このコンポーネントがついているオブジェクトを基準とします。",
|
||||
"merge_animator.layer_priority": "レイヤー優先度",
|
||||
"merge_animator.layer_priority.tooltip": "アニメーターにレイヤーが統合される順番を制御します。低い値から高い値の順に統合されます。マイナスの場合は元々のAvatar Descriptorについているコントローラーより前に統合され、ゼロ以上の場合はそのあとに統合されます。",
|
||||
|
||||
"worldfixed.quest": "このコンポーネントはクエスト単体非対応のため無効となっています。",
|
||||
"worldfixed.normal": "このオブジェクトはConstraint等でアバターに追従させない限りワールドに固定されます。",
|
||||
"fpvisible.normal": "このオブジェクトは一人視点で表示されます。",
|
||||
|
@ -25,6 +25,7 @@
|
||||
#if MA_VRCSDK3_AVATARS
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using nadena.dev.modular_avatar.editor.ErrorReporting;
|
||||
using UnityEditor;
|
||||
using UnityEditor.Animations;
|
||||
@ -67,10 +68,23 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
if (descriptor.specialAnimationLayers != null) InitSessions(descriptor.specialAnimationLayers);
|
||||
|
||||
var toMerge = avatarGameObject.transform.GetComponentsInChildren<ModularAvatarMergeAnimator>(true);
|
||||
Dictionary<VRCAvatarDescriptor.AnimLayerType, List<ModularAvatarMergeAnimator>> byLayerType
|
||||
= new Dictionary<VRCAvatarDescriptor.AnimLayerType, List<ModularAvatarMergeAnimator>>();
|
||||
|
||||
foreach (var merge in toMerge)
|
||||
{
|
||||
BuildReport.ReportingObject(merge, () => ProcessMergeAnimator(avatarGameObject, context, merge));
|
||||
if (!byLayerType.TryGetValue(merge.layerType, out var components))
|
||||
{
|
||||
components = new List<ModularAvatarMergeAnimator>();
|
||||
byLayerType[merge.layerType] = components;
|
||||
}
|
||||
|
||||
components.Add(merge);
|
||||
}
|
||||
|
||||
foreach (var entry in byLayerType)
|
||||
{
|
||||
ProcessLayerType(context, entry.Key, entry.Value);
|
||||
}
|
||||
|
||||
descriptor.baseAnimationLayers = FinishSessions(descriptor.baseAnimationLayers);
|
||||
@ -78,35 +92,57 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
descriptor.customizeAnimationLayers = true;
|
||||
}
|
||||
|
||||
private void ProcessMergeAnimator(GameObject avatarGameObject, BuildContext context,
|
||||
ModularAvatarMergeAnimator merge)
|
||||
private void ProcessLayerType(
|
||||
BuildContext context,
|
||||
VRCAvatarDescriptor.AnimLayerType layerType,
|
||||
List<ModularAvatarMergeAnimator> toMerge
|
||||
)
|
||||
{
|
||||
if (merge.animator == null) return;
|
||||
// Stable sort
|
||||
var sorted = toMerge.OrderBy(x => x.layerPriority)
|
||||
.ToList();
|
||||
var beforeOriginal = sorted.Where(x => x.layerPriority < 0)
|
||||
.ToList();
|
||||
var afterOriginal = sorted.Where(x => x.layerPriority >= 0)
|
||||
.ToList();
|
||||
|
||||
var session = new AnimatorCombiner(context, layerType.ToString() + " (merged)");
|
||||
mergeSessions[layerType] = session;
|
||||
|
||||
foreach (var component in beforeOriginal)
|
||||
{
|
||||
MergeSingle(context, session, component);
|
||||
}
|
||||
|
||||
if (defaultControllers_.TryGetValue(layerType, out var defaultController) && defaultController.layers.Length > 0)
|
||||
{
|
||||
session.AddController("", defaultController, null, forceFirstLayerWeight: true);
|
||||
}
|
||||
|
||||
foreach (var component in afterOriginal)
|
||||
{
|
||||
MergeSingle(context, session, component);
|
||||
}
|
||||
}
|
||||
|
||||
private void MergeSingle(BuildContext context, AnimatorCombiner session, ModularAvatarMergeAnimator merge)
|
||||
{
|
||||
string basePath;
|
||||
if (merge.pathMode == MergeAnimatorPathMode.Relative)
|
||||
{
|
||||
var relativePath = RuntimeUtil.RelativePath(avatarGameObject, merge.gameObject);
|
||||
var targetObject = merge.relativePathRoot.Get(context.AvatarRootTransform);
|
||||
if (targetObject == null) targetObject = merge.gameObject;
|
||||
|
||||
var relativePath = RuntimeUtil.RelativePath(context.AvatarRootObject, targetObject);
|
||||
basePath = relativePath != "" ? relativePath + "/" : "";
|
||||
}
|
||||
else
|
||||
{
|
||||
basePath = "";
|
||||
}
|
||||
|
||||
if (!mergeSessions.TryGetValue(merge.layerType, out var session))
|
||||
{
|
||||
session = new AnimatorCombiner(context, merge.layerType.ToString() + " (merged)");
|
||||
mergeSessions[merge.layerType] = session;
|
||||
if (defaultControllers_.TryGetValue(merge.layerType, out var defaultController))
|
||||
{
|
||||
session.AddController("", defaultController, null);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool? writeDefaults = merge.matchAvatarWriteDefaults ? writeDefaults_[merge.layerType] : null;
|
||||
mergeSessions[merge.layerType]
|
||||
.AddController(basePath, (AnimatorController) merge.animator, writeDefaults);
|
||||
session.AddController(basePath, (AnimatorController) merge.animator, writeDefaults);
|
||||
|
||||
if (merge.deleteAttachedAnimator)
|
||||
{
|
||||
|
171
Editor/MergeBlendTreePass.cs
Normal file
@ -0,0 +1,171 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using BestHTTP.SecureProtocol.Org.BouncyCastle.Utilities;
|
||||
using nadena.dev.modular_avatar.animation;
|
||||
using nadena.dev.ndmf;
|
||||
using nadena.dev.ndmf.util;
|
||||
using UnityEditor.Animations;
|
||||
using UnityEngine;
|
||||
using VRC.SDK3.Avatars.Components;
|
||||
|
||||
namespace nadena.dev.modular_avatar.core.editor
|
||||
{
|
||||
internal class MergeBlendTreePass : Pass<MergeBlendTreePass>
|
||||
{
|
||||
internal const string ALWAYS_ONE = "__ModularAvatarInternal/One";
|
||||
internal const string BlendTreeLayerName = "ModularAvatar: Merge Blend Tree";
|
||||
|
||||
private AnimatorController _controller;
|
||||
private BlendTree _rootBlendTree;
|
||||
private HashSet<string> _parameterNames;
|
||||
|
||||
protected override void Execute(ndmf.BuildContext context)
|
||||
{
|
||||
_rootBlendTree = null;
|
||||
_parameterNames = new HashSet<string>();
|
||||
_controller = new AnimatorController();
|
||||
|
||||
foreach (var component in
|
||||
context.AvatarRootObject.GetComponentsInChildren<ModularAvatarMergeBlendTree>(true))
|
||||
{
|
||||
ErrorReport.WithContextObject(component, () => ProcessComponent(context, component));
|
||||
}
|
||||
|
||||
List<AnimatorControllerParameter> parameters = new List<AnimatorControllerParameter>(_parameterNames.Count + 1);
|
||||
if (_controller != null)
|
||||
{
|
||||
_parameterNames.Remove(ALWAYS_ONE);
|
||||
|
||||
parameters.Add(new AnimatorControllerParameter()
|
||||
{
|
||||
defaultFloat = 1,
|
||||
name = ALWAYS_ONE,
|
||||
type = AnimatorControllerParameterType.Float
|
||||
});
|
||||
|
||||
foreach (var name in _parameterNames)
|
||||
{
|
||||
parameters.Add(new AnimatorControllerParameter()
|
||||
{
|
||||
name = name,
|
||||
type = AnimatorControllerParameterType.Float
|
||||
});
|
||||
}
|
||||
|
||||
_controller.parameters = parameters.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessComponent(ndmf.BuildContext context, ModularAvatarMergeBlendTree component)
|
||||
{
|
||||
BlendTree componentBlendTree = component.BlendTree as BlendTree;
|
||||
|
||||
if (componentBlendTree == null)
|
||||
{
|
||||
ErrorReport.ReportError(Localization.L, ErrorSeverity.NonFatal, "error.merge_blend_tree.missing_tree");
|
||||
return;
|
||||
}
|
||||
|
||||
string basePath = null;
|
||||
if (component.PathMode == MergeAnimatorPathMode.Relative)
|
||||
{
|
||||
var root = component.RelativePathRoot.Get(context.AvatarRootTransform);
|
||||
if (root == null) root = component.gameObject;
|
||||
|
||||
basePath = RuntimeUtil.AvatarRootPath(root) + "/";
|
||||
}
|
||||
|
||||
var bt = new DeepClone(context).DoClone(componentBlendTree, basePath);
|
||||
var rootBlend = GetRootBlendTree(context);
|
||||
|
||||
rootBlend.AddChild(bt);
|
||||
var children = rootBlend.children;
|
||||
children[children.Length - 1].directBlendParameter = ALWAYS_ONE;
|
||||
rootBlend.children = children;
|
||||
|
||||
foreach (var asset in bt.ReferencedAssets(includeScene: false))
|
||||
{
|
||||
if (asset is BlendTree bt2)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(bt2.blendParameter) && bt2.blendType != BlendTreeType.Direct)
|
||||
{
|
||||
_parameterNames.Add(bt2.blendParameter);
|
||||
}
|
||||
|
||||
if (bt2.blendType != BlendTreeType.Direct && bt2.blendType != BlendTreeType.Simple1D)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(bt2.blendParameterY))
|
||||
{
|
||||
_parameterNames.Add(bt2.blendParameterY);
|
||||
}
|
||||
}
|
||||
|
||||
if (bt2.blendType == BlendTreeType.Direct)
|
||||
{
|
||||
foreach (var childMotion in bt2.children)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(childMotion.directBlendParameter))
|
||||
{
|
||||
_parameterNames.Add(childMotion.directBlendParameter);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private BlendTree GetRootBlendTree(ndmf.BuildContext context)
|
||||
{
|
||||
if (_rootBlendTree != null) return _rootBlendTree;
|
||||
|
||||
var newController = new AnimatorController();
|
||||
var newStateMachine = new AnimatorStateMachine();
|
||||
var newState = new AnimatorState();
|
||||
|
||||
_rootBlendTree = new BlendTree();
|
||||
_controller = newController;
|
||||
|
||||
newController.layers = new[]
|
||||
{
|
||||
new AnimatorControllerLayer
|
||||
{
|
||||
blendingMode = AnimatorLayerBlendingMode.Override,
|
||||
defaultWeight = 1,
|
||||
name = BlendTreeLayerName,
|
||||
stateMachine = newStateMachine
|
||||
}
|
||||
};
|
||||
|
||||
newStateMachine.name = "ModularAvatarMergeBlendTree";
|
||||
newStateMachine.states = new[]
|
||||
{
|
||||
new ChildAnimatorState
|
||||
{
|
||||
state = newState,
|
||||
position = Vector3.zero
|
||||
}
|
||||
};
|
||||
newStateMachine.defaultState = newState;
|
||||
|
||||
newState.writeDefaultValues = true;
|
||||
newState.motion = _rootBlendTree;
|
||||
|
||||
_rootBlendTree.blendType = BlendTreeType.Direct;
|
||||
_rootBlendTree.blendParameter = ALWAYS_ONE;
|
||||
|
||||
var mergeObject = new GameObject("ModularAvatarMergeBlendTree");
|
||||
var merger = mergeObject.AddComponent<ModularAvatarMergeAnimator>();
|
||||
merger.animator = newController;
|
||||
merger.pathMode = MergeAnimatorPathMode.Absolute;
|
||||
merger.matchAvatarWriteDefaults = false;
|
||||
merger.layerType = VRCAvatarDescriptor.AnimLayerType.FX;
|
||||
merger.deleteAttachedAnimator = false;
|
||||
merger.layerPriority = Int32.MinValue;
|
||||
|
||||
mergeObject.transform.SetParent(context.AvatarRootTransform, false);
|
||||
mergeObject.transform.SetSiblingIndex(0);
|
||||
|
||||
return _rootBlendTree;
|
||||
}
|
||||
}
|
||||
}
|
3
Editor/MergeBlendTreePass.cs.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 54adb9bad5fa4080bc0a5e4ca2c2c530
|
||||
timeCreated: 1703148577
|
@ -38,6 +38,7 @@ namespace nadena.dev.modular_avatar.core.editor.plugin
|
||||
seq.Run(MeshSettingsPluginPass.Instance);
|
||||
#if MA_VRCSDK3_AVATARS
|
||||
seq.Run(RenameParametersPluginPass.Instance);
|
||||
seq.Run(MergeBlendTreePass.Instance);
|
||||
seq.Run(MergeAnimatorPluginPass.Instance);
|
||||
seq.Run(MenuInstallPluginPass.Instance);
|
||||
#endif
|
||||
|
@ -4,6 +4,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using nadena.dev.modular_avatar.animation;
|
||||
using nadena.dev.modular_avatar.editor.ErrorReporting;
|
||||
using UnityEditor;
|
||||
using UnityEditor.Animations;
|
||||
@ -171,6 +172,18 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
break;
|
||||
}
|
||||
|
||||
case ModularAvatarMergeBlendTree merger:
|
||||
{
|
||||
var bt = merger.BlendTree as BlendTree;
|
||||
if (bt != null)
|
||||
{
|
||||
merger.BlendTree = bt = new DeepClone(_context.PluginBuildContext).DoClone(bt);
|
||||
ProcessBlendtree(bt, remaps);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case ModularAvatarMenuInstaller installer:
|
||||
{
|
||||
if (installer.menuToAppend != null && installer.enabled)
|
||||
|
@ -44,6 +44,8 @@ namespace nadena.dev.modular_avatar.core
|
||||
public bool deleteAttachedAnimator;
|
||||
public MergeAnimatorPathMode pathMode = MergeAnimatorPathMode.Relative;
|
||||
public bool matchAvatarWriteDefaults;
|
||||
public AvatarObjectReference relativePathRoot = new AvatarObjectReference();
|
||||
public int layerPriority = 0;
|
||||
|
||||
public override void ResolveReferences()
|
||||
{
|
||||
|
14
Runtime/ModularAvatarMergeBlendTree.cs
Normal file
@ -0,0 +1,14 @@
|
||||
using UnityEngine;
|
||||
|
||||
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
|
||||
{
|
||||
// We can't actually reference a BlendTree here because it's not available when building a player build
|
||||
public UnityEngine.Object BlendTree;
|
||||
public MergeAnimatorPathMode PathMode = MergeAnimatorPathMode.Relative;
|
||||
public AvatarObjectReference RelativePathRoot = new AvatarObjectReference();
|
||||
}
|
||||
}
|
11
Runtime/ModularAvatarMergeBlendTree.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 229dd561ca024a6588e388160921a70f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: a8edd5bd1a0a64a40aa99cc09fb5f198, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
55
UnitTests~/Animation/AnimationTestUtil.cs
Normal file
@ -0,0 +1,55 @@
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using UnityEditor;
|
||||
using UnityEditor.Animations;
|
||||
using UnityEngine;
|
||||
|
||||
namespace modular_avatar_tests
|
||||
{
|
||||
public static class AnimationTestUtil
|
||||
{
|
||||
public static AnimatorController TestController(string name, Motion motion = null)
|
||||
{
|
||||
var controller = new AnimatorController();
|
||||
var stateMachine = new AnimatorStateMachine();
|
||||
var state = new AnimatorState();
|
||||
state.name = name;
|
||||
|
||||
controller.layers = new[]
|
||||
{
|
||||
new AnimatorControllerLayer
|
||||
{
|
||||
blendingMode = AnimatorLayerBlendingMode.Override,
|
||||
defaultWeight = 1,
|
||||
name = name,
|
||||
stateMachine = stateMachine
|
||||
}
|
||||
};
|
||||
|
||||
stateMachine.name = name;
|
||||
stateMachine.states = new[]
|
||||
{
|
||||
new ChildAnimatorState()
|
||||
{
|
||||
state = state
|
||||
}
|
||||
};
|
||||
stateMachine.defaultState = state;
|
||||
state.motion = motion;
|
||||
|
||||
return controller;
|
||||
}
|
||||
|
||||
public static AnimationClip AnimationWithPath(string path)
|
||||
{
|
||||
AnimationClip clip = new AnimationClip();
|
||||
clip.SetCurve(path, typeof(Transform), "localPosition.x", AnimationCurve.Constant(0, 1, 0));
|
||||
return clip;
|
||||
}
|
||||
|
||||
public static void AssertAnimationHasPath(AnimationClip clip, string path)
|
||||
{
|
||||
Assert.IsTrue(AnimationUtility.GetCurveBindings(clip).Any(b => b.path == path));
|
||||
}
|
||||
}
|
||||
}
|
3
UnitTests~/Animation/AnimationTestUtil.cs.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8374b5dea0d547fa9a40a5334767bdd0
|
||||
timeCreated: 1703226767
|
128
UnitTests~/Animation/MergeBlendTreeTest.cs
Normal file
@ -0,0 +1,128 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using nadena.dev.modular_avatar.core;
|
||||
using nadena.dev.modular_avatar.core.editor;
|
||||
using NUnit.Framework;
|
||||
using UnityEditor;
|
||||
using UnityEditor.Animations;
|
||||
using UnityEngine;
|
||||
using VRC.SDK3.Avatars.Components;
|
||||
using AvatarProcessor = nadena.dev.ndmf.AvatarProcessor;
|
||||
|
||||
namespace modular_avatar_tests
|
||||
{
|
||||
public class MergeBlendTreeTest : TestBase
|
||||
{
|
||||
[Test]
|
||||
public void SimpleMergeTest()
|
||||
{
|
||||
BlendTree bt1 = new BlendTree();
|
||||
bt1.blendParameter = "abcd";
|
||||
bt1.blendParameterY = "defg";
|
||||
bt1.blendType = BlendTreeType.Simple1D;
|
||||
bt1.AddChild(AnimationTestUtil.AnimationWithPath("a"));
|
||||
|
||||
BlendTree bt2 = new BlendTree();
|
||||
bt2.blendParameter = "p1";
|
||||
bt2.blendParameterY = "p2";
|
||||
bt2.blendType = BlendTreeType.FreeformCartesian2D;
|
||||
bt2.AddChild(AnimationTestUtil.AnimationWithPath("b"));
|
||||
|
||||
var root = CreateRoot("root");
|
||||
var c1 = CreateChild(root, "child1");
|
||||
var mergeComponent = c1.AddComponent<ModularAvatarMergeBlendTree>();
|
||||
mergeComponent.BlendTree = bt1;
|
||||
mergeComponent.PathMode = MergeAnimatorPathMode.Relative;
|
||||
CreateChild(c1, "a");
|
||||
|
||||
var c2 = CreateChild(root, "child2");
|
||||
var mergeComponent2 = c2.AddComponent<ModularAvatarMergeBlendTree>();
|
||||
mergeComponent2.BlendTree = bt2;
|
||||
mergeComponent2.PathMode = MergeAnimatorPathMode.Absolute;
|
||||
CreateChild(c2, "b");
|
||||
|
||||
AvatarProcessor.ProcessAvatar(root);
|
||||
|
||||
var fxController = FindFxController(root).animatorController as AnimatorController;
|
||||
var fx = findFxLayer(root, MergeBlendTreePass.BlendTreeLayerName);
|
||||
Assert.AreSame(fxController.layers[0].stateMachine, fx.stateMachine);
|
||||
Assert.AreEqual(1, fx.stateMachine.states.Length);
|
||||
|
||||
var motion = fx.stateMachine.states[0].state.motion as BlendTree;
|
||||
Assert.AreEqual(BlendTreeType.Direct, motion.blendType);
|
||||
Assert.AreEqual(2, motion.children.Length);
|
||||
Assert.AreEqual(MergeBlendTreePass.ALWAYS_ONE, motion.children[0].directBlendParameter);
|
||||
Assert.AreEqual(MergeBlendTreePass.ALWAYS_ONE, motion.children[1].directBlendParameter);
|
||||
AnimationTestUtil.AssertAnimationHasPath(((BlendTree)motion.children[0].motion).children[0].motion as AnimationClip, "child1/a");
|
||||
AnimationTestUtil.AssertAnimationHasPath(((BlendTree)motion.children[1].motion).children[0].motion as AnimationClip, "b");
|
||||
|
||||
Assert.IsTrue(fxController.parameters.Any(p =>
|
||||
p.name == "abcd" && p.type == AnimatorControllerParameterType.Float));
|
||||
Assert.IsFalse(fxController.parameters.Any(p =>
|
||||
p.name == "defg" && p.type == AnimatorControllerParameterType.Float));
|
||||
Assert.IsTrue(fxController.parameters.Any(p =>
|
||||
p.name == "p1" && p.type == AnimatorControllerParameterType.Float));
|
||||
Assert.IsTrue(fxController.parameters.Any(p =>
|
||||
p.name == "p2" && p.type == AnimatorControllerParameterType.Float));
|
||||
Assert.IsTrue(fxController.parameters.Any(p =>
|
||||
p.name == MergeBlendTreePass.ALWAYS_ONE && p.type == AnimatorControllerParameterType.Float
|
||||
&& Math.Abs(p.defaultFloat - 1.0f) < 0.0001f));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AlternateRootTest()
|
||||
{
|
||||
BlendTree bt = new BlendTree();
|
||||
bt.AddChild(AnimationTestUtil.AnimationWithPath("a"));
|
||||
|
||||
var root = CreateRoot("root");
|
||||
var c1 = CreateChild(root, "child1");
|
||||
var mergeComponent = c1.AddComponent<ModularAvatarMergeBlendTree>();
|
||||
mergeComponent.BlendTree = bt;
|
||||
mergeComponent.PathMode = MergeAnimatorPathMode.Relative;
|
||||
mergeComponent.RelativePathRoot.referencePath = "child2";
|
||||
CreateChild(c1, "a");
|
||||
|
||||
var c2 = CreateChild(root, "child2");
|
||||
CreateChild(c2, "a");
|
||||
|
||||
AvatarProcessor.ProcessAvatar(root);
|
||||
|
||||
var fx = findFxLayer(root, MergeBlendTreePass.BlendTreeLayerName);
|
||||
var motion = fx.stateMachine.states[0].state.motion as BlendTree;
|
||||
|
||||
AnimationTestUtil.AssertAnimationHasPath(((BlendTree)motion.children[0].motion).children[0].motion as AnimationClip, "child2/a");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void MergeOrderTest()
|
||||
{
|
||||
var root = CreateRoot("root");
|
||||
var bt = new BlendTree();
|
||||
|
||||
var c1 = CreateChild(root, "child1");
|
||||
var mergeComponent = c1.AddComponent<ModularAvatarMergeBlendTree>();
|
||||
mergeComponent.BlendTree = bt;
|
||||
|
||||
TestMerge(root, "m1");
|
||||
TestMerge(root, "m2").layerPriority = int.MinValue;
|
||||
TestMerge(root, "m3").layerPriority = int.MaxValue;
|
||||
|
||||
AvatarProcessor.ProcessAvatar(root);
|
||||
|
||||
var layerNames = (FindFxController(root).animatorController as AnimatorController)
|
||||
.layers.Select(l => l.name).ToArray();
|
||||
|
||||
Assert.AreEqual(new[] {MergeBlendTreePass.BlendTreeLayerName, "m2", "Eyes", "FaceMood", "m1", "m3"}, layerNames);
|
||||
}
|
||||
|
||||
ModularAvatarMergeAnimator TestMerge(GameObject root, string mergeName, Motion motion = null)
|
||||
{
|
||||
var obj = CreateChild(root, mergeName);
|
||||
var merge = obj.AddComponent<ModularAvatarMergeAnimator>();
|
||||
merge.animator = AnimationTestUtil.TestController(mergeName, motion);
|
||||
|
||||
return merge;
|
||||
}
|
||||
}
|
||||
}
|
3
UnitTests~/Animation/MergeBlendTreeTest.cs.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a5bd6966db564d38b8b726c57d71ccfb
|
||||
timeCreated: 1703158381
|
28
UnitTests~/Animation/MergeOrderTest.cs
Normal file
@ -0,0 +1,28 @@
|
||||
using System.Linq;
|
||||
using nadena.dev.ndmf;
|
||||
using NUnit.Framework;
|
||||
using UnityEditor.Animations;
|
||||
|
||||
namespace modular_avatar_tests
|
||||
{
|
||||
public class MergeOrderTest : TestBase
|
||||
{
|
||||
[Test]
|
||||
public void TestMergeOrder()
|
||||
{
|
||||
var root = CreatePrefab("MergeOrderTest.prefab");
|
||||
|
||||
AvatarProcessor.ProcessAvatar(root);
|
||||
|
||||
var fxController = FindFxController(root);
|
||||
|
||||
var layerNames = (FindFxController(root).animatorController as AnimatorController)
|
||||
.layers.Select(l => l.name).ToArray();
|
||||
|
||||
Assert.AreEqual(new []
|
||||
{
|
||||
"1", "2", "3", "4", "5"
|
||||
}, layerNames);
|
||||
}
|
||||
}
|
||||
}
|
3
UnitTests~/Animation/MergeOrderTest.cs.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4051c70770314cc78c8f1ed584e3b3d6
|
||||
timeCreated: 1703226547
|
8
UnitTests~/Animation/MergeOrderTest.meta
Normal file
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 31065186232b77343816d51c1e9560b9
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
537
UnitTests~/Animation/MergeOrderTest.prefab
Normal file
@ -0,0 +1,537 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!1 &328125214001799775
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 3308352267436084320}
|
||||
- component: {fileID: 3884116447948407917}
|
||||
m_Layer: 0
|
||||
m_Name: 2
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!4 &3308352267436084320
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 328125214001799775}
|
||||
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: 8977586966452677008}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!114 &3884116447948407917
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 328125214001799775}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 1bb122659f724ebf85fe095ac02dc339, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
animator: {fileID: 9100000, guid: eb6145d273e8ef54b9ca2a9e95f92305, type: 2}
|
||||
layerType: 5
|
||||
deleteAttachedAnimator: 0
|
||||
pathMode: 0
|
||||
matchAvatarWriteDefaults: 0
|
||||
relativePathRoot:
|
||||
referencePath:
|
||||
layerPriority: -100
|
||||
--- !u!1 &2283261212784644683
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 3823446312882549902}
|
||||
- component: {fileID: 6937154616757373335}
|
||||
m_Layer: 0
|
||||
m_Name: 4
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!4 &3823446312882549902
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 2283261212784644683}
|
||||
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: 8977586966452677008}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!114 &6937154616757373335
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 2283261212784644683}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 1bb122659f724ebf85fe095ac02dc339, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
animator: {fileID: 9100000, guid: a8ac270c680173c4799d999c9bac1534, type: 2}
|
||||
layerType: 5
|
||||
deleteAttachedAnimator: 0
|
||||
pathMode: 0
|
||||
matchAvatarWriteDefaults: 0
|
||||
relativePathRoot:
|
||||
referencePath:
|
||||
layerPriority: 1
|
||||
--- !u!1 &4823348639853131423
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 3390644001987370182}
|
||||
- component: {fileID: 6865338768055249797}
|
||||
m_Layer: 0
|
||||
m_Name: 1
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!4 &3390644001987370182
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 4823348639853131423}
|
||||
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: 8977586966452677008}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!114 &6865338768055249797
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 4823348639853131423}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 1bb122659f724ebf85fe095ac02dc339, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
animator: {fileID: 9100000, guid: 0ff7a3e17c1c03e48a05b1081bf8f820, type: 2}
|
||||
layerType: 5
|
||||
deleteAttachedAnimator: 0
|
||||
pathMode: 0
|
||||
matchAvatarWriteDefaults: 0
|
||||
relativePathRoot:
|
||||
referencePath:
|
||||
layerPriority: -100
|
||||
--- !u!1 &5888860052520729914
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 8977586966452677008}
|
||||
- component: {fileID: 4741512682607283207}
|
||||
- component: {fileID: 203557163609087434}
|
||||
- component: {fileID: 4929062914605056292}
|
||||
m_Layer: 0
|
||||
m_Name: MergeOrderTest
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!4 &8977586966452677008
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 5888860052520729914}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: -1.2012978, y: 1.1367362, z: 0.22603863}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children:
|
||||
- {fileID: 3823446312882549902}
|
||||
- {fileID: 3390644001987370182}
|
||||
- {fileID: 3308352267436084320}
|
||||
- {fileID: 4333182672044057003}
|
||||
m_Father: {fileID: 0}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!95 &4741512682607283207
|
||||
Animator:
|
||||
serializedVersion: 5
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 5888860052520729914}
|
||||
m_Enabled: 1
|
||||
m_Avatar: {fileID: 0}
|
||||
m_Controller: {fileID: 0}
|
||||
m_CullingMode: 0
|
||||
m_UpdateMode: 0
|
||||
m_ApplyRootMotion: 0
|
||||
m_LinearVelocityBlending: 0
|
||||
m_StabilizeFeet: 0
|
||||
m_WarningMessage:
|
||||
m_HasTransformHierarchy: 1
|
||||
m_AllowConstantClipSamplingOptimization: 1
|
||||
m_KeepAnimatorStateOnDisable: 0
|
||||
m_WriteDefaultValuesOnDisable: 0
|
||||
--- !u!114 &203557163609087434
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 5888860052520729914}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 542108242, guid: 67cc4cb7839cd3741b63733d5adf0442, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
Name:
|
||||
ViewPosition: {x: 0, y: 1.6, z: 0.2}
|
||||
Animations: 0
|
||||
ScaleIPD: 1
|
||||
lipSync: 0
|
||||
lipSyncJawBone: {fileID: 0}
|
||||
lipSyncJawClosed: {x: 0, y: 0, z: 0, w: 1}
|
||||
lipSyncJawOpen: {x: 0, y: 0, z: 0, w: 1}
|
||||
VisemeSkinnedMesh: {fileID: 0}
|
||||
MouthOpenBlendShapeName: Facial_Blends.Jaw_Down
|
||||
VisemeBlendShapes: []
|
||||
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}
|
||||
enableEyeLook: 0
|
||||
customEyeLookSettings:
|
||||
eyeMovement:
|
||||
confidence: 0.5
|
||||
excitement: 0.5
|
||||
leftEye: {fileID: 0}
|
||||
rightEye: {fileID: 0}
|
||||
eyesLookingStraight:
|
||||
linked: 1
|
||||
left: {x: 0, y: 0, z: 0, w: 0}
|
||||
right: {x: 0, y: 0, z: 0, w: 0}
|
||||
eyesLookingUp:
|
||||
linked: 1
|
||||
left: {x: 0, y: 0, z: 0, w: 0}
|
||||
right: {x: 0, y: 0, z: 0, w: 0}
|
||||
eyesLookingDown:
|
||||
linked: 1
|
||||
left: {x: 0, y: 0, z: 0, w: 0}
|
||||
right: {x: 0, y: 0, z: 0, w: 0}
|
||||
eyesLookingLeft:
|
||||
linked: 1
|
||||
left: {x: 0, y: 0, z: 0, w: 0}
|
||||
right: {x: 0, y: 0, z: 0, w: 0}
|
||||
eyesLookingRight:
|
||||
linked: 1
|
||||
left: {x: 0, y: 0, z: 0, w: 0}
|
||||
right: {x: 0, y: 0, z: 0, w: 0}
|
||||
eyelidType: 0
|
||||
upperLeftEyelid: {fileID: 0}
|
||||
upperRightEyelid: {fileID: 0}
|
||||
lowerLeftEyelid: {fileID: 0}
|
||||
lowerRightEyelid: {fileID: 0}
|
||||
eyelidsDefault:
|
||||
upper:
|
||||
linked: 1
|
||||
left: {x: 0, y: 0, z: 0, w: 0}
|
||||
right: {x: 0, y: 0, z: 0, w: 0}
|
||||
lower:
|
||||
linked: 1
|
||||
left: {x: 0, y: 0, z: 0, w: 0}
|
||||
right: {x: 0, y: 0, z: 0, w: 0}
|
||||
eyelidsClosed:
|
||||
upper:
|
||||
linked: 1
|
||||
left: {x: 0, y: 0, z: 0, w: 0}
|
||||
right: {x: 0, y: 0, z: 0, w: 0}
|
||||
lower:
|
||||
linked: 1
|
||||
left: {x: 0, y: 0, z: 0, w: 0}
|
||||
right: {x: 0, y: 0, z: 0, w: 0}
|
||||
eyelidsLookingUp:
|
||||
upper:
|
||||
linked: 1
|
||||
left: {x: 0, y: 0, z: 0, w: 0}
|
||||
right: {x: 0, y: 0, z: 0, w: 0}
|
||||
lower:
|
||||
linked: 1
|
||||
left: {x: 0, y: 0, z: 0, w: 0}
|
||||
right: {x: 0, y: 0, z: 0, w: 0}
|
||||
eyelidsLookingDown:
|
||||
upper:
|
||||
linked: 1
|
||||
left: {x: 0, y: 0, z: 0, w: 0}
|
||||
right: {x: 0, y: 0, z: 0, w: 0}
|
||||
lower:
|
||||
linked: 1
|
||||
left: {x: 0, y: 0, z: 0, w: 0}
|
||||
right: {x: 0, y: 0, z: 0, w: 0}
|
||||
eyelidsSkinnedMesh: {fileID: 0}
|
||||
eyelidsBlendshapes:
|
||||
customizeAnimationLayers: 1
|
||||
baseAnimationLayers:
|
||||
- isEnabled: 0
|
||||
type: 0
|
||||
animatorController: {fileID: 0}
|
||||
mask: {fileID: 0}
|
||||
isDefault: 1
|
||||
- isEnabled: 0
|
||||
type: 4
|
||||
animatorController: {fileID: 0}
|
||||
mask: {fileID: 0}
|
||||
isDefault: 1
|
||||
- isEnabled: 0
|
||||
type: 5
|
||||
animatorController: {fileID: 9100000, guid: d471c3325f786f849b9071ed4caf5e01,
|
||||
type: 2}
|
||||
mask: {fileID: 0}
|
||||
isDefault: 0
|
||||
specialAnimationLayers:
|
||||
- isEnabled: 0
|
||||
type: 6
|
||||
animatorController: {fileID: 0}
|
||||
mask: {fileID: 0}
|
||||
isDefault: 1
|
||||
- isEnabled: 0
|
||||
type: 7
|
||||
animatorController: {fileID: 0}
|
||||
mask: {fileID: 0}
|
||||
isDefault: 1
|
||||
- isEnabled: 0
|
||||
type: 8
|
||||
animatorController: {fileID: 0}
|
||||
mask: {fileID: 0}
|
||||
isDefault: 1
|
||||
AnimationPreset: {fileID: 0}
|
||||
animationHashSet: []
|
||||
autoFootsteps: 1
|
||||
autoLocomotion: 1
|
||||
collider_head:
|
||||
isMirrored: 1
|
||||
state: 0
|
||||
transform: {fileID: 0}
|
||||
radius: 0
|
||||
height: 0
|
||||
position: {x: 0, y: 0, z: 0}
|
||||
rotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
collider_torso:
|
||||
isMirrored: 1
|
||||
state: 0
|
||||
transform: {fileID: 0}
|
||||
radius: 0
|
||||
height: 0
|
||||
position: {x: 0, y: 0, z: 0}
|
||||
rotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
collider_footR:
|
||||
isMirrored: 1
|
||||
state: 0
|
||||
transform: {fileID: 0}
|
||||
radius: 0
|
||||
height: 0
|
||||
position: {x: 0, y: 0, z: 0}
|
||||
rotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
collider_footL:
|
||||
isMirrored: 1
|
||||
state: 0
|
||||
transform: {fileID: 0}
|
||||
radius: 0
|
||||
height: 0
|
||||
position: {x: 0, y: 0, z: 0}
|
||||
rotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
collider_handR:
|
||||
isMirrored: 1
|
||||
state: 0
|
||||
transform: {fileID: 0}
|
||||
radius: 0
|
||||
height: 0
|
||||
position: {x: 0, y: 0, z: 0}
|
||||
rotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
collider_handL:
|
||||
isMirrored: 1
|
||||
state: 0
|
||||
transform: {fileID: 0}
|
||||
radius: 0
|
||||
height: 0
|
||||
position: {x: 0, y: 0, z: 0}
|
||||
rotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
collider_fingerIndexL:
|
||||
isMirrored: 1
|
||||
state: 0
|
||||
transform: {fileID: 0}
|
||||
radius: 0
|
||||
height: 0
|
||||
position: {x: 0, y: 0, z: 0}
|
||||
rotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
collider_fingerMiddleL:
|
||||
isMirrored: 1
|
||||
state: 0
|
||||
transform: {fileID: 0}
|
||||
radius: 0
|
||||
height: 0
|
||||
position: {x: 0, y: 0, z: 0}
|
||||
rotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
collider_fingerRingL:
|
||||
isMirrored: 1
|
||||
state: 0
|
||||
transform: {fileID: 0}
|
||||
radius: 0
|
||||
height: 0
|
||||
position: {x: 0, y: 0, z: 0}
|
||||
rotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
collider_fingerLittleL:
|
||||
isMirrored: 1
|
||||
state: 0
|
||||
transform: {fileID: 0}
|
||||
radius: 0
|
||||
height: 0
|
||||
position: {x: 0, y: 0, z: 0}
|
||||
rotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
collider_fingerIndexR:
|
||||
isMirrored: 1
|
||||
state: 0
|
||||
transform: {fileID: 0}
|
||||
radius: 0
|
||||
height: 0
|
||||
position: {x: 0, y: 0, z: 0}
|
||||
rotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
collider_fingerMiddleR:
|
||||
isMirrored: 1
|
||||
state: 0
|
||||
transform: {fileID: 0}
|
||||
radius: 0
|
||||
height: 0
|
||||
position: {x: 0, y: 0, z: 0}
|
||||
rotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
collider_fingerRingR:
|
||||
isMirrored: 1
|
||||
state: 0
|
||||
transform: {fileID: 0}
|
||||
radius: 0
|
||||
height: 0
|
||||
position: {x: 0, y: 0, z: 0}
|
||||
rotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
collider_fingerLittleR:
|
||||
isMirrored: 1
|
||||
state: 0
|
||||
transform: {fileID: 0}
|
||||
radius: 0
|
||||
height: 0
|
||||
position: {x: 0, y: 0, z: 0}
|
||||
rotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
--- !u!114 &4929062914605056292
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 5888860052520729914}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: -1427037861, guid: 4ecd63eff847044b68db9453ce219299, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
launchedFromSDKPipeline: 0
|
||||
completedSDKPipeline: 0
|
||||
blueprintId:
|
||||
contentType: 0
|
||||
assetBundleUnityVersion:
|
||||
fallbackStatus: 0
|
||||
--- !u!1 &8806789658407322211
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 4333182672044057003}
|
||||
- component: {fileID: 2348079790857662602}
|
||||
m_Layer: 0
|
||||
m_Name: 3
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!4 &4333182672044057003
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 8806789658407322211}
|
||||
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: 8977586966452677008}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!114 &2348079790857662602
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 8806789658407322211}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 1bb122659f724ebf85fe095ac02dc339, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
animator: {fileID: 9100000, guid: 8f90ad8b7c986a44d88051d583a20fdf, type: 2}
|
||||
layerType: 5
|
||||
deleteAttachedAnimator: 0
|
||||
pathMode: 0
|
||||
matchAvatarWriteDefaults: 0
|
||||
relativePathRoot:
|
||||
referencePath:
|
||||
layerPriority: 0
|
7
UnitTests~/Animation/MergeOrderTest.prefab.meta
Normal file
@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: db4a93ae91e8fe541b04a3de201eae43
|
||||
PrefabImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
72
UnitTests~/Animation/MergeOrderTest/FX.controller
Normal file
@ -0,0 +1,72 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!1102 &-6723305648047965004
|
||||
AnimatorState:
|
||||
serializedVersion: 6
|
||||
m_ObjectHideFlags: 1
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_Name: a
|
||||
m_Speed: 1
|
||||
m_CycleOffset: 0
|
||||
m_Transitions: []
|
||||
m_StateMachineBehaviours: []
|
||||
m_Position: {x: 50, y: 50, z: 0}
|
||||
m_IKOnFeet: 0
|
||||
m_WriteDefaultValues: 1
|
||||
m_Mirror: 0
|
||||
m_SpeedParameterActive: 0
|
||||
m_MirrorParameterActive: 0
|
||||
m_CycleOffsetParameterActive: 0
|
||||
m_TimeParameterActive: 0
|
||||
m_Motion: {fileID: 7400000, guid: e13eddbf7adb8ec47a8f5af784b14814, type: 2}
|
||||
m_Tag:
|
||||
m_SpeedParameter:
|
||||
m_MirrorParameter:
|
||||
m_CycleOffsetParameter:
|
||||
m_TimeParameter:
|
||||
--- !u!1107 &-6101661261653446652
|
||||
AnimatorStateMachine:
|
||||
serializedVersion: 6
|
||||
m_ObjectHideFlags: 1
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_Name: 3
|
||||
m_ChildStates:
|
||||
- serializedVersion: 1
|
||||
m_State: {fileID: -6723305648047965004}
|
||||
m_Position: {x: 467.5824, y: 113.26929, z: 0}
|
||||
m_ChildStateMachines: []
|
||||
m_AnyStateTransitions: []
|
||||
m_EntryTransitions: []
|
||||
m_StateMachineTransitions: {}
|
||||
m_StateMachineBehaviours: []
|
||||
m_AnyStatePosition: {x: 50, y: 20, z: 0}
|
||||
m_EntryPosition: {x: 50, y: 120, z: 0}
|
||||
m_ExitPosition: {x: 800, y: 120, z: 0}
|
||||
m_ParentStateMachinePosition: {x: 800, y: 20, z: 0}
|
||||
m_DefaultState: {fileID: -6723305648047965004}
|
||||
--- !u!91 &9100000
|
||||
AnimatorController:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_Name: FX
|
||||
serializedVersion: 5
|
||||
m_AnimatorParameters: []
|
||||
m_AnimatorLayers:
|
||||
- serializedVersion: 5
|
||||
m_Name: 3
|
||||
m_StateMachine: {fileID: -6101661261653446652}
|
||||
m_Mask: {fileID: 0}
|
||||
m_Motions: []
|
||||
m_Behaviours: []
|
||||
m_BlendingMode: 0
|
||||
m_SyncedLayerIndex: -1
|
||||
m_DefaultWeight: 0
|
||||
m_IKPass: 0
|
||||
m_SyncedLayerAffectsTiming: 0
|
||||
m_Controller: {fileID: 9100000}
|
8
UnitTests~/Animation/MergeOrderTest/FX.controller.meta
Normal file
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d471c3325f786f849b9071ed4caf5e01
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 9100000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
72
UnitTests~/Animation/MergeOrderTest/First.controller
Normal file
@ -0,0 +1,72 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!1102 &-586138749803539241
|
||||
AnimatorState:
|
||||
serializedVersion: 6
|
||||
m_ObjectHideFlags: 1
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_Name: a
|
||||
m_Speed: 1
|
||||
m_CycleOffset: 0
|
||||
m_Transitions: []
|
||||
m_StateMachineBehaviours: []
|
||||
m_Position: {x: 50, y: 50, z: 0}
|
||||
m_IKOnFeet: 0
|
||||
m_WriteDefaultValues: 1
|
||||
m_Mirror: 0
|
||||
m_SpeedParameterActive: 0
|
||||
m_MirrorParameterActive: 0
|
||||
m_CycleOffsetParameterActive: 0
|
||||
m_TimeParameterActive: 0
|
||||
m_Motion: {fileID: 7400000, guid: e13eddbf7adb8ec47a8f5af784b14814, type: 2}
|
||||
m_Tag:
|
||||
m_SpeedParameter:
|
||||
m_MirrorParameter:
|
||||
m_CycleOffsetParameter:
|
||||
m_TimeParameter:
|
||||
--- !u!91 &9100000
|
||||
AnimatorController:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_Name: First
|
||||
serializedVersion: 5
|
||||
m_AnimatorParameters: []
|
||||
m_AnimatorLayers:
|
||||
- serializedVersion: 5
|
||||
m_Name: 1
|
||||
m_StateMachine: {fileID: 3312057567024330509}
|
||||
m_Mask: {fileID: 0}
|
||||
m_Motions: []
|
||||
m_Behaviours: []
|
||||
m_BlendingMode: 0
|
||||
m_SyncedLayerIndex: -1
|
||||
m_DefaultWeight: 0
|
||||
m_IKPass: 0
|
||||
m_SyncedLayerAffectsTiming: 0
|
||||
m_Controller: {fileID: 9100000}
|
||||
--- !u!1107 &3312057567024330509
|
||||
AnimatorStateMachine:
|
||||
serializedVersion: 6
|
||||
m_ObjectHideFlags: 1
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_Name: 1
|
||||
m_ChildStates:
|
||||
- serializedVersion: 1
|
||||
m_State: {fileID: -586138749803539241}
|
||||
m_Position: {x: 271.8407, y: -366.9505, z: 0}
|
||||
m_ChildStateMachines: []
|
||||
m_AnyStateTransitions: []
|
||||
m_EntryTransitions: []
|
||||
m_StateMachineTransitions: {}
|
||||
m_StateMachineBehaviours: []
|
||||
m_AnyStatePosition: {x: 50, y: 20, z: 0}
|
||||
m_EntryPosition: {x: 50, y: 120, z: 0}
|
||||
m_ExitPosition: {x: 800, y: 120, z: 0}
|
||||
m_ParentStateMachinePosition: {x: 800, y: 20, z: 0}
|
||||
m_DefaultState: {fileID: -586138749803539241}
|
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0ff7a3e17c1c03e48a05b1081bf8f820
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 9100000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
72
UnitTests~/Animation/MergeOrderTest/Fourth.controller
Normal file
@ -0,0 +1,72 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!1107 &-6972772901112240568
|
||||
AnimatorStateMachine:
|
||||
serializedVersion: 6
|
||||
m_ObjectHideFlags: 1
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_Name: 5
|
||||
m_ChildStates:
|
||||
- serializedVersion: 1
|
||||
m_State: {fileID: -6452914645168321189}
|
||||
m_Position: {x: 277.06042, y: -372.1703, z: 0}
|
||||
m_ChildStateMachines: []
|
||||
m_AnyStateTransitions: []
|
||||
m_EntryTransitions: []
|
||||
m_StateMachineTransitions: {}
|
||||
m_StateMachineBehaviours: []
|
||||
m_AnyStatePosition: {x: 50, y: 20, z: 0}
|
||||
m_EntryPosition: {x: 50, y: 120, z: 0}
|
||||
m_ExitPosition: {x: 800, y: 120, z: 0}
|
||||
m_ParentStateMachinePosition: {x: 800, y: 20, z: 0}
|
||||
m_DefaultState: {fileID: -6452914645168321189}
|
||||
--- !u!1102 &-6452914645168321189
|
||||
AnimatorState:
|
||||
serializedVersion: 6
|
||||
m_ObjectHideFlags: 1
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_Name: a
|
||||
m_Speed: 1
|
||||
m_CycleOffset: 0
|
||||
m_Transitions: []
|
||||
m_StateMachineBehaviours: []
|
||||
m_Position: {x: 50, y: 50, z: 0}
|
||||
m_IKOnFeet: 0
|
||||
m_WriteDefaultValues: 1
|
||||
m_Mirror: 0
|
||||
m_SpeedParameterActive: 0
|
||||
m_MirrorParameterActive: 0
|
||||
m_CycleOffsetParameterActive: 0
|
||||
m_TimeParameterActive: 0
|
||||
m_Motion: {fileID: 7400000, guid: e13eddbf7adb8ec47a8f5af784b14814, type: 2}
|
||||
m_Tag:
|
||||
m_SpeedParameter:
|
||||
m_MirrorParameter:
|
||||
m_CycleOffsetParameter:
|
||||
m_TimeParameter:
|
||||
--- !u!91 &9100000
|
||||
AnimatorController:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_Name: Fourth
|
||||
serializedVersion: 5
|
||||
m_AnimatorParameters: []
|
||||
m_AnimatorLayers:
|
||||
- serializedVersion: 5
|
||||
m_Name: 5
|
||||
m_StateMachine: {fileID: -6972772901112240568}
|
||||
m_Mask: {fileID: 0}
|
||||
m_Motions: []
|
||||
m_Behaviours: []
|
||||
m_BlendingMode: 0
|
||||
m_SyncedLayerIndex: -1
|
||||
m_DefaultWeight: 0
|
||||
m_IKPass: 0
|
||||
m_SyncedLayerAffectsTiming: 0
|
||||
m_Controller: {fileID: 9100000}
|
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a8ac270c680173c4799d999c9bac1534
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 9100000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
72
UnitTests~/Animation/MergeOrderTest/Second.controller
Normal file
@ -0,0 +1,72 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!1102 &-3838991756498900347
|
||||
AnimatorState:
|
||||
serializedVersion: 6
|
||||
m_ObjectHideFlags: 1
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_Name: a
|
||||
m_Speed: 1
|
||||
m_CycleOffset: 0
|
||||
m_Transitions: []
|
||||
m_StateMachineBehaviours: []
|
||||
m_Position: {x: 50, y: 50, z: 0}
|
||||
m_IKOnFeet: 0
|
||||
m_WriteDefaultValues: 1
|
||||
m_Mirror: 0
|
||||
m_SpeedParameterActive: 0
|
||||
m_MirrorParameterActive: 0
|
||||
m_CycleOffsetParameterActive: 0
|
||||
m_TimeParameterActive: 0
|
||||
m_Motion: {fileID: 7400000, guid: e13eddbf7adb8ec47a8f5af784b14814, type: 2}
|
||||
m_Tag:
|
||||
m_SpeedParameter:
|
||||
m_MirrorParameter:
|
||||
m_CycleOffsetParameter:
|
||||
m_TimeParameter:
|
||||
--- !u!1107 &-3521989285324055791
|
||||
AnimatorStateMachine:
|
||||
serializedVersion: 6
|
||||
m_ObjectHideFlags: 1
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_Name: 2
|
||||
m_ChildStates:
|
||||
- serializedVersion: 1
|
||||
m_State: {fileID: -3838991756498900347}
|
||||
m_Position: {x: 303.1593, y: -145.10986, z: 0}
|
||||
m_ChildStateMachines: []
|
||||
m_AnyStateTransitions: []
|
||||
m_EntryTransitions: []
|
||||
m_StateMachineTransitions: {}
|
||||
m_StateMachineBehaviours: []
|
||||
m_AnyStatePosition: {x: 50, y: 20, z: 0}
|
||||
m_EntryPosition: {x: 50, y: 120, z: 0}
|
||||
m_ExitPosition: {x: 800, y: 120, z: 0}
|
||||
m_ParentStateMachinePosition: {x: 800, y: 20, z: 0}
|
||||
m_DefaultState: {fileID: -3838991756498900347}
|
||||
--- !u!91 &9100000
|
||||
AnimatorController:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_Name: Second
|
||||
serializedVersion: 5
|
||||
m_AnimatorParameters: []
|
||||
m_AnimatorLayers:
|
||||
- serializedVersion: 5
|
||||
m_Name: 2
|
||||
m_StateMachine: {fileID: -3521989285324055791}
|
||||
m_Mask: {fileID: 0}
|
||||
m_Motions: []
|
||||
m_Behaviours: []
|
||||
m_BlendingMode: 0
|
||||
m_SyncedLayerIndex: -1
|
||||
m_DefaultWeight: 0
|
||||
m_IKPass: 0
|
||||
m_SyncedLayerAffectsTiming: 0
|
||||
m_Controller: {fileID: 9100000}
|
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: eb6145d273e8ef54b9ca2a9e95f92305
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 9100000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
72
UnitTests~/Animation/MergeOrderTest/Third.controller
Normal file
@ -0,0 +1,72 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!1102 &-8389599259838401866
|
||||
AnimatorState:
|
||||
serializedVersion: 6
|
||||
m_ObjectHideFlags: 1
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_Name: a
|
||||
m_Speed: 1
|
||||
m_CycleOffset: 0
|
||||
m_Transitions: []
|
||||
m_StateMachineBehaviours: []
|
||||
m_Position: {x: 50, y: 50, z: 0}
|
||||
m_IKOnFeet: 0
|
||||
m_WriteDefaultValues: 1
|
||||
m_Mirror: 0
|
||||
m_SpeedParameterActive: 0
|
||||
m_MirrorParameterActive: 0
|
||||
m_CycleOffsetParameterActive: 0
|
||||
m_TimeParameterActive: 0
|
||||
m_Motion: {fileID: 7400000, guid: e13eddbf7adb8ec47a8f5af784b14814, type: 2}
|
||||
m_Tag:
|
||||
m_SpeedParameter:
|
||||
m_MirrorParameter:
|
||||
m_CycleOffsetParameter:
|
||||
m_TimeParameter:
|
||||
--- !u!1107 &-6972772901112240568
|
||||
AnimatorStateMachine:
|
||||
serializedVersion: 6
|
||||
m_ObjectHideFlags: 1
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_Name: 4
|
||||
m_ChildStates:
|
||||
- serializedVersion: 1
|
||||
m_State: {fileID: -8389599259838401866}
|
||||
m_Position: {x: 381.45605, y: -173.81866, z: 0}
|
||||
m_ChildStateMachines: []
|
||||
m_AnyStateTransitions: []
|
||||
m_EntryTransitions: []
|
||||
m_StateMachineTransitions: {}
|
||||
m_StateMachineBehaviours: []
|
||||
m_AnyStatePosition: {x: 50, y: 20, z: 0}
|
||||
m_EntryPosition: {x: 50, y: 120, z: 0}
|
||||
m_ExitPosition: {x: 800, y: 120, z: 0}
|
||||
m_ParentStateMachinePosition: {x: 800, y: 20, z: 0}
|
||||
m_DefaultState: {fileID: -8389599259838401866}
|
||||
--- !u!91 &9100000
|
||||
AnimatorController:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_Name: Third
|
||||
serializedVersion: 5
|
||||
m_AnimatorParameters: []
|
||||
m_AnimatorLayers:
|
||||
- serializedVersion: 5
|
||||
m_Name: 4
|
||||
m_StateMachine: {fileID: -6972772901112240568}
|
||||
m_Mask: {fileID: 0}
|
||||
m_Motions: []
|
||||
m_Behaviours: []
|
||||
m_BlendingMode: 0
|
||||
m_SyncedLayerIndex: -1
|
||||
m_DefaultWeight: 0
|
||||
m_IKPass: 0
|
||||
m_SyncedLayerAffectsTiming: 0
|
||||
m_Controller: {fileID: 9100000}
|
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8f90ad8b7c986a44d88051d583a20fdf
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 9100000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
53
UnitTests~/Animation/MergeOrderTest/a.anim
Normal file
@ -0,0 +1,53 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!74 &7400000
|
||||
AnimationClip:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_Name: a
|
||||
serializedVersion: 7
|
||||
m_Legacy: 0
|
||||
m_Compressed: 0
|
||||
m_UseHighQualityCurve: 1
|
||||
m_RotationCurves: []
|
||||
m_CompressedRotationCurves: []
|
||||
m_EulerCurves: []
|
||||
m_PositionCurves: []
|
||||
m_ScaleCurves: []
|
||||
m_FloatCurves: []
|
||||
m_PPtrCurves: []
|
||||
m_SampleRate: 60
|
||||
m_WrapMode: 0
|
||||
m_Bounds:
|
||||
m_Center: {x: 0, y: 0, z: 0}
|
||||
m_Extent: {x: 0, y: 0, z: 0}
|
||||
m_ClipBindingConstant:
|
||||
genericBindings: []
|
||||
pptrCurveMapping: []
|
||||
m_AnimationClipSettings:
|
||||
serializedVersion: 2
|
||||
m_AdditiveReferencePoseClip: {fileID: 0}
|
||||
m_AdditiveReferencePoseTime: 0
|
||||
m_StartTime: 0
|
||||
m_StopTime: 1
|
||||
m_OrientationOffsetY: 0
|
||||
m_Level: 0
|
||||
m_CycleOffset: 0
|
||||
m_HasAdditiveReferencePose: 0
|
||||
m_LoopTime: 0
|
||||
m_LoopBlend: 0
|
||||
m_LoopBlendOrientation: 0
|
||||
m_LoopBlendPositionY: 0
|
||||
m_LoopBlendPositionXZ: 0
|
||||
m_KeepOriginalOrientation: 0
|
||||
m_KeepOriginalPositionY: 1
|
||||
m_KeepOriginalPositionXZ: 0
|
||||
m_HeightFromFeet: 0
|
||||
m_Mirror: 0
|
||||
m_EditorCurves: []
|
||||
m_EulerEditorCurves: []
|
||||
m_HasGenericRootTransform: 0
|
||||
m_HasMotionFloatCurves: 0
|
||||
m_Events: []
|
8
UnitTests~/Animation/MergeOrderTest/a.anim.meta
Normal file
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e13eddbf7adb8ec47a8f5af784b14814
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 7400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -2,8 +2,10 @@
|
||||
|
||||
using nadena.dev.ndmf;
|
||||
using nadena.dev.modular_avatar.animation;
|
||||
using nadena.dev.modular_avatar.core;
|
||||
using NUnit.Framework;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace modular_avatar_tests
|
||||
{
|
||||
@ -25,6 +27,44 @@ namespace modular_avatar_tests
|
||||
Assert.True(new SerializedObject(hq_on.motion).FindProperty("m_UseHighQualityCurve").boolValue);
|
||||
Assert.False(new SerializedObject(hq_off.motion).FindProperty("m_UseHighQualityCurve").boolValue);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void RelativePathTest()
|
||||
{
|
||||
var root = CreateRoot("root");
|
||||
|
||||
var c1 = CreateChild(root, "child1");
|
||||
var c2 = CreateChild(root, "child2");
|
||||
var c3 = CreateChild(root, "child3");
|
||||
|
||||
var a1 = CreateChild(c1, "a");
|
||||
var a2 = CreateChild(c2, "a");
|
||||
var a3 = CreateChild(c3, "a");
|
||||
var a0 = CreateChild(root, "a");
|
||||
|
||||
var merge_rel_1 = AnimationTestUtil.TestController("T1", AnimationTestUtil.AnimationWithPath("a"));
|
||||
var merge_rel_2 = AnimationTestUtil.TestController("T2", AnimationTestUtil.AnimationWithPath("a"));
|
||||
var merge_abs = AnimationTestUtil.TestController("T3", AnimationTestUtil.AnimationWithPath("a"));
|
||||
|
||||
var merge_rel_1_comp = c1.AddComponent<ModularAvatarMergeAnimator>();
|
||||
merge_rel_1_comp.animator = merge_rel_1;
|
||||
merge_rel_1_comp.pathMode = MergeAnimatorPathMode.Relative;
|
||||
merge_rel_1_comp.relativePathRoot.referencePath = "child2";
|
||||
|
||||
var merge_rel_2_comp = c3.AddComponent<ModularAvatarMergeAnimator>();
|
||||
merge_rel_2_comp.animator = merge_rel_2;
|
||||
merge_rel_2_comp.pathMode = MergeAnimatorPathMode.Relative;
|
||||
|
||||
var merge_abs_comp = c2.AddComponent<ModularAvatarMergeAnimator>();
|
||||
merge_abs_comp.animator = merge_abs;
|
||||
merge_abs_comp.pathMode = MergeAnimatorPathMode.Absolute;
|
||||
|
||||
AvatarProcessor.ProcessAvatar(root);
|
||||
|
||||
AnimationTestUtil.AssertAnimationHasPath(findFxMotion(root, "T3") as AnimationClip, "a");
|
||||
AnimationTestUtil.AssertAnimationHasPath(findFxMotion(root, "T1") as AnimationClip, "child2/a");
|
||||
AnimationTestUtil.AssertAnimationHasPath(findFxMotion(root, "T2") as AnimationClip, "child3/a");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -139,8 +139,7 @@ namespace modular_avatar_tests
|
||||
#if MA_VRCSDK3_AVATARS
|
||||
protected static AnimatorControllerLayer findFxLayer(GameObject prefab, string layerName)
|
||||
{
|
||||
var fx = prefab.GetComponent<VRCAvatarDescriptor>().baseAnimationLayers
|
||||
.FirstOrDefault(l => l.type == VRCAvatarDescriptor.AnimLayerType.FX);
|
||||
var fx = FindFxController(prefab);
|
||||
|
||||
Assert.NotNull(fx);
|
||||
var ac = fx.animatorController as AnimatorController;
|
||||
@ -151,6 +150,12 @@ namespace modular_avatar_tests
|
||||
Assert.NotNull(layer);
|
||||
return layer;
|
||||
}
|
||||
|
||||
internal static VRCAvatarDescriptor.CustomAnimLayer FindFxController(GameObject prefab)
|
||||
{
|
||||
return prefab.GetComponent<VRCAvatarDescriptor>().baseAnimationLayers
|
||||
.FirstOrDefault(l => l.type == VRCAvatarDescriptor.AnimLayerType.FX);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 66 KiB |
@ -32,7 +32,13 @@ As a development convenience, you can check the "Delete attached animator" box t
|
||||
|
||||
Animations that move humanoid bones ignore the relative path logic, and will always apply to the overall avatar. As such most humanoid animations (e.g. AFK animations) can be used as-is.
|
||||
|
||||
### Absolute path mode
|
||||
### Path mode
|
||||
|
||||
The path mode option controls how animation paths are interpreted. In "Relative" mode, all paths are relative to a
|
||||
specific object, usually the one the Merge Animator component is attached to. This allows you to create gimmicks that
|
||||
work when they're moved around in the avatar,
|
||||
and makes it easier to record the animations, by using the Unity animator component (as described above). You can
|
||||
control which object is used as the root for paths in animations by setting the "Relative Path Root" field.
|
||||
|
||||
If you want to animate objects that are already attached to the avatar (that aren't under your object), set the path mode to "Absolute". This will cause the animator to use absolute paths, and will not attempt to interpret paths relative to the Merge Animator component.
|
||||
This means you will need to record your animations using the avatar's root animator instead.
|
||||
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 32 KiB |
32
docs~/docs/reference/merge-blend-tree.md
Normal file
@ -0,0 +1,32 @@
|
||||
# Merge Blend Tree
|
||||
|
||||
![Merge Blend Tree](merge-blend-tree.png)
|
||||
|
||||
The merge blend tree component allows you to merge multiple blend trees into a single FX layer.
|
||||
This is an advanced component that allows for building lower-overhead animators by merging multiple gimmicks into a
|
||||
single layer.
|
||||
|
||||
## When should I use it?
|
||||
|
||||
You should use Merge Blend Tree when you have a blend tree that you want to be always active on the avatar.
|
||||
|
||||
## When shouldn't I use it?
|
||||
|
||||
You should not use Merge Blend Tree if you need to disable/enable the blend tree, or have control over motion time.
|
||||
|
||||
## Setting up Merge Blend Tree
|
||||
|
||||
First, create a Blend Tree asset. You can do this by right clicking on the project window and selecting
|
||||
Create -> BlendTree.
|
||||
|
||||
Configure your blend tree as desired, then add a Merge Blend Tree component and specify the Blend Tree in the Blend
|
||||
Tree field.
|
||||
|
||||
You can configure Path Mode and Relative Path Root similarly to Merge Animator; for more details, see the
|
||||
[Merge Animator documentation](merge-animator.md).
|
||||
|
||||
## How blend trees are merged
|
||||
|
||||
Modular Avatar will create a new layer at the top of the FX controller. This layer will contain a single state, with
|
||||
Write Defaults on, and containing a Direct Blend Tree. Each merged blend tree will be attached to this Direct Blend
|
||||
Tree, with its parameter always set to one.
|
BIN
docs~/docs/reference/merge-blend-tree.png
Normal file
After Width: | Height: | Size: 23 KiB |
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 70 KiB |
@ -37,7 +37,9 @@ GameObjectにAnimatorコンポーネントも追加して、Animationパネル
|
||||
ヒューマノイドボーンを操作するアニメーションは上記の相対的なパスで解釈されるのではなく、アバター全体に適用されます。
|
||||
AFKアニメーションなどほとんどのヒューマノイドアニメーションがそのまま使えるというわけです。
|
||||
|
||||
### 絶対的パスモード
|
||||
### パスモード
|
||||
|
||||
パスモード設定に応じて、アニメーションのパスの扱いが変わります。「相対的」モードでは、アニメーションのパスが特定のオブジェクトを基準として解釈されます。これで移動されても動作するギミックが作れるし、アニメーションの収録がしやすくなります。デフォルトでは、コンポーネントがついているオブジェクトが基準になりますが、「相対的パスのルート」で変更できます。
|
||||
|
||||
自分のプレハブ外の、元々からあったオブジェクトを操作する場合は、「パスモード」を「絶対的」に変えてください。
|
||||
これでアニメーターの中のパスがアバターを基準に解釈され、Merge Animatorから相対的に解釈されるシステムがはずされます。
|
||||
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 36 KiB |
@ -0,0 +1,29 @@
|
||||
# Merge Blend Tree
|
||||
|
||||
![Merge Blend Tree](merge-blend-tree.png)
|
||||
|
||||
Merge Blend Treeは、複数のブレンドツリーを1つのFXレイヤーにマージすることができます。
|
||||
複数のギミックを1つのレイヤーにまとめて、負荷を低減するための高度なコンポーネントです。
|
||||
|
||||
## いつ使うもの?
|
||||
|
||||
ブレンドツリーを常にアバターで稼働させたい場合に使います。
|
||||
|
||||
## いつ使わないもの?
|
||||
|
||||
ブレンドツリーを無効にしたり、モーションタイムを制御したりする必要がある場合は、Merge Blend Treeを使わないでください。
|
||||
|
||||
## セットアップ方法
|
||||
|
||||
まず、ブレンドツリーのアセットを作成します。プロジェクトウィンドウで右クリックして、Create -> BlendTreeを選択してください。
|
||||
|
||||
ブレンドツリーを設定したら、Merge Blend Treeコンポーネントを追加して、「ブレンドツリー」フィールドに指定します。
|
||||
|
||||
パスモードと相対パスルートは、Merge Animatorと同様に設定できます。
|
||||
詳細は、[Merge Animatorのドキュメント](merge-animator.md)を参照してください。
|
||||
|
||||
## ブレンドツリーのマージ方法
|
||||
|
||||
Modular Avatarは、FXコントローラーの一番上に新しいレイヤーを作成します。
|
||||
このレイヤーには、Write Defaultsがオンになっている単一のステートが含まれています。
|
||||
マージされたブレンドツリーは、パラメーターが常に1に設定されているこのDirect Blend Treeに接続されます。
|
After Width: | Height: | Size: 24 KiB |
@ -16,6 +16,6 @@
|
||||
},
|
||||
"vpmDependencies": {
|
||||
"com.vrchat.avatars": ">=3.2.0",
|
||||
"nadena.dev.ndmf": ">=1.3.0-alpha.0 <2.0.0-a"
|
||||
"nadena.dev.ndmf": ">=1.3.0-alpha.1 <2.0.0-a"
|
||||
}
|
||||
}
|
||||
|