mirror of
https://github.com/bdunderscore/modular-avatar.git
synced 2024-12-29 18:55:06 +08:00
fix: remove duplicate animator merger and add parameter type mismatch error asset references (#585)
Closes: #549
This commit is contained in:
parent
2650566f9a
commit
610ce40270
@ -29,7 +29,7 @@ namespace nadena.dev.modular_avatar.animation
|
|||||||
{
|
{
|
||||||
if (controller == null) return null;
|
if (controller == null) return null;
|
||||||
|
|
||||||
var merger = new AnimatorCombiner(controller.name + " (clone)", context.AssetContainer);
|
var merger = new AnimatorCombiner(context, controller.name + " (cloned)");
|
||||||
switch (controller)
|
switch (controller)
|
||||||
{
|
{
|
||||||
case AnimatorController ac:
|
case AnimatorController ac:
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* MIT License
|
* MIT License
|
||||||
*
|
*
|
||||||
* Copyright (c) 2022-2023 bd_
|
* Copyright (c) 2022 bd_
|
||||||
*
|
*
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
@ -22,38 +22,38 @@
|
|||||||
* SOFTWARE.
|
* SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#region
|
#if MA_VRCSDK3_AVATARS
|
||||||
|
#endif
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using nadena.dev.modular_avatar.animation;
|
||||||
using nadena.dev.modular_avatar.core.editor;
|
using nadena.dev.modular_avatar.core.editor;
|
||||||
|
using nadena.dev.modular_avatar.editor.ErrorReporting;
|
||||||
|
using nadena.dev.ndmf.util;
|
||||||
using UnityEditor;
|
using UnityEditor;
|
||||||
using UnityEditor.Animations;
|
using UnityEditor.Animations;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
|
||||||
#if MA_VRCSDK3_AVATARS
|
|
||||||
using VRC.SDK3.Avatars.Components;
|
using VRC.SDK3.Avatars.Components;
|
||||||
#endif
|
using VRC.SDKBase;
|
||||||
|
|
||||||
using Object = UnityEngine.Object;
|
using Object = UnityEngine.Object;
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
namespace nadena.dev.modular_avatar.animation
|
namespace nadena.dev.modular_avatar.animation
|
||||||
{
|
{
|
||||||
internal class AnimatorCombiner
|
internal class AnimatorCombiner
|
||||||
{
|
{
|
||||||
private readonly AnimatorController _combined;
|
private readonly AnimatorController _combined;
|
||||||
private bool isSaved;
|
|
||||||
|
|
||||||
private AnimatorOverrideController _overrideController;
|
private readonly DeepClone _deepClone;
|
||||||
|
|
||||||
private List<AnimatorControllerLayer> _layers = new List<AnimatorControllerLayer>();
|
private List<AnimatorControllerLayer> _layers = new List<AnimatorControllerLayer>();
|
||||||
|
|
||||||
private Dictionary<String, AnimatorControllerParameter> _parameters =
|
private Dictionary<String, AnimatorControllerParameter> _parameters =
|
||||||
new Dictionary<string, AnimatorControllerParameter>();
|
new Dictionary<string, AnimatorControllerParameter>();
|
||||||
|
|
||||||
|
private Dictionary<String, AnimatorController> _parameterSource =
|
||||||
|
new Dictionary<string, AnimatorController>();
|
||||||
|
|
||||||
private Dictionary<KeyValuePair<String, Motion>, Motion> _motions =
|
private Dictionary<KeyValuePair<String, Motion>, Motion> _motions =
|
||||||
new Dictionary<KeyValuePair<string, Motion>, Motion>();
|
new Dictionary<KeyValuePair<string, Motion>, Motion>();
|
||||||
|
|
||||||
@ -62,38 +62,115 @@ namespace nadena.dev.modular_avatar.animation
|
|||||||
|
|
||||||
private Dictionary<Object, Object> _cloneMap;
|
private Dictionary<Object, Object> _cloneMap;
|
||||||
|
|
||||||
private int controllerBaseLayer = 0;
|
private int _controllerBaseLayer = 0;
|
||||||
|
|
||||||
public AnimatorCombiner(String assetName, Object assetContainer)
|
public VRC_AnimatorLayerControl.BlendableLayer? BlendableLayer;
|
||||||
|
|
||||||
|
public AnimatorCombiner(ndmf.BuildContext context, String assetName)
|
||||||
{
|
{
|
||||||
_combined = new AnimatorController();
|
_combined = new AnimatorController();
|
||||||
if (assetContainer != null)
|
if (context.AssetContainer != null && EditorUtility.IsPersistent(context.AssetContainer))
|
||||||
{
|
{
|
||||||
if (!EditorUtility.IsPersistent(assetContainer) ||
|
AssetDatabase.AddObjectToAsset(_combined, context.AssetContainer);
|
||||||
string.IsNullOrEmpty(AssetDatabase.GetAssetPath(assetContainer)))
|
|
||||||
{
|
|
||||||
// Debug.Log("Nonpersistent asset container: " + assetContainer.name);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
AssetDatabase.AddObjectToAsset(_combined, assetContainer);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
isSaved = assetContainer != null;
|
|
||||||
_combined.name = assetName;
|
_combined.name = assetName;
|
||||||
|
|
||||||
|
_deepClone = new DeepClone(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
public AnimatorController Finish()
|
public AnimatorController Finish()
|
||||||
{
|
{
|
||||||
|
PruneEmptyLayers();
|
||||||
|
|
||||||
_combined.parameters = _parameters.Values.ToArray();
|
_combined.parameters = _parameters.Values.ToArray();
|
||||||
_combined.layers = _layers.ToArray();
|
_combined.layers = _layers.ToArray();
|
||||||
return _combined;
|
return _combined;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AddController(string basePath, AnimatorController controller, bool? writeDefaults)
|
private void PruneEmptyLayers()
|
||||||
{
|
{
|
||||||
controllerBaseLayer = _layers.Count;
|
var originalLayers = _layers;
|
||||||
|
int[] layerIndexMappings = new int[originalLayers.Count];
|
||||||
|
|
||||||
|
List<AnimatorControllerLayer> newLayers = new List<AnimatorControllerLayer>();
|
||||||
|
|
||||||
|
for (int i = 0; i < originalLayers.Count; i++)
|
||||||
|
{
|
||||||
|
if (i > 0 && IsEmptyLayer(originalLayers[i]))
|
||||||
|
{
|
||||||
|
layerIndexMappings[i] = -1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
layerIndexMappings[i] = newLayers.Count;
|
||||||
|
newLayers.Add(originalLayers[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var layer in newLayers)
|
||||||
|
{
|
||||||
|
if (layer.stateMachine == null) continue;
|
||||||
|
|
||||||
|
foreach (var asset in layer.stateMachine.ReferencedAssets(includeScene: false))
|
||||||
|
{
|
||||||
|
if (asset is AnimatorState alc)
|
||||||
|
{
|
||||||
|
alc.behaviours = AdjustStateBehaviors(alc.behaviours);
|
||||||
|
}
|
||||||
|
else if (asset is AnimatorStateMachine asm)
|
||||||
|
{
|
||||||
|
asm.behaviours = AdjustStateBehaviors(asm.behaviours);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_layers = newLayers;
|
||||||
|
|
||||||
|
StateMachineBehaviour[] AdjustStateBehaviors(StateMachineBehaviour[] behaviours)
|
||||||
|
{
|
||||||
|
if (behaviours.Length == 0) return behaviours;
|
||||||
|
|
||||||
|
var newBehaviors = new List<StateMachineBehaviour>();
|
||||||
|
foreach (var b in behaviours)
|
||||||
|
{
|
||||||
|
if (b is VRCAnimatorLayerControl alc && alc.playable == BlendableLayer)
|
||||||
|
{
|
||||||
|
int newLayer = -1;
|
||||||
|
if (alc.layer >= 0 && alc.layer < layerIndexMappings.Length)
|
||||||
|
{
|
||||||
|
newLayer = layerIndexMappings[alc.layer];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newLayer != -1)
|
||||||
|
{
|
||||||
|
alc.layer = newLayer;
|
||||||
|
newBehaviors.Add(alc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
newBehaviors.Add(b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return newBehaviors.ToArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsEmptyLayer(AnimatorControllerLayer layer)
|
||||||
|
{
|
||||||
|
if (layer.syncedLayerIndex >= 0) return false;
|
||||||
|
if (layer.avatarMask != null) return false;
|
||||||
|
|
||||||
|
return layer.stateMachine == null
|
||||||
|
|| (layer.stateMachine.states.Length == 0 && layer.stateMachine.stateMachines.Length == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddController(string basePath, AnimatorController controller, bool? writeDefaults,
|
||||||
|
bool forceFirstLayerWeight = false)
|
||||||
|
{
|
||||||
|
_controllerBaseLayer = _layers.Count;
|
||||||
_cloneMap = new Dictionary<Object, Object>();
|
_cloneMap = new Dictionary<Object, Object>();
|
||||||
|
|
||||||
foreach (var param in controller.parameters)
|
foreach (var param in controller.parameters)
|
||||||
@ -102,21 +179,20 @@ namespace nadena.dev.modular_avatar.animation
|
|||||||
{
|
{
|
||||||
if (acp.type != param.type)
|
if (acp.type != param.type)
|
||||||
{
|
{
|
||||||
/*
|
BuildReport.LogFatal("error.merge_animator.param_type_mismatch",
|
||||||
BuildReport.LogFatal("error.merge_animator.param_type_mismatch", new[]
|
param.name,
|
||||||
{
|
acp.type.ToString(),
|
||||||
param.name, acp.type.ToString(),
|
param.type.ToString(),
|
||||||
param.type.ToString()
|
controller,
|
||||||
});
|
_parameterSource[param.name]
|
||||||
*/
|
);
|
||||||
// TODO: Error reporting
|
|
||||||
throw new Exception("Parameter type mismatch");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
_parameters.Add(param.name, param);
|
_parameters.Add(param.name, param);
|
||||||
|
_parameterSource.Add(param.name, controller);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool first = true;
|
bool first = true;
|
||||||
@ -124,6 +200,11 @@ namespace nadena.dev.modular_avatar.animation
|
|||||||
foreach (var layer in layers)
|
foreach (var layer in layers)
|
||||||
{
|
{
|
||||||
insertLayer(basePath, layer, first, writeDefaults, layers);
|
insertLayer(basePath, layer, first, writeDefaults, layers);
|
||||||
|
if (first && forceFirstLayerWeight)
|
||||||
|
{
|
||||||
|
_layers[_layers.Count - 1].defaultWeight = 1;
|
||||||
|
}
|
||||||
|
|
||||||
first = false;
|
first = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -133,14 +214,13 @@ namespace nadena.dev.modular_avatar.animation
|
|||||||
{
|
{
|
||||||
AnimatorController controller = overrideController.runtimeAnimatorController as AnimatorController;
|
AnimatorController controller = overrideController.runtimeAnimatorController as AnimatorController;
|
||||||
if (controller == null) return;
|
if (controller == null) return;
|
||||||
_overrideController = overrideController;
|
_deepClone.OverrideController = overrideController;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
this.AddController(basePath, controller, writeDefaults);
|
this.AddController(basePath, controller, writeDefaults);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
_overrideController = null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -176,24 +256,23 @@ namespace nadena.dev.modular_avatar.animation
|
|||||||
var overrideMotion = layer.GetOverrideMotion(state);
|
var overrideMotion = layer.GetOverrideMotion(state);
|
||||||
if (overrideMotion != null)
|
if (overrideMotion != null)
|
||||||
{
|
{
|
||||||
newLayer.SetOverrideMotion((AnimatorState) _cloneMap[state], overrideMotion);
|
newLayer.SetOverrideMotion((AnimatorState)_cloneMap[state], overrideMotion);
|
||||||
}
|
}
|
||||||
|
|
||||||
var overrideBehaviors = (StateMachineBehaviour[]) layer.GetOverrideBehaviours(state)?.Clone();
|
var overrideBehaviors = (StateMachineBehaviour[])layer.GetOverrideBehaviours(state)?.Clone();
|
||||||
if (overrideBehaviors != null)
|
if (overrideBehaviors != null)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < overrideBehaviors.Length; i++)
|
for (int i = 0; i < overrideBehaviors.Length; i++)
|
||||||
{
|
{
|
||||||
overrideBehaviors[i] = deepClone(overrideBehaviors[i], x => x,
|
overrideBehaviors[i] = _deepClone.DoClone(overrideBehaviors[i]);
|
||||||
new Dictionary<Object, Object>());
|
|
||||||
AdjustBehavior(overrideBehaviors[i]);
|
AdjustBehavior(overrideBehaviors[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
newLayer.SetOverrideBehaviours((AnimatorState) _cloneMap[state], overrideBehaviors);
|
newLayer.SetOverrideBehaviours((AnimatorState)_cloneMap[state], overrideBehaviors);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
newLayer.syncedLayerIndex += controllerBaseLayer;
|
newLayer.syncedLayerIndex += _controllerBaseLayer;
|
||||||
}
|
}
|
||||||
|
|
||||||
_layers.Add(newLayer);
|
_layers.Add(newLayer);
|
||||||
@ -263,7 +342,7 @@ namespace nadena.dev.modular_avatar.animation
|
|||||||
return asm;
|
return asm;
|
||||||
}
|
}
|
||||||
|
|
||||||
asm = deepClone(layerStateMachine, (obj) => customClone(obj, basePath), _cloneMap);
|
asm = _deepClone.DoClone(layerStateMachine, basePath, _cloneMap);
|
||||||
|
|
||||||
foreach (var state in WalkAllStates(asm))
|
foreach (var state in WalkAllStates(asm))
|
||||||
{
|
{
|
||||||
@ -286,166 +365,11 @@ namespace nadena.dev.modular_avatar.animation
|
|||||||
{
|
{
|
||||||
// TODO - need to figure out how to handle cross-layer references. For now this will handle
|
// TODO - need to figure out how to handle cross-layer references. For now this will handle
|
||||||
// intra-animator cases.
|
// intra-animator cases.
|
||||||
layerControl.layer += controllerBaseLayer;
|
layerControl.layer += _controllerBaseLayer;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#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 == "" || clip.IsProxyAnimation()) return clip;
|
|
||||||
|
|
||||||
AnimationClip newClip = new AnimationClip();
|
|
||||||
newClip.name = clip.name;
|
|
||||||
if (isSaved)
|
|
||||||
{
|
|
||||||
AssetDatabase.AddObjectToAsset(newClip, _combined);
|
|
||||||
}
|
|
||||||
|
|
||||||
SerializedObject srcSO = new SerializedObject(clip);
|
|
||||||
SerializedObject destSO = new SerializedObject(newClip);
|
|
||||||
|
|
||||||
SerializedProperty iter = srcSO.GetIterator();
|
|
||||||
|
|
||||||
while (iter.Next(false))
|
|
||||||
{
|
|
||||||
destSO.CopyFromSerializedProperty(iter);
|
|
||||||
}
|
|
||||||
|
|
||||||
destSO.ApplyModifiedPropertiesWithoutUndo();
|
|
||||||
|
|
||||||
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 && _combined != null && EditorUtility.IsPersistent(_combined))
|
|
||||||
{
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,3 +1,3 @@
|
|||||||
fileFormatVersion: 2
|
fileFormatVersion: 2
|
||||||
guid: 00241ec2d86cdae4bb5076af0699b33a
|
guid: 614457d82b1a4b109788029754c9fc1a
|
||||||
timeCreated: 1691238359
|
timeCreated: 1703674134
|
@ -10,20 +10,20 @@ using BuildContext = nadena.dev.ndmf.BuildContext;
|
|||||||
namespace nadena.dev.modular_avatar.animation
|
namespace nadena.dev.modular_avatar.animation
|
||||||
{
|
{
|
||||||
using UnityObject = UnityEngine.Object;
|
using UnityObject = UnityEngine.Object;
|
||||||
|
|
||||||
internal class DeepClone
|
internal class DeepClone
|
||||||
{
|
{
|
||||||
private bool _isSaved;
|
private bool _isSaved;
|
||||||
private UnityObject _combined;
|
private UnityObject _combined;
|
||||||
|
|
||||||
public AnimatorOverrideController OverrideController { get; set; }
|
public AnimatorOverrideController OverrideController { get; set; }
|
||||||
|
|
||||||
public DeepClone(BuildContext context)
|
public DeepClone(BuildContext context)
|
||||||
{
|
{
|
||||||
_isSaved = context.AssetContainer != null;
|
_isSaved = context.AssetContainer != null && EditorUtility.IsPersistent(context.AssetContainer);
|
||||||
_combined = context.AssetContainer;
|
_combined = context.AssetContainer;
|
||||||
}
|
}
|
||||||
|
|
||||||
public T DoClone<T>(T original,
|
public T DoClone<T>(T original,
|
||||||
string basePath = null,
|
string basePath = null,
|
||||||
Dictionary<UnityObject, UnityObject> cloneMap = null
|
Dictionary<UnityObject, UnityObject> cloneMap = null
|
||||||
@ -79,7 +79,7 @@ namespace nadena.dev.modular_avatar.animation
|
|||||||
|
|
||||||
if (cloneMap.ContainsKey(original))
|
if (cloneMap.ContainsKey(original))
|
||||||
{
|
{
|
||||||
return (T) cloneMap[original];
|
return (T)cloneMap[original];
|
||||||
}
|
}
|
||||||
|
|
||||||
var obj = visitor?.Invoke(original);
|
var obj = visitor?.Invoke(original);
|
||||||
@ -90,7 +90,8 @@ namespace nadena.dev.modular_avatar.animation
|
|||||||
{
|
{
|
||||||
ObjectRegistry.RegisterReplacedObject(original, obj);
|
ObjectRegistry.RegisterReplacedObject(original, obj);
|
||||||
}
|
}
|
||||||
return (T) obj;
|
|
||||||
|
return (T)obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
var ctor = original.GetType().GetConstructor(Type.EmptyTypes);
|
var ctor = original.GetType().GetConstructor(Type.EmptyTypes);
|
||||||
@ -100,7 +101,7 @@ namespace nadena.dev.modular_avatar.animation
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
obj = (T) ctor.Invoke(Array.Empty<object>());
|
obj = (T)ctor.Invoke(Array.Empty<object>());
|
||||||
EditorUtility.CopySerialized(original, obj);
|
EditorUtility.CopySerialized(original, obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -136,9 +137,9 @@ namespace nadena.dev.modular_avatar.animation
|
|||||||
|
|
||||||
so.ApplyModifiedPropertiesWithoutUndo();
|
so.ApplyModifiedPropertiesWithoutUndo();
|
||||||
|
|
||||||
return (T) obj;
|
return (T)obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
private UnityObject CloneWithPathMapping(UnityObject o, string basePath)
|
private UnityObject CloneWithPathMapping(UnityObject o, string basePath)
|
||||||
{
|
{
|
||||||
if (o is AnimationClip clip)
|
if (o is AnimationClip clip)
|
||||||
@ -188,7 +189,7 @@ namespace nadena.dev.modular_avatar.animation
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string MapPath(EditorCurveBinding binding, string basePath)
|
private static string MapPath(EditorCurveBinding binding, string basePath)
|
||||||
{
|
{
|
||||||
if (binding.type == typeof(Animator) && binding.path == "")
|
if (binding.type == typeof(Animator) && binding.path == "")
|
||||||
|
@ -1,370 +0,0 @@
|
|||||||
/*
|
|
||||||
* MIT License
|
|
||||||
*
|
|
||||||
* Copyright (c) 2022 bd_
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
|
||||||
* in the Software without restriction, including without limitation the rights
|
|
||||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
* copies of the Software, and to permit persons to whom the Software is
|
|
||||||
* furnished to do so, subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be included in all
|
|
||||||
* copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
* SOFTWARE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using nadena.dev.modular_avatar.animation;
|
|
||||||
using nadena.dev.modular_avatar.editor.ErrorReporting;
|
|
||||||
using nadena.dev.ndmf.util;
|
|
||||||
using UnityEditor;
|
|
||||||
using UnityEditor.Animations;
|
|
||||||
using UnityEngine;
|
|
||||||
using VRC.SDKBase;
|
|
||||||
|
|
||||||
#if MA_VRCSDK3_AVATARS
|
|
||||||
using VRC.SDK3.Avatars.Components;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
using Object = UnityEngine.Object;
|
|
||||||
|
|
||||||
namespace nadena.dev.modular_avatar.core.editor
|
|
||||||
{
|
|
||||||
internal class AnimatorCombiner
|
|
||||||
{
|
|
||||||
private readonly AnimatorController _combined;
|
|
||||||
private bool isSaved;
|
|
||||||
|
|
||||||
private DeepClone _deepClone;
|
|
||||||
|
|
||||||
private AnimatorOverrideController _overrideController;
|
|
||||||
|
|
||||||
private List<AnimatorControllerLayer> _layers = new List<AnimatorControllerLayer>();
|
|
||||||
|
|
||||||
private Dictionary<String, AnimatorControllerParameter> _parameters =
|
|
||||||
new Dictionary<string, AnimatorControllerParameter>();
|
|
||||||
|
|
||||||
private Dictionary<KeyValuePair<String, Motion>, Motion> _motions =
|
|
||||||
new Dictionary<KeyValuePair<string, Motion>, Motion>();
|
|
||||||
|
|
||||||
private Dictionary<KeyValuePair<String, AnimatorStateMachine>, AnimatorStateMachine> _stateMachines =
|
|
||||||
new Dictionary<KeyValuePair<string, AnimatorStateMachine>, AnimatorStateMachine>();
|
|
||||||
|
|
||||||
private Dictionary<Object, Object> _cloneMap;
|
|
||||||
|
|
||||||
private int controllerBaseLayer = 0;
|
|
||||||
|
|
||||||
public VRC_AnimatorLayerControl.BlendableLayer? BlendableLayer;
|
|
||||||
|
|
||||||
public AnimatorCombiner(BuildContext context, String assetName)
|
|
||||||
{
|
|
||||||
_combined = context.CreateAnimator();
|
|
||||||
isSaved = !string.IsNullOrEmpty(AssetDatabase.GetAssetPath(_combined));
|
|
||||||
_combined.name = assetName;
|
|
||||||
|
|
||||||
_deepClone = new DeepClone(context.PluginBuildContext);
|
|
||||||
}
|
|
||||||
|
|
||||||
public AnimatorController Finish()
|
|
||||||
{
|
|
||||||
PruneEmptyLayers();
|
|
||||||
|
|
||||||
_combined.parameters = _parameters.Values.ToArray();
|
|
||||||
_combined.layers = _layers.ToArray();
|
|
||||||
return _combined;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void PruneEmptyLayers()
|
|
||||||
{
|
|
||||||
var originalLayers = _layers;
|
|
||||||
int[] layerIndexMappings = new int[originalLayers.Count];
|
|
||||||
|
|
||||||
List<AnimatorControllerLayer> newLayers = new List<AnimatorControllerLayer>();
|
|
||||||
|
|
||||||
for (int i = 0; i < originalLayers.Count; i++)
|
|
||||||
{
|
|
||||||
if (i > 0 && IsEmptyLayer(originalLayers[i]))
|
|
||||||
{
|
|
||||||
layerIndexMappings[i] = -1;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
layerIndexMappings[i] = newLayers.Count;
|
|
||||||
newLayers.Add(originalLayers[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var layer in newLayers)
|
|
||||||
{
|
|
||||||
if (layer.stateMachine == null) continue;
|
|
||||||
|
|
||||||
foreach (var asset in layer.stateMachine.ReferencedAssets(includeScene: false))
|
|
||||||
{
|
|
||||||
if (asset is AnimatorState alc)
|
|
||||||
{
|
|
||||||
alc.behaviours = AdjustStateBehaviors(alc.behaviours);
|
|
||||||
}
|
|
||||||
else if (asset is AnimatorStateMachine asm)
|
|
||||||
{
|
|
||||||
asm.behaviours = AdjustStateBehaviors(asm.behaviours);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_layers = newLayers;
|
|
||||||
|
|
||||||
StateMachineBehaviour[] AdjustStateBehaviors(StateMachineBehaviour[] behaviours)
|
|
||||||
{
|
|
||||||
if (behaviours.Length == 0) return behaviours;
|
|
||||||
|
|
||||||
var newBehaviors = new List<StateMachineBehaviour>();
|
|
||||||
foreach (var b in behaviours)
|
|
||||||
{
|
|
||||||
if (b is VRCAnimatorLayerControl alc && alc.playable == BlendableLayer)
|
|
||||||
{
|
|
||||||
int newLayer = -1;
|
|
||||||
if (alc.layer >= 0 && alc.layer < layerIndexMappings.Length)
|
|
||||||
{
|
|
||||||
newLayer = layerIndexMappings[alc.layer];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (newLayer != -1)
|
|
||||||
{
|
|
||||||
alc.layer = newLayer;
|
|
||||||
newBehaviors.Add(alc);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
newBehaviors.Add(b);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return newBehaviors.ToArray();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool IsEmptyLayer(AnimatorControllerLayer layer)
|
|
||||||
{
|
|
||||||
if (layer.syncedLayerIndex >= 0) return false;
|
|
||||||
if (layer.avatarMask != null) return false;
|
|
||||||
|
|
||||||
return layer.stateMachine == null
|
|
||||||
|| (layer.stateMachine.states.Length == 0 && layer.stateMachine.stateMachines.Length == 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void AddController(string basePath, AnimatorController controller, bool? writeDefaults,
|
|
||||||
bool forceFirstLayerWeight = false)
|
|
||||||
{
|
|
||||||
controllerBaseLayer = _layers.Count;
|
|
||||||
_cloneMap = new Dictionary<Object, Object>();
|
|
||||||
|
|
||||||
foreach (var param in controller.parameters)
|
|
||||||
{
|
|
||||||
if (_parameters.TryGetValue(param.name, out var acp))
|
|
||||||
{
|
|
||||||
if (acp.type != param.type)
|
|
||||||
{
|
|
||||||
BuildReport.LogFatal("error.merge_animator.param_type_mismatch", new[]
|
|
||||||
{
|
|
||||||
param.name, acp.type.ToString(),
|
|
||||||
param.type.ToString()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
_parameters.Add(param.name, param);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool first = true;
|
|
||||||
var layers = controller.layers;
|
|
||||||
foreach (var layer in layers)
|
|
||||||
{
|
|
||||||
insertLayer(basePath, layer, first, writeDefaults, layers);
|
|
||||||
if (first && forceFirstLayerWeight)
|
|
||||||
{
|
|
||||||
_layers[_layers.Count - 1].defaultWeight = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
first = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void AddOverrideController(string basePath, AnimatorOverrideController overrideController,
|
|
||||||
bool? writeDefaults)
|
|
||||||
{
|
|
||||||
AnimatorController controller = overrideController.runtimeAnimatorController as AnimatorController;
|
|
||||||
if (controller == null) return;
|
|
||||||
_deepClone.OverrideController = overrideController;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
this.AddController(basePath, controller, writeDefaults);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
_overrideController = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void insertLayer(
|
|
||||||
string basePath,
|
|
||||||
AnimatorControllerLayer layer,
|
|
||||||
bool first,
|
|
||||||
bool? writeDefaults,
|
|
||||||
AnimatorControllerLayer[] layers
|
|
||||||
)
|
|
||||||
{
|
|
||||||
var newLayer = new AnimatorControllerLayer()
|
|
||||||
{
|
|
||||||
name = layer.name,
|
|
||||||
avatarMask = layer.avatarMask, // TODO map transforms
|
|
||||||
blendingMode = layer.blendingMode,
|
|
||||||
defaultWeight = first ? 1 : layer.defaultWeight,
|
|
||||||
syncedLayerIndex = layer.syncedLayerIndex,
|
|
||||||
syncedLayerAffectsTiming = layer.syncedLayerAffectsTiming,
|
|
||||||
iKPass = layer.iKPass,
|
|
||||||
stateMachine = mapStateMachine(basePath, layer.stateMachine),
|
|
||||||
};
|
|
||||||
|
|
||||||
UpdateWriteDefaults(newLayer.stateMachine, writeDefaults);
|
|
||||||
|
|
||||||
if (newLayer.syncedLayerIndex != -1 && newLayer.syncedLayerIndex >= 0 &&
|
|
||||||
newLayer.syncedLayerIndex < layers.Length)
|
|
||||||
{
|
|
||||||
// Transfer any motion overrides onto the new synced layer
|
|
||||||
var baseLayer = layers[newLayer.syncedLayerIndex];
|
|
||||||
foreach (var state in WalkAllStates(baseLayer.stateMachine))
|
|
||||||
{
|
|
||||||
var overrideMotion = layer.GetOverrideMotion(state);
|
|
||||||
if (overrideMotion != null)
|
|
||||||
{
|
|
||||||
newLayer.SetOverrideMotion((AnimatorState)_cloneMap[state], overrideMotion);
|
|
||||||
}
|
|
||||||
|
|
||||||
var overrideBehaviors = (StateMachineBehaviour[])layer.GetOverrideBehaviours(state)?.Clone();
|
|
||||||
if (overrideBehaviors != null)
|
|
||||||
{
|
|
||||||
for (int i = 0; i < overrideBehaviors.Length; i++)
|
|
||||||
{
|
|
||||||
overrideBehaviors[i] = _deepClone.DoClone(overrideBehaviors[i]);
|
|
||||||
AdjustBehavior(overrideBehaviors[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
newLayer.SetOverrideBehaviours((AnimatorState)_cloneMap[state], overrideBehaviors);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
newLayer.syncedLayerIndex += controllerBaseLayer;
|
|
||||||
}
|
|
||||||
|
|
||||||
_layers.Add(newLayer);
|
|
||||||
}
|
|
||||||
|
|
||||||
IEnumerable<AnimatorState> WalkAllStates(AnimatorStateMachine animatorStateMachine)
|
|
||||||
{
|
|
||||||
HashSet<Object> visited = new HashSet<Object>();
|
|
||||||
|
|
||||||
foreach (var state in VisitStateMachine(animatorStateMachine))
|
|
||||||
{
|
|
||||||
yield return state;
|
|
||||||
}
|
|
||||||
|
|
||||||
IEnumerable<AnimatorState> VisitStateMachine(AnimatorStateMachine layerStateMachine)
|
|
||||||
{
|
|
||||||
if (!visited.Add(layerStateMachine)) yield break;
|
|
||||||
|
|
||||||
foreach (var state in layerStateMachine.states)
|
|
||||||
{
|
|
||||||
if (state.state == null) continue;
|
|
||||||
|
|
||||||
yield return state.state;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var child in layerStateMachine.stateMachines)
|
|
||||||
{
|
|
||||||
if (child.stateMachine == null) continue;
|
|
||||||
|
|
||||||
if (visited.Contains(child.stateMachine)) continue;
|
|
||||||
visited.Add(child.stateMachine);
|
|
||||||
foreach (var state in VisitStateMachine(child.stateMachine))
|
|
||||||
{
|
|
||||||
yield return state;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void UpdateWriteDefaults(AnimatorStateMachine stateMachine, bool? writeDefaults)
|
|
||||||
{
|
|
||||||
if (!writeDefaults.HasValue) return;
|
|
||||||
|
|
||||||
var queue = new Queue<AnimatorStateMachine>();
|
|
||||||
queue.Enqueue(stateMachine);
|
|
||||||
while (queue.Count > 0)
|
|
||||||
{
|
|
||||||
var sm = queue.Dequeue();
|
|
||||||
foreach (var state in sm.states)
|
|
||||||
{
|
|
||||||
state.state.writeDefaultValues = writeDefaults.Value;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var child in sm.stateMachines)
|
|
||||||
{
|
|
||||||
queue.Enqueue(child.stateMachine);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private AnimatorStateMachine mapStateMachine(string basePath, AnimatorStateMachine layerStateMachine)
|
|
||||||
{
|
|
||||||
var cacheKey = new KeyValuePair<string, AnimatorStateMachine>(basePath, layerStateMachine);
|
|
||||||
|
|
||||||
if (_stateMachines.TryGetValue(cacheKey, out var asm))
|
|
||||||
{
|
|
||||||
return asm;
|
|
||||||
}
|
|
||||||
|
|
||||||
asm = _deepClone.DoClone(layerStateMachine, basePath, _cloneMap);
|
|
||||||
|
|
||||||
foreach (var state in WalkAllStates(asm))
|
|
||||||
{
|
|
||||||
foreach (var behavior in state.behaviours)
|
|
||||||
{
|
|
||||||
AdjustBehavior(behavior);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_stateMachines[cacheKey] = asm;
|
|
||||||
return asm;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void AdjustBehavior(StateMachineBehaviour behavior)
|
|
||||||
{
|
|
||||||
#if MA_VRCSDK3_AVATARS
|
|
||||||
switch (behavior)
|
|
||||||
{
|
|
||||||
case VRCAnimatorLayerControl layerControl:
|
|
||||||
{
|
|
||||||
// TODO - need to figure out how to handle cross-layer references. For now this will handle
|
|
||||||
// intra-animator cases.
|
|
||||||
layerControl.layer += controllerBaseLayer;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,3 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: 3c76e0db714645f7aa6580f208f98e49
|
|
||||||
timeCreated: 1661644852
|
|
@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using nadena.dev.modular_avatar.animation;
|
using nadena.dev.modular_avatar.animation;
|
||||||
|
using nadena.dev.ndmf;
|
||||||
using UnityEditor;
|
using UnityEditor;
|
||||||
using UnityEditor.Animations;
|
using UnityEditor.Animations;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
@ -94,7 +95,7 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
{
|
{
|
||||||
if (controller == null) return null;
|
if (controller == null) return null;
|
||||||
|
|
||||||
var merger = new AnimatorCombiner(this, controller.name + " (clone)");
|
var merger = new AnimatorCombiner(PluginBuildContext, controller.name + " (clone)");
|
||||||
switch (controller)
|
switch (controller)
|
||||||
{
|
{
|
||||||
case AnimatorController ac:
|
case AnimatorController ac:
|
||||||
@ -107,12 +108,16 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
throw new Exception("Unknown RuntimeAnimatorContoller type " + controller.GetType());
|
throw new Exception("Unknown RuntimeAnimatorContoller type " + controller.GetType());
|
||||||
}
|
}
|
||||||
|
|
||||||
return merger.Finish();
|
var result = merger.Finish();
|
||||||
|
|
||||||
|
ObjectRegistry.RegisterReplacedObject(controller, result);
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public AnimatorController ConvertAnimatorController(AnimatorOverrideController overrideController)
|
public AnimatorController ConvertAnimatorController(AnimatorOverrideController overrideController)
|
||||||
{
|
{
|
||||||
var merger = new AnimatorCombiner(this, overrideController.name + " (clone)");
|
var merger = new AnimatorCombiner(PluginBuildContext, overrideController.name + " (clone)");
|
||||||
merger.AddOverrideController("", overrideController, null);
|
merger.AddOverrideController("", overrideController, null);
|
||||||
return merger.Finish();
|
return merger.Finish();
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using nadena.dev.modular_avatar.editor.ErrorReporting;
|
using nadena.dev.modular_avatar.animation;
|
||||||
using UnityEditor;
|
using UnityEditor;
|
||||||
using UnityEditor.Animations;
|
using UnityEditor.Animations;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
@ -108,7 +108,7 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
var afterOriginal = sorted.Where(x => x.layerPriority >= 0)
|
var afterOriginal = sorted.Where(x => x.layerPriority >= 0)
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
var session = new AnimatorCombiner(context, layerType.ToString() + " (merged)");
|
var session = new AnimatorCombiner(context.PluginBuildContext, layerType.ToString() + " (merged)");
|
||||||
mergeSessions[layerType] = session;
|
mergeSessions[layerType] = session;
|
||||||
mergeSessions[layerType].BlendableLayer = BlendableLayerFor(layerType);
|
mergeSessions[layerType].BlendableLayer = BlendableLayerFor(layerType);
|
||||||
|
|
||||||
@ -194,7 +194,8 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
{
|
{
|
||||||
// For non-default layers, ensure we always clone the controller for the benefit of subsequent
|
// For non-default layers, ensure we always clone the controller for the benefit of subsequent
|
||||||
// processing phases
|
// processing phases
|
||||||
mergeSessions[layer.type] = new AnimatorCombiner(_context, layer.type.ToString());
|
mergeSessions[layer.type] =
|
||||||
|
new AnimatorCombiner(_context.PluginBuildContext, layer.type.ToString());
|
||||||
mergeSessions[layer.type].BlendableLayer = BlendableLayerFor(layer.type);
|
mergeSessions[layer.type].BlendableLayer = BlendableLayerFor(layer.type);
|
||||||
mergeSessions[layer.type].AddController("", controller, null);
|
mergeSessions[layer.type].AddController("", controller, null);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user