mirror of
https://github.com/bdunderscore/modular-avatar.git
synced 2025-05-11 21:59:02 +08:00
add basic support for toggle groups
This commit is contained in:
parent
d8d4942b5d
commit
42f1fcfd59
@ -208,16 +208,48 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
List<VRCExpressionParameters.Parameter> expParameters = expParams.parameters.ToList();
|
List<VRCExpressionParameters.Parameter> expParameters = expParams.parameters.ToList();
|
||||||
List<BlendTree> blendTrees = new List<BlendTree>();
|
List<BlendTree> blendTrees = new List<BlendTree>();
|
||||||
|
|
||||||
int index = 0;
|
Dictionary<Component, List<ModularAvatarMenuItem>> groupedItems =
|
||||||
|
new Dictionary<Component, List<ModularAvatarMenuItem>>();
|
||||||
|
|
||||||
foreach (var item in items)
|
foreach (var item in items)
|
||||||
{
|
{
|
||||||
var paramname = "_MA/A/" + item.gameObject.name + "/" + (index++);
|
List<ModularAvatarMenuItem> group;
|
||||||
// TODO toggle group handling
|
if (item.toggleGroup)
|
||||||
|
{
|
||||||
|
if (!groupedItems.TryGetValue(item.toggleGroup, out group))
|
||||||
|
{
|
||||||
|
group = new List<ModularAvatarMenuItem>();
|
||||||
|
groupedItems.Add(item.toggleGroup, group);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
group = new List<ModularAvatarMenuItem>();
|
||||||
|
groupedItems.Add(item, group);
|
||||||
|
}
|
||||||
|
|
||||||
|
group.Add(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
int paramIndex = 0;
|
||||||
|
foreach (var kvp in groupedItems)
|
||||||
|
{
|
||||||
|
// sort default first
|
||||||
|
var group = kvp.Value;
|
||||||
|
group.Sort((a, b) => b.isDefault.CompareTo(a.isDefault));
|
||||||
|
|
||||||
|
// Generate parameter
|
||||||
|
var paramname = "_MA/A/" + kvp.Key.gameObject.name + "/" + (paramIndex++);
|
||||||
|
|
||||||
|
var expParamType = group.Count > 1
|
||||||
|
? VRCExpressionParameters.ValueType.Int
|
||||||
|
: VRCExpressionParameters.ValueType.Bool;
|
||||||
|
|
||||||
expParameters.Add(new VRCExpressionParameters.Parameter()
|
expParameters.Add(new VRCExpressionParameters.Parameter()
|
||||||
{
|
{
|
||||||
name = paramname,
|
name = paramname,
|
||||||
defaultValue = 0, // TODO
|
defaultValue = 0, // TODO
|
||||||
valueType = VRCExpressionParameters.ValueType.Bool,
|
valueType = expParamType,
|
||||||
saved = false, // TODO
|
saved = false, // TODO
|
||||||
});
|
});
|
||||||
acParameters.Add(new AnimatorControllerParameter()
|
acParameters.Add(new AnimatorControllerParameter()
|
||||||
@ -227,31 +259,48 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
defaultFloat = 0, // TODO
|
defaultFloat = 0, // TODO
|
||||||
});
|
});
|
||||||
|
|
||||||
item.Control.parameter = new VRCExpressionsMenu.Control.Parameter() {name = paramname};
|
var hasDefault = group[0].isDefault;
|
||||||
item.Control.value = 1;
|
for (int i = 0; i < group.Count; i++)
|
||||||
|
{
|
||||||
|
var control = group[i].Control;
|
||||||
|
control.parameter = new VRCExpressionsMenu.Control.Parameter() {name = paramname};
|
||||||
|
control.value = hasDefault ? i : i + 1;
|
||||||
|
}
|
||||||
|
|
||||||
var blendTree = new BlendTree();
|
var blendTree = new BlendTree();
|
||||||
blendTree.name = paramname;
|
blendTree.name = paramname;
|
||||||
blendTree.blendParameter = paramname;
|
blendTree.blendParameter = paramname;
|
||||||
blendTree.blendType = BlendTreeType.Simple1D;
|
blendTree.blendType = BlendTreeType.Simple1D;
|
||||||
blendTree.useAutomaticThresholds = false;
|
blendTree.useAutomaticThresholds = false;
|
||||||
blendTree.children = new[]
|
|
||||||
|
List<ChildMotion> children = new List<ChildMotion>();
|
||||||
|
|
||||||
|
List<Motion> motions = GenerateMotions(group, out var inactiveMotion);
|
||||||
|
|
||||||
|
if (!hasDefault)
|
||||||
{
|
{
|
||||||
new ChildMotion()
|
motions.Insert(0, inactiveMotion);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < motions.Count; i++)
|
||||||
|
{
|
||||||
|
children.Add(new ChildMotion()
|
||||||
{
|
{
|
||||||
motion = GenerateMotion(item, false),
|
motion = motions[i],
|
||||||
position = new Vector2(0, 0),
|
position = new Vector2(i, 0),
|
||||||
threshold = 0.25f,
|
threshold = i - 0.1f,
|
||||||
timeScale = 1,
|
timeScale = 1,
|
||||||
},
|
});
|
||||||
new ChildMotion()
|
children.Add(new ChildMotion()
|
||||||
{
|
{
|
||||||
motion = GenerateMotion(item, true),
|
motion = motions[i],
|
||||||
position = new Vector2(1, 0),
|
position = new Vector2(i, 0),
|
||||||
threshold = 0.75f,
|
threshold = i + 0.1f,
|
||||||
timeScale = 1,
|
timeScale = 1,
|
||||||
},
|
});
|
||||||
};
|
}
|
||||||
|
|
||||||
|
blendTree.children = children.ToArray();
|
||||||
|
|
||||||
_context.SaveAsset(blendTree);
|
_context.SaveAsset(blendTree);
|
||||||
blendTrees.Add(blendTree);
|
blendTrees.Add(blendTree);
|
||||||
@ -263,18 +312,16 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
return blendTrees;
|
return blendTrees;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Motion GenerateMotion(ModularAvatarMenuItem item, bool active)
|
void MergeCurves(
|
||||||
|
IDictionary<MenuCurveBinding, (Component, AnimationCurve)> curves,
|
||||||
|
ModularAvatarMenuItem item,
|
||||||
|
Func<MenuAction, IDictionary<MenuCurveBinding, AnimationCurve>> getCurves,
|
||||||
|
bool ignoreDuplicates
|
||||||
|
)
|
||||||
{
|
{
|
||||||
AnimationClip clip = new AnimationClip();
|
|
||||||
_context.SaveAsset(clip);
|
|
||||||
clip.name = item.gameObject.name + (active ? " (On)" : " (Off)");
|
|
||||||
|
|
||||||
Dictionary<MenuCurveBinding, (Component, AnimationCurve)> curves =
|
|
||||||
new Dictionary<MenuCurveBinding, (Component, AnimationCurve)>();
|
|
||||||
|
|
||||||
foreach (var action in item.GetComponents<MenuAction>())
|
foreach (var action in item.GetComponents<MenuAction>())
|
||||||
{
|
{
|
||||||
var newCurves = active ? action.GetCurves() : action.GetDefaultCurves();
|
var newCurves = getCurves(action);
|
||||||
|
|
||||||
foreach (var curvePair in newCurves)
|
foreach (var curvePair in newCurves)
|
||||||
{
|
{
|
||||||
@ -283,7 +330,7 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
|
|
||||||
if (curves.TryGetValue(binding, out var existing))
|
if (curves.TryGetValue(binding, out var existing))
|
||||||
{
|
{
|
||||||
if (active)
|
if (!ignoreDuplicates)
|
||||||
{
|
{
|
||||||
BuildReport.LogFatal("animation_gen.conflict", new object[]
|
BuildReport.LogFatal("animation_gen.conflict", new object[]
|
||||||
{
|
{
|
||||||
@ -301,7 +348,67 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<Motion> GenerateMotions(List<ModularAvatarMenuItem> items, out Motion inactiveMotion)
|
||||||
|
{
|
||||||
|
Dictionary<MenuCurveBinding, (Component, AnimationCurve)> inactiveCurves =
|
||||||
|
new Dictionary<MenuCurveBinding, (Component, AnimationCurve)>();
|
||||||
|
|
||||||
|
var defaultItems = items.Where(i => i.isDefault).ToList();
|
||||||
|
if (defaultItems.Count > 1)
|
||||||
|
{
|
||||||
|
BuildReport.LogFatal("animation_gen.multiple_defaults", Array.Empty<object>(),
|
||||||
|
defaultItems.ToArray<UnityEngine.Object>());
|
||||||
|
defaultItems.RemoveRange(1, defaultItems.Count - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
MergeCurves(inactiveCurves, defaultItems[0], a => a.GetInactiveCurves(true), false);
|
||||||
|
|
||||||
|
foreach (var item in items)
|
||||||
|
{
|
||||||
|
if (defaultItems.Count == 0 || defaultItems[0] != item)
|
||||||
|
{
|
||||||
|
MergeCurves(inactiveCurves, item, a => a.GetInactiveCurves(false), true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inactiveMotion = CurvesToMotion(inactiveCurves);
|
||||||
|
var groupName = (items[0].toggleGroup != null
|
||||||
|
? items[0].toggleGroup.gameObject.name
|
||||||
|
: items[0].gameObject.name);
|
||||||
|
inactiveMotion.name =
|
||||||
|
groupName
|
||||||
|
+ " (Inactive)";
|
||||||
|
|
||||||
|
List<Motion> motions = new List<Motion>();
|
||||||
|
|
||||||
|
foreach (var item in items)
|
||||||
|
{
|
||||||
|
Dictionary<MenuCurveBinding, (Component, AnimationCurve)> activeCurves =
|
||||||
|
new Dictionary<MenuCurveBinding, (Component, AnimationCurve)>();
|
||||||
|
|
||||||
|
MergeCurves(activeCurves, item, a => a.GetCurves(), false);
|
||||||
|
foreach (var kvp in inactiveCurves)
|
||||||
|
{
|
||||||
|
if (!activeCurves.ContainsKey(kvp.Key))
|
||||||
|
{
|
||||||
|
activeCurves.Add(kvp.Key, kvp.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var clip = CurvesToMotion(activeCurves);
|
||||||
|
clip.name = groupName + " (" + item.gameObject.name + ")";
|
||||||
|
motions.Add(clip);
|
||||||
|
}
|
||||||
|
|
||||||
|
return motions;
|
||||||
|
}
|
||||||
|
|
||||||
|
Motion CurvesToMotion(IDictionary<MenuCurveBinding, (Component, AnimationCurve)> curves)
|
||||||
|
{
|
||||||
|
var clip = new AnimationClip();
|
||||||
|
_context.SaveAsset(clip);
|
||||||
foreach (var entry in curves)
|
foreach (var entry in curves)
|
||||||
{
|
{
|
||||||
clip.SetCurve(
|
clip.SetCurve(
|
||||||
|
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
|||||||
using System.Collections.Immutable;
|
using System.Collections.Immutable;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
using Object = System.Object;
|
||||||
|
|
||||||
namespace nadena.dev.modular_avatar.core
|
namespace nadena.dev.modular_avatar.core
|
||||||
{
|
{
|
||||||
@ -46,8 +47,18 @@ namespace nadena.dev.modular_avatar.core
|
|||||||
|
|
||||||
public interface MenuAction
|
public interface MenuAction
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the curves applied when this action is active
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
ImmutableDictionary<MenuCurveBinding, AnimationCurve> GetCurves();
|
ImmutableDictionary<MenuCurveBinding, AnimationCurve> GetCurves();
|
||||||
ImmutableDictionary<MenuCurveBinding, AnimationCurve> GetDefaultCurves();
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the curves applied when this action is inactive (and no other actions override).
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="isDefault">True if this action is part of the default toggle option.</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
ImmutableDictionary<MenuCurveBinding, AnimationCurve> GetInactiveCurves(bool isDefault);
|
||||||
}
|
}
|
||||||
|
|
||||||
[RequireComponent(typeof(ModularAvatarMenuItem))]
|
[RequireComponent(typeof(ModularAvatarMenuItem))]
|
||||||
@ -71,12 +82,24 @@ namespace nadena.dev.modular_avatar.core
|
|||||||
).ToImmutableDictionary();
|
).ToImmutableDictionary();
|
||||||
}
|
}
|
||||||
|
|
||||||
public ImmutableDictionary<MenuCurveBinding, AnimationCurve> GetDefaultCurves()
|
public ImmutableDictionary<MenuCurveBinding, AnimationCurve> GetInactiveCurves(bool isDefault)
|
||||||
{
|
{
|
||||||
return Objects.Select(obj =>
|
return Objects.Select(obj =>
|
||||||
new KeyValuePair<MenuCurveBinding, AnimationCurve>(
|
{
|
||||||
new MenuCurveBinding(obj.target, typeof(GameObject), "m_IsActive"),
|
bool active;
|
||||||
AnimationCurve.Constant(0, 1, obj.target.activeSelf ? 1 : 0))
|
if (isDefault)
|
||||||
|
{
|
||||||
|
active = !obj.Active; // inactive state is the opposite of the default state
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
active = obj.target.activeSelf; // inactive state is the current state
|
||||||
|
}
|
||||||
|
|
||||||
|
return new KeyValuePair<MenuCurveBinding, AnimationCurve>(
|
||||||
|
new MenuCurveBinding(obj.target, typeof(GameObject), "m_IsActive"),
|
||||||
|
AnimationCurve.Constant(0, 1, active ? 1 : 0));
|
||||||
|
}
|
||||||
).ToImmutableDictionary();
|
).ToImmutableDictionary();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,9 @@ namespace nadena.dev.modular_avatar.core
|
|||||||
|
|
||||||
public GameObject menuSource_otherObjectChildren;
|
public GameObject menuSource_otherObjectChildren;
|
||||||
|
|
||||||
|
public ToggleGroup toggleGroup;
|
||||||
|
public bool isDefault;
|
||||||
|
|
||||||
public override void Visit(NodeContext context)
|
public override void Visit(NodeContext context)
|
||||||
{
|
{
|
||||||
var cloned = new VirtualControl(Control);
|
var cloned = new VirtualControl(Control);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user