mirror of
https://github.com/bdunderscore/modular-avatar.git
synced 2025-01-31 02:32:53 +08:00
rollback: remove action system for 1.5.0 release
This commit is contained in:
parent
ababe2e80a
commit
bf42cb95c1
@ -191,7 +191,6 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
|
||||
var context = new BuildContext(vrcAvatarDescriptor);
|
||||
|
||||
new ActionGenerator(context).OnPreprocessAvatar(vrcAvatarDescriptor);
|
||||
new RenameParametersHook().OnPreprocessAvatar(avatarGameObject, context);
|
||||
new MergeAnimatorProcessor().OnPreprocessAvatar(avatarGameObject, context);
|
||||
context.AnimationDatabase.Bootstrap(vrcAvatarDescriptor);
|
||||
|
@ -8,12 +8,6 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
{
|
||||
public class EasySetupOutfit
|
||||
{
|
||||
private static readonly ControlGroup CLOTHING_GROUP
|
||||
= Util.LoadAssetByGuid<GameObject>("e451e988456f35b49a3d011d780bda07")?.GetComponent<ControlGroup>();
|
||||
|
||||
private static readonly VRCExpressionsMenu CLOTHING_MENU
|
||||
= Util.LoadAssetByGuid<VRCExpressionsMenu>("2fe0aa7ecd6bc4443bade672c978f59d");
|
||||
|
||||
private const int PRIORITY = 49;
|
||||
|
||||
[MenuItem("GameObject/ModularAvatar/Setup Outfit", false, PRIORITY)]
|
||||
@ -34,30 +28,6 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
merge.InferPrefixSuffix();
|
||||
HeuristicBoneMapper.RenameBonesByHeuristic(merge);
|
||||
}
|
||||
|
||||
if (CLOTHING_MENU == null || CLOTHING_GROUP == null) return;
|
||||
|
||||
var outfitObject = (GameObject) cmd.context;
|
||||
|
||||
if (outfitObject.GetComponent<ModularAvatarMenuInstaller>() == null)
|
||||
{
|
||||
var installer = Undo.AddComponent<ModularAvatarMenuInstaller>(outfitObject);
|
||||
installer.installTargetMenu = CLOTHING_MENU;
|
||||
|
||||
var menuItem = Undo.AddComponent<ModularAvatarMenuItem>(outfitObject);
|
||||
menuItem.Control.type = VRCExpressionsMenu.Control.ControlType.Toggle;
|
||||
menuItem.controlGroup = CLOTHING_GROUP;
|
||||
|
||||
var action = Undo.AddComponent<ActionToggleObject>(outfitObject);
|
||||
action.Objects.Add(new ActionToggleObject.ObjectEntry()
|
||||
{
|
||||
target = new AvatarObjectReference()
|
||||
{
|
||||
referencePath = RuntimeUtil.AvatarRootPath(outfitObject)
|
||||
},
|
||||
Active = true
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
[MenuItem("GameObject/ModularAvatar/Setup Outfit", true, PRIORITY)]
|
||||
|
@ -1,188 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEditor;
|
||||
using UnityEditorInternal;
|
||||
using UnityEngine;
|
||||
|
||||
namespace nadena.dev.modular_avatar.core.editor
|
||||
{
|
||||
[CustomEditor(typeof(ActionBlendshape))]
|
||||
internal class ActionBlendshapeEditor : MAEditorBase
|
||||
{
|
||||
private ColumnReorderableList _list;
|
||||
private SerializedProperty _listProp;
|
||||
private Dictionary<Mesh, string[]> _blendshapeNamesCache = new Dictionary<Mesh, string[]>();
|
||||
private Transform _avatarRoot;
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
_listProp = serializedObject.FindProperty(nameof(ActionBlendshape.Blendshapes));
|
||||
_list = new ColumnReorderableList(
|
||||
serializedObject,
|
||||
_listProp,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true
|
||||
);
|
||||
|
||||
_list.Repaint = Repaint;
|
||||
_list.OnGenerateColumns = BuildColumns;
|
||||
_list.onRemoveCallback += OnRemoveElement;
|
||||
_list.onAddCallback += OnAddElement;
|
||||
|
||||
_list.elementHeight += 2;
|
||||
|
||||
if (targets.Length == 1)
|
||||
{
|
||||
_avatarRoot = RuntimeUtil.FindAvatarInParents(((Component) target).transform).transform;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnAddElement(ReorderableList list)
|
||||
{
|
||||
_listProp.arraySize++;
|
||||
}
|
||||
|
||||
private void OnRemoveElement(ReorderableList reorderableList)
|
||||
{
|
||||
if (reorderableList.index < _listProp.arraySize)
|
||||
{
|
||||
_listProp.DeleteArrayElementAtIndex(reorderableList.index);
|
||||
}
|
||||
}
|
||||
|
||||
private void RenderBlendshapeColumn(Rect rect, SerializedProperty elem)
|
||||
{
|
||||
var prop_obj = elem.FindPropertyRelative(nameof(ActionBlendshape.BlendshapeSpec.target))
|
||||
?.FindPropertyRelative(nameof(AvatarObjectReference.referencePath));
|
||||
var prop_blendshape = elem.FindPropertyRelative(nameof(ActionBlendshape.BlendshapeSpec.blendshape));
|
||||
|
||||
SkinnedMeshRenderer targetRenderer = null;
|
||||
|
||||
if (_avatarRoot != null)
|
||||
{
|
||||
var targetObj = _avatarRoot.Find(prop_obj.stringValue);
|
||||
if (targetObj != null)
|
||||
{
|
||||
targetRenderer = targetObj.GetComponent<SkinnedMeshRenderer>();
|
||||
}
|
||||
}
|
||||
|
||||
if (targetRenderer == null || targetRenderer.sharedMesh == null)
|
||||
{
|
||||
EditorGUI.PropertyField(rect, prop_blendshape, GUIContent.none);
|
||||
return;
|
||||
}
|
||||
|
||||
var sharedMesh = targetRenderer.sharedMesh;
|
||||
|
||||
if (!_blendshapeNamesCache.TryGetValue(sharedMesh, out var names))
|
||||
{
|
||||
names = new string[sharedMesh.blendShapeCount + 1];
|
||||
names[0] = "";
|
||||
for (var i = 1; i < sharedMesh.blendShapeCount; i++)
|
||||
{
|
||||
names[i] = sharedMesh.GetBlendShapeName(i - 1);
|
||||
}
|
||||
|
||||
_blendshapeNamesCache[sharedMesh] = names;
|
||||
}
|
||||
|
||||
var blendshapeIndex = Array.IndexOf(names, prop_blendshape.stringValue);
|
||||
var style = new GUIStyle(EditorStyles.popup);
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
blendshapeIndex = EditorGUI.Popup(rect, blendshapeIndex, names, style);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
prop_blendshape.stringValue = names[blendshapeIndex];
|
||||
}
|
||||
else if (blendshapeIndex < 0)
|
||||
{
|
||||
var toDisplay = prop_blendshape.stringValue;
|
||||
|
||||
UpdateAllStates(style, s => s.textColor = Color.Lerp(s.textColor, Color.red, 0.85f));
|
||||
|
||||
GUI.Label(rect, toDisplay, style);
|
||||
}
|
||||
}
|
||||
|
||||
private static void UpdateAllStates(GUIStyle style, Action<GUIStyleState> action)
|
||||
{
|
||||
var state = style.normal;
|
||||
action(state);
|
||||
style.normal = state;
|
||||
|
||||
state = style.hover;
|
||||
action(state);
|
||||
style.hover = state;
|
||||
|
||||
state = style.active;
|
||||
action(state);
|
||||
style.active = state;
|
||||
|
||||
state = style.focused;
|
||||
action(state);
|
||||
style.focused = state;
|
||||
|
||||
state = style.onNormal;
|
||||
action(state);
|
||||
style.onNormal = state;
|
||||
|
||||
state = style.onHover;
|
||||
action(state);
|
||||
style.onHover = state;
|
||||
|
||||
state = style.onActive;
|
||||
action(state);
|
||||
style.onActive = state;
|
||||
|
||||
state = style.onFocused;
|
||||
action(state);
|
||||
style.onFocused = state;
|
||||
}
|
||||
|
||||
private void BuildColumns(float width)
|
||||
{
|
||||
GUIContent testValueContent = new GUIContent("100");
|
||||
var valueFieldSize = EditorStyles.textField.CalcSize(testValueContent);
|
||||
var remainingWidth = width - valueFieldSize.x - _list.margin;
|
||||
var fieldWidth = (remainingWidth - _list.margin) / 2;
|
||||
|
||||
_list.AddColumn(fieldWidth, "action.blendshape.header.object", (rect, elem) =>
|
||||
{
|
||||
var targetProp = elem.FindPropertyRelative(nameof(ActionBlendshape.BlendshapeSpec.target));
|
||||
EditorGUI.PropertyField(rect, targetProp, GUIContent.none);
|
||||
});
|
||||
|
||||
_list.AddColumn(fieldWidth, "action.blendshape.header.blendshape", RenderBlendshapeColumn);
|
||||
|
||||
_list.AddColumn(valueFieldSize.x, "action.blendshape.header.value", (rect, elem) =>
|
||||
{
|
||||
var valueProp = elem.FindPropertyRelative(nameof(ActionBlendshape.BlendshapeSpec.value));
|
||||
EditorGUI.PropertyField(rect, valueProp, GUIContent.none);
|
||||
});
|
||||
}
|
||||
|
||||
protected override void OnInnerInspectorGUI()
|
||||
{
|
||||
serializedObject.Update();
|
||||
|
||||
Rect rect = GUILayoutUtility.GetRect(
|
||||
10f,
|
||||
_list.headerHeight + _list.elementHeight * Math.Max(1, _list.serializedProperty.arraySize) +
|
||||
_list.footerHeight,
|
||||
GUILayout.ExpandWidth(true)
|
||||
);
|
||||
|
||||
_list.DoList(rect);
|
||||
|
||||
EditorGUILayout.Space(8);
|
||||
|
||||
Localization.ShowLanguageUI();
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 78859e260e8e426bb4673c9077ed5d7b
|
||||
timeCreated: 1683272729
|
@ -1,172 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using BestHTTP;
|
||||
using UnityEditor;
|
||||
using UnityEditorInternal;
|
||||
using UnityEngine;
|
||||
using VRC.Collections;
|
||||
using static nadena.dev.modular_avatar.core.editor.Localization;
|
||||
|
||||
namespace nadena.dev.modular_avatar.core.editor
|
||||
{
|
||||
[CustomEditor(typeof(ActionToggleObject))]
|
||||
internal class ActionToggleObjectInspector : MAEditorBase
|
||||
{
|
||||
private ColumnReorderableList _list;
|
||||
private SerializedProperty _listProp;
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
_listProp = serializedObject.FindProperty(nameof(ActionToggleObject.Objects));
|
||||
_list = new ColumnReorderableList(
|
||||
serializedObject,
|
||||
_listProp,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true
|
||||
);
|
||||
|
||||
_list.Repaint = Repaint;
|
||||
_list.OnGenerateColumns = BuildColumns;
|
||||
_list.onRemoveCallback += OnRemoveElement;
|
||||
_list.onAddCallback += OnAddElement;
|
||||
|
||||
_list.elementHeight += 2;
|
||||
}
|
||||
|
||||
private void OnAddElement(ReorderableList list)
|
||||
{
|
||||
_listProp.arraySize++;
|
||||
}
|
||||
|
||||
private void OnRemoveElement(ReorderableList reorderableList)
|
||||
{
|
||||
if (reorderableList.index < _listProp.arraySize)
|
||||
{
|
||||
_listProp.DeleteArrayElementAtIndex(reorderableList.index);
|
||||
}
|
||||
}
|
||||
|
||||
private void BuildColumns(float width)
|
||||
{
|
||||
var t = EditorStyles.toggle;
|
||||
var toggleSize = t.CalcSize(GUIContent.none);
|
||||
|
||||
var checkHeaderContent = G("action.toggle_object.header.show");
|
||||
var rectHeaderSize = EditorStyles.label.CalcSize(checkHeaderContent);
|
||||
|
||||
width = _list.AddColumn(rectHeaderSize.x, "action.toggle_object.header.show", (rect, prop) =>
|
||||
{
|
||||
rect = CenterElement(rect, toggleSize);
|
||||
|
||||
var activeProp = prop.FindPropertyRelative(nameof(ActionToggleObject.ObjectEntry.Active));
|
||||
EditorGUI.PropertyField(rect, activeProp, GUIContent.none);
|
||||
});
|
||||
|
||||
_list.AddColumn(width, "action.toggle_object.header.object", ((rect, elem) =>
|
||||
{
|
||||
var targetProp = elem.FindPropertyRelative(nameof(ActionToggleObject.ObjectEntry.target));
|
||||
EditorGUI.PropertyField(rect, targetProp, GUIContent.none);
|
||||
}));
|
||||
}
|
||||
|
||||
private static Rect CenterElement(Rect rect, Vector2 toggleSize)
|
||||
{
|
||||
float adjust = rect.height - toggleSize.y;
|
||||
rect.yMin += adjust / 2;
|
||||
rect.yMax -= adjust / 2;
|
||||
|
||||
adjust = rect.width - toggleSize.x;
|
||||
rect.xMin += adjust / 2;
|
||||
rect.xMax -= adjust / 2;
|
||||
|
||||
return rect;
|
||||
}
|
||||
|
||||
protected override void OnInnerInspectorGUI()
|
||||
{
|
||||
serializedObject.Update();
|
||||
|
||||
Rect rect = GUILayoutUtility.GetRect(
|
||||
10f,
|
||||
_list.headerHeight + _list.elementHeight * Math.Max(1, _list.serializedProperty.arraySize) +
|
||||
_list.footerHeight,
|
||||
GUILayout.ExpandWidth(true)
|
||||
);
|
||||
|
||||
_list.DoList(rect);
|
||||
|
||||
EditorGUILayout.Space(8);
|
||||
|
||||
Localization.ShowLanguageUI();
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
|
||||
if (rect.Contains(Event.current.mousePosition))
|
||||
{
|
||||
switch (Event.current.type)
|
||||
{
|
||||
case EventType.DragUpdated:
|
||||
if (!DragIsGameObject()) break;
|
||||
DragAndDrop.visualMode = DragAndDropVisualMode.Link;
|
||||
Event.current.Use();
|
||||
break;
|
||||
case EventType.DragPerform:
|
||||
{
|
||||
if (!DragIsGameObject()) break;
|
||||
var targetObj = (ActionToggleObject) target;
|
||||
|
||||
if (targetObj.Objects == null)
|
||||
{
|
||||
targetObj.Objects = new List<ActionToggleObject.ObjectEntry>();
|
||||
}
|
||||
|
||||
HashSet<GameObject> currentObjects = new HashSet<GameObject>();
|
||||
foreach (var obj in targetObj.Objects)
|
||||
{
|
||||
if (obj != null && obj.target != null)
|
||||
{
|
||||
currentObjects.Add(obj.target.Get(targetObj));
|
||||
}
|
||||
}
|
||||
|
||||
var objects = targetObj.Objects.ToList();
|
||||
|
||||
foreach (var obj in DragAndDrop.objectReferences)
|
||||
{
|
||||
if (obj is GameObject go && !currentObjects.Contains(go))
|
||||
{
|
||||
objects.Add(new ActionToggleObject.ObjectEntry()
|
||||
{
|
||||
target = new AvatarObjectReference()
|
||||
{
|
||||
referencePath = RuntimeUtil.AvatarRootPath(go)
|
||||
},
|
||||
Active = go.activeSelf
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Undo.RecordObject(targetObj, "Add objects");
|
||||
targetObj.Objects = objects;
|
||||
EditorUtility.SetDirty(targetObj);
|
||||
PrefabUtility.RecordPrefabInstancePropertyModifications(targetObj);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool DragIsGameObject()
|
||||
{
|
||||
foreach (var obj in DragAndDrop.objectReferences)
|
||||
{
|
||||
if (obj is GameObject) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: df175549a5ea45d59d9f2daa031bbbf1
|
||||
timeCreated: 1677901132
|
@ -1,210 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEditor;
|
||||
using UnityEditor.IMGUI.Controls;
|
||||
using UnityEngine;
|
||||
using static nadena.dev.modular_avatar.core.editor.Localization;
|
||||
|
||||
namespace nadena.dev.modular_avatar.core.editor
|
||||
{
|
||||
internal class ControlGroupDefaultDropdown : AdvancedDropdown
|
||||
{
|
||||
public string[] _names { get; private set; }
|
||||
public ModularAvatarMenuItem[] _menuItems { get; private set; }
|
||||
|
||||
public event Action<ModularAvatarMenuItem> OnItemSelected;
|
||||
|
||||
public ControlGroupDefaultDropdown(ModularAvatarMenuItem[] menuItems) : base(new AdvancedDropdownState())
|
||||
{
|
||||
_names = menuItems.Select(n =>
|
||||
{
|
||||
if (n == null || n.gameObject == null)
|
||||
{
|
||||
return Localization.S("control_group.default_value.unset");
|
||||
}
|
||||
else
|
||||
{
|
||||
return n.gameObject.name;
|
||||
}
|
||||
}).ToArray();
|
||||
_menuItems = menuItems;
|
||||
}
|
||||
|
||||
protected override AdvancedDropdownItem BuildRoot()
|
||||
{
|
||||
var root = new AdvancedDropdownItem(S("control_group.default_value"));
|
||||
for (int i = 0; i < _names.Length; i++)
|
||||
{
|
||||
var item = new AdvancedDropdownItem(_names[i]);
|
||||
item.id = i;
|
||||
root.AddChild(item);
|
||||
}
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
protected override void ItemSelected(AdvancedDropdownItem item)
|
||||
{
|
||||
OnItemSelected?.Invoke(_menuItems[item.id]);
|
||||
}
|
||||
}
|
||||
|
||||
[CustomEditor(typeof(ControlGroup))]
|
||||
internal class ControlGroupInspector : MAEditorBase
|
||||
{
|
||||
private bool _showInner;
|
||||
private SerializedProperty _isSynced, _isSaved, _defaultValue;
|
||||
private ControlGroupDefaultDropdown _dropdown;
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
EditorApplication.hierarchyChanged += Invalidate;
|
||||
|
||||
_isSynced = serializedObject.FindProperty(nameof(ControlGroup.isSynced));
|
||||
_isSaved = serializedObject.FindProperty(nameof(ControlGroup.isSaved));
|
||||
_defaultValue = serializedObject.FindProperty(nameof(ControlGroup.defaultValue));
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
EditorApplication.hierarchyChanged -= Invalidate;
|
||||
}
|
||||
|
||||
private List<Action> _menuItemActions = null;
|
||||
|
||||
private void Invalidate()
|
||||
{
|
||||
var target = (ControlGroup) this.target;
|
||||
var avatar = RuntimeUtil.FindAvatarInParents(target.transform);
|
||||
var menuItems = avatar.GetComponentsInChildren<ModularAvatarMenuItem>(true);
|
||||
|
||||
_menuItemActions = new List<Action>();
|
||||
var filteredMenuItems = new List<ModularAvatarMenuItem>();
|
||||
foreach (var menuItem in menuItems.Where(item => item.controlGroup == target))
|
||||
{
|
||||
var node = CreateMenuItemNode(menuItem);
|
||||
_menuItemActions.Add(node);
|
||||
filteredMenuItems.Add(menuItem);
|
||||
}
|
||||
|
||||
filteredMenuItems.Insert(0, null);
|
||||
_dropdown = new ControlGroupDefaultDropdown(filteredMenuItems.ToArray());
|
||||
_dropdown.OnItemSelected += (item) =>
|
||||
{
|
||||
serializedObject.Update();
|
||||
_defaultValue.objectReferenceValue = item;
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
};
|
||||
}
|
||||
|
||||
private Action CreateMenuItemNode(ModularAvatarMenuItem menuItem)
|
||||
{
|
||||
bool foldout = false;
|
||||
|
||||
var coreUI = new MenuItemCoreGUI(new SerializedObject(menuItem), Repaint);
|
||||
var enableMenuItem = new SerializedObject(menuItem.gameObject).FindProperty("m_IsActive");
|
||||
|
||||
List<Action> foldoutInspectors = null;
|
||||
|
||||
return () =>
|
||||
{
|
||||
using (new MenuObjectHeader(menuItem, enableMenuItem).Scope())
|
||||
{
|
||||
coreUI.DoGUI();
|
||||
|
||||
foldout = EditorGUILayout.Foldout(foldout, G("control_group.foldout.actions"));
|
||||
if (foldout)
|
||||
{
|
||||
if (foldoutInspectors == null)
|
||||
{
|
||||
foldoutInspectors = menuItem.GetComponents<MenuAction>()
|
||||
.Select(action =>
|
||||
{
|
||||
var component = (Component) action;
|
||||
var editor = CreateEditor(component);
|
||||
var enabled_prop = new SerializedObject(component).FindProperty("m_Enabled");
|
||||
|
||||
return (Action) (() =>
|
||||
{
|
||||
using (new MenuObjectHeader(component, enabled_prop).Scope())
|
||||
{
|
||||
editor.OnInspectorGUI();
|
||||
}
|
||||
});
|
||||
})
|
||||
.ToList();
|
||||
}
|
||||
|
||||
foreach (var inspector in foldoutInspectors)
|
||||
{
|
||||
inspector();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private Rect _dropdownButtonRect;
|
||||
|
||||
protected override void OnInnerInspectorGUI()
|
||||
{
|
||||
serializedObject.Update();
|
||||
|
||||
if (_menuItemActions == null || _dropdown == null) Invalidate();
|
||||
|
||||
EditorGUILayout.PropertyField(_isSynced, G("control_group.is_synced"));
|
||||
EditorGUILayout.PropertyField(_isSaved, G("control_group.is_saved"));
|
||||
//EditorGUILayout.PropertyField(_defaultValue, G("control_group.default_value")); // TODO - dropdown
|
||||
|
||||
if (_dropdown != null)
|
||||
{
|
||||
var label = G("control_group.default_value");
|
||||
var position = EditorGUILayout.GetControlRect(true);
|
||||
position = EditorGUI.PrefixLabel(position, label);
|
||||
|
||||
var currentValue = _defaultValue.objectReferenceValue;
|
||||
string selected;
|
||||
|
||||
if (currentValue == null || !(currentValue is ModularAvatarMenuItem item) ||
|
||||
!item.controlGroup == target)
|
||||
{
|
||||
selected = S("control_group.default_value.unset");
|
||||
}
|
||||
else
|
||||
{
|
||||
selected = item.gameObject.name;
|
||||
}
|
||||
|
||||
if (GUI.Button(position, selected, EditorStyles.popup))
|
||||
{
|
||||
_dropdown.Show(position);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
_showInner = EditorGUILayout.Foldout(_showInner, G("control_group.foldout.menu_items"));
|
||||
if (_showInner)
|
||||
{
|
||||
foreach (var action in _menuItemActions)
|
||||
{
|
||||
try
|
||||
{
|
||||
EditorGUI.indentLevel++;
|
||||
action();
|
||||
}
|
||||
finally
|
||||
{
|
||||
EditorGUI.indentLevel--;
|
||||
}
|
||||
|
||||
EditorGUILayout.Space(4);
|
||||
}
|
||||
}
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
|
||||
Localization.ShowLanguageUI();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 357527439f084cd6812d6e5dcd9692f8
|
||||
timeCreated: 1677595893
|
@ -14,148 +14,24 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
[CanEditMultipleObjects]
|
||||
internal class MAMenuItemInspector : MAEditorBase
|
||||
{
|
||||
private VRCExpressionsMenu MENU_CLOTHING;
|
||||
private ControlGroup CONTROL_GROUP_CLOTHING;
|
||||
|
||||
private List<(GUIContent, Action)> _presets = new List<(GUIContent, Action)>();
|
||||
private MenuItemCoreGUI _coreGUI;
|
||||
|
||||
private long _cacheSeq = -1;
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
_coreGUI = new MenuItemCoreGUI(serializedObject, Repaint);
|
||||
_coreGUI.AlwaysExpandContents = true;
|
||||
|
||||
MENU_CLOTHING = Util.LoadAssetByGuid<VRCExpressionsMenu>("2fe0aa7ecd6bc4443bade672c978f59d");
|
||||
CONTROL_GROUP_CLOTHING
|
||||
= Util.LoadAssetByGuid<GameObject>("e451e988456f35b49a3d011d780bda07")
|
||||
?.GetComponent<ControlGroup>();
|
||||
|
||||
RebuildPresets();
|
||||
}
|
||||
|
||||
private void RebuildPresets()
|
||||
{
|
||||
_presets.Clear();
|
||||
|
||||
_cacheSeq = VirtualMenu.CacheSequence;
|
||||
|
||||
if (targets.Length == 1)
|
||||
{
|
||||
ModularAvatarMenuItem item = (ModularAvatarMenuItem) target;
|
||||
if (item.GetComponent<ModularAvatarMenuInstaller>() == null
|
||||
&& item.GetComponent<MenuAction>() != null
|
||||
&& item.Control.type == VRCExpressionsMenu.Control.ControlType.Toggle
|
||||
&& item.controlGroup != CONTROL_GROUP_CLOTHING
|
||||
)
|
||||
{
|
||||
_presets.Add((new GUIContent("Configure as outfit toggle"), ConfigureOutfitToggle));
|
||||
}
|
||||
|
||||
if (item.Control.type == VRCExpressionsMenu.Control.ControlType.SubMenu
|
||||
&& item.MenuSource == SubmenuSource.Children)
|
||||
{
|
||||
_presets.Add((new GUIContent("Set items as exclusive toggles"), ConfigureExclusiveToggles));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ConfigureExclusiveToggles()
|
||||
{
|
||||
ModularAvatarMenuItem item = (ModularAvatarMenuItem) target;
|
||||
|
||||
var controlGroup = item.gameObject.GetComponent<ControlGroup>();
|
||||
if (controlGroup == null)
|
||||
{
|
||||
controlGroup = Undo.AddComponent<ControlGroup>(item.gameObject);
|
||||
}
|
||||
|
||||
var sourceObject = (item.menuSource_otherObjectChildren
|
||||
? item.menuSource_otherObjectChildren
|
||||
: item.gameObject).transform;
|
||||
|
||||
foreach (Transform t in sourceObject)
|
||||
{
|
||||
var subItem = t.GetComponent<ModularAvatarMenuItem>();
|
||||
if (subItem == null) continue;
|
||||
if (subItem.GetComponent<MenuAction>() == null) continue;
|
||||
|
||||
Undo.RecordObject(subItem, "Configure exclusive toggles");
|
||||
subItem.controlGroup = controlGroup;
|
||||
subItem.Control.type = VRCExpressionsMenu.Control.ControlType.Toggle;
|
||||
}
|
||||
|
||||
// Clear caches
|
||||
OnEnable();
|
||||
}
|
||||
|
||||
private void ConfigureOutfitToggle()
|
||||
{
|
||||
ModularAvatarMenuItem item = (ModularAvatarMenuItem) target;
|
||||
Undo.RecordObject(item, "Configure outfit toggle");
|
||||
item.controlGroup = CONTROL_GROUP_CLOTHING;
|
||||
|
||||
// Determine if the item is already registered in the virtual menu - if not, we need to install it
|
||||
var avatar = RuntimeUtil.FindAvatarInParents(item.transform);
|
||||
if (avatar != null)
|
||||
{
|
||||
VirtualMenu virtualMenu = VirtualMenu.ForAvatar(RuntimeUtil.FindAvatarInParents(item.transform));
|
||||
if (virtualMenu.ContainsNode(item)) return;
|
||||
}
|
||||
|
||||
var installer = item.gameObject.AddComponent<ModularAvatarMenuInstaller>();
|
||||
Undo.RegisterCreatedObjectUndo(installer, "Configure outfit toggle");
|
||||
installer.installTargetMenu = MENU_CLOTHING;
|
||||
|
||||
// Clear caches
|
||||
OnEnable();
|
||||
}
|
||||
|
||||
protected override void OnInnerInspectorGUI()
|
||||
{
|
||||
if (VirtualMenu.CacheSequence != _cacheSeq) RebuildPresets();
|
||||
|
||||
serializedObject.Update();
|
||||
|
||||
_coreGUI.DoGUI();
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
|
||||
ShowPresetsButton();
|
||||
|
||||
ShowLanguageUI();
|
||||
}
|
||||
|
||||
private void ShowPresetsButton()
|
||||
{
|
||||
if (_presets.Count == 0) return;
|
||||
|
||||
var style = EditorStyles.popup;
|
||||
var rect = EditorGUILayout.GetControlRect(true, 18f, style);
|
||||
var controlId = GUIUtility.GetControlID("MAPresetsButton".GetHashCode(), FocusType.Keyboard, rect);
|
||||
|
||||
rect.xMin += 2 * rect.width / 3;
|
||||
|
||||
if (GUI.Button(rect, new GUIContent("Presets"), EditorStyles.popup))
|
||||
{
|
||||
EditorUtility.DisplayCustomMenu(
|
||||
rect,
|
||||
_presets.Select(elem => elem.Item1).ToArray(),
|
||||
-1,
|
||||
(_userData, _options, _index) =>
|
||||
{
|
||||
if (_index >= 0 && _index < _presets.Count)
|
||||
{
|
||||
_presets[_index].Item2();
|
||||
|
||||
RebuildPresets();
|
||||
}
|
||||
},
|
||||
controlId
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[CustomEditor(typeof(ModularAvatarMenuGroup))]
|
||||
|
@ -29,7 +29,6 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
private readonly SerializedProperty _type;
|
||||
private readonly SerializedProperty _value;
|
||||
private readonly SerializedProperty _submenu;
|
||||
private readonly SerializedProperty _controlGroup;
|
||||
|
||||
private readonly ParameterGUI _parameterGUI;
|
||||
|
||||
@ -45,32 +44,10 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
|
||||
private readonly SerializedProperty _prop_submenuSource;
|
||||
private readonly SerializedProperty _prop_otherObjSource;
|
||||
private readonly SerializedProperty _prop_isSynced, _prop_isSaved;
|
||||
|
||||
public bool AlwaysExpandContents = false;
|
||||
public bool ExpandContents = false;
|
||||
|
||||
private bool _hasActions;
|
||||
|
||||
private bool HasActions(TargetParameter? p = null)
|
||||
{
|
||||
return _controlGroup != null && _obj.targetObjects.Any(o =>
|
||||
{
|
||||
if (!(o is ModularAvatarMenuItem c)) return false;
|
||||
if (p.HasValue)
|
||||
{
|
||||
foreach (var component in c.GetComponents<MenuAction>())
|
||||
{
|
||||
if (component.BindsParameter(p.Value)) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return c.GetComponent<MenuAction>() != null;
|
||||
});
|
||||
}
|
||||
|
||||
public MenuItemCoreGUI(SerializedObject obj, Action redraw)
|
||||
{
|
||||
_obj = obj;
|
||||
@ -91,7 +68,6 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
);
|
||||
|
||||
_name = gameObjects.FindProperty("m_Name");
|
||||
_controlGroup = obj.FindProperty(nameof(ModularAvatarMenuItem.controlGroup));
|
||||
|
||||
var control = obj.FindProperty(nameof(ModularAvatarMenuItem.Control));
|
||||
|
||||
@ -109,8 +85,6 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
|
||||
_prop_submenuSource = obj.FindProperty(nameof(ModularAvatarMenuItem.MenuSource));
|
||||
_prop_otherObjSource = obj.FindProperty(nameof(ModularAvatarMenuItem.menuSource_otherObjectChildren));
|
||||
_prop_isSynced = obj.FindProperty(nameof(ModularAvatarMenuItem.isSynced));
|
||||
_prop_isSaved = obj.FindProperty(nameof(ModularAvatarMenuItem.isSaved));
|
||||
_previewGUI = new MenuPreviewGUI(redraw);
|
||||
}
|
||||
|
||||
@ -133,8 +107,6 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
_value = _control.FindPropertyRelative(nameof(VRCExpressionsMenu.Control.value));
|
||||
_submenu = _control.FindPropertyRelative(nameof(VRCExpressionsMenu.Control.subMenu));
|
||||
|
||||
_controlGroup = null;
|
||||
|
||||
_parameterGUI = new ParameterGUI(parameterReference, parameter, redraw);
|
||||
|
||||
_subParamsRoot = _control.FindPropertyRelative(nameof(VRCExpressionsMenu.Control.subParameters));
|
||||
@ -142,15 +114,11 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
|
||||
_prop_submenuSource = null;
|
||||
_prop_otherObjSource = null;
|
||||
_prop_isSynced = null;
|
||||
_prop_isSaved = null;
|
||||
_previewGUI = new MenuPreviewGUI(redraw);
|
||||
}
|
||||
|
||||
public void DoGUI()
|
||||
{
|
||||
_hasActions = HasActions();
|
||||
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
|
||||
EditorGUILayout.BeginVertical();
|
||||
@ -163,24 +131,9 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
|
||||
EditorGUILayout.PropertyField(_texture, G("menuitem.prop.icon"));
|
||||
EditorGUILayout.PropertyField(_type, G("menuitem.prop.type"));
|
||||
EditorGUILayout.PropertyField(_value, G("menuitem.prop.value"));
|
||||
|
||||
if (_hasActions)
|
||||
{
|
||||
EditorGUILayout.PropertyField(_controlGroup, G("menuitem.prop.control_group"));
|
||||
|
||||
if (!_controlGroup.hasMultipleDifferentValues && _controlGroup.objectReferenceValue == null)
|
||||
{
|
||||
EditorGUILayout.PropertyField(_prop_isSynced, G("menuitem.prop.is_synced"));
|
||||
EditorGUILayout.PropertyField(_prop_isSaved, G("menuitem.prop.is_saved"));
|
||||
}
|
||||
}
|
||||
|
||||
if (!_hasActions || !HasActions(TargetParameter.BaseParameter))
|
||||
{
|
||||
EditorGUILayout.PropertyField(_value, G("menuitem.prop.value"));
|
||||
}
|
||||
|
||||
_parameterGUI.DoGUI(!_hasActions || !HasActions(TargetParameter.BaseParameter));
|
||||
_parameterGUI.DoGUI(true);
|
||||
|
||||
EditorGUILayout.EndVertical();
|
||||
|
||||
@ -336,7 +289,7 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
{
|
||||
EnsureParameterCount(1);
|
||||
|
||||
_subParams[0].DoGUI(!_hasActions || !HasActions(TargetParameter.RadialParam),
|
||||
_subParams[0].DoGUI(true,
|
||||
G("menuitem.param.rotation"));
|
||||
|
||||
break;
|
||||
@ -349,9 +302,9 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
EditorGUILayout.LabelField(G("menuitem.label.parameters"), EditorStyles.boldLabel);
|
||||
EditorGUILayout.Space(2);
|
||||
|
||||
_subParams[0].DoGUI(!_hasActions || !HasActions(TargetParameter.Horizontal),
|
||||
_subParams[0].DoGUI(true,
|
||||
G("menuitem.param.horizontal"));
|
||||
_subParams[1].DoGUI(!_hasActions || !HasActions(TargetParameter.Vertical),
|
||||
_subParams[1].DoGUI(true,
|
||||
G("menuitem.param.vertical"));
|
||||
|
||||
DoFourAxisLabels(false);
|
||||
@ -449,10 +402,10 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
center.xMin += blockWidth;
|
||||
center.xMax -= blockWidth;
|
||||
|
||||
SingleLabel(0, up, TargetParameter.Up);
|
||||
SingleLabel(1, right, TargetParameter.Right);
|
||||
SingleLabel(2, down, TargetParameter.Down);
|
||||
SingleLabel(3, left, TargetParameter.Left);
|
||||
SingleLabel(0, up);
|
||||
SingleLabel(1, right);
|
||||
SingleLabel(2, down);
|
||||
SingleLabel(3, left);
|
||||
|
||||
var rect_param_l = center;
|
||||
rect_param_l.yMin = rect_param_l.yMax - EditorGUIUtility.singleLineHeight;
|
||||
@ -462,7 +415,7 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
if (showParams) CenterLabel(rect_param_l, G("menuitem.prop.parameter"), EditorStyles.label);
|
||||
CenterLabel(rect_name_l, G("menuitem.prop.label"), EditorStyles.label);
|
||||
|
||||
void SingleLabel(int index, Rect block, TargetParameter parameter)
|
||||
void SingleLabel(int index, Rect block)
|
||||
{
|
||||
var prop_name = _labels[index].FindPropertyRelative(nameof(VRCExpressionsMenu.Control.Label.name));
|
||||
var prop_icon = _labels[index].FindPropertyRelative(nameof(VRCExpressionsMenu.Control.Label.icon));
|
||||
@ -479,7 +432,7 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
EditorGUI.PropertyField(rect_name, prop_name, GUIContent.none);
|
||||
if (showParams)
|
||||
{
|
||||
_subParams[index].DoGUI(rect_param, !_hasActions || !HasActions(parameter), GUIContent.none);
|
||||
_subParams[index].DoGUI(rect_param, true, GUIContent.none);
|
||||
}
|
||||
|
||||
var tex = prop_icon.objectReferenceValue as Texture;
|
||||
|
@ -1,560 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using nadena.dev.modular_avatar.editor.ErrorReporting;
|
||||
using UnityEditor;
|
||||
using UnityEditor.Animations;
|
||||
using UnityEngine;
|
||||
using VRC.SDK3.Avatars.Components;
|
||||
using VRC.SDK3.Avatars.ScriptableObjects;
|
||||
using VRC.SDKBase;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace nadena.dev.modular_avatar.core.editor
|
||||
{
|
||||
internal class ActionGenerator
|
||||
{
|
||||
private const string DIRECT_BLEND_TREE_PARAM = "_MA/ONE";
|
||||
private readonly BuildContext _context;
|
||||
|
||||
public ActionGenerator(BuildContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public void OnPreprocessAvatar(VRCAvatarDescriptor avatar)
|
||||
{
|
||||
List<Action> revertChangesActions = new List<Action>();
|
||||
|
||||
try
|
||||
{
|
||||
ApplyControlGroupOverrides(revertChangesActions, avatar);
|
||||
Generate(avatar);
|
||||
}
|
||||
finally
|
||||
{
|
||||
foreach (var action in revertChangesActions)
|
||||
{
|
||||
try
|
||||
{
|
||||
action();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyControlGroupOverrides(List<Action> revertChangesActions, VRCAvatarDescriptor avatar)
|
||||
{
|
||||
foreach (var groupOverride in avatar.GetComponentsInChildren<SetGroupDefaults>(true))
|
||||
{
|
||||
var target = groupOverride.targetControlGroup;
|
||||
if (target == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
bool oldSynced = target.isSynced;
|
||||
bool oldSaved = target.isSaved;
|
||||
var oldDefault = target.defaultValue;
|
||||
|
||||
revertChangesActions.Add(() =>
|
||||
{
|
||||
if (target == null) return;
|
||||
|
||||
target.isSynced = oldSynced;
|
||||
target.isSaved = oldSaved;
|
||||
target.defaultValue = oldDefault;
|
||||
});
|
||||
|
||||
target.isSynced = groupOverride.isSynced;
|
||||
target.isSaved = groupOverride.isSaved;
|
||||
target.defaultValue = groupOverride.defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
private void Generate(VRCAvatarDescriptor avatar)
|
||||
{
|
||||
// Locate MenuActions
|
||||
var actionMenus = avatar.GetComponentsInChildren<MenuAction>(true)
|
||||
.Select(a => ((Component) a).gameObject.GetComponent<ModularAvatarMenuItem>())
|
||||
.Where(item => item != null)
|
||||
.ToImmutableHashSet();
|
||||
|
||||
if (actionMenus.IsEmpty) return;
|
||||
|
||||
// Generate the root blendtree and animation; insert into the FX layer
|
||||
var animLayers = avatar.baseAnimationLayers;
|
||||
int fxLayerIndex = -1;
|
||||
AnimatorController controller = null;
|
||||
|
||||
// TODO: refactor out layer manipulation here (+ the base state generator)
|
||||
|
||||
for (int i = 0; i < animLayers.Length; i++)
|
||||
{
|
||||
if (animLayers[i].type == VRCAvatarDescriptor.AnimLayerType.FX)
|
||||
{
|
||||
fxLayerIndex = i;
|
||||
controller = _context.DeepCloneAnimator(animLayers[i].animatorController);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (controller == null)
|
||||
{
|
||||
controller = new AnimatorController();
|
||||
controller.name = "FX Controller";
|
||||
_context.SaveAsset(controller);
|
||||
}
|
||||
|
||||
animLayers[fxLayerIndex].animatorController = controller;
|
||||
avatar.baseAnimationLayers = animLayers;
|
||||
|
||||
var parameters = controller.parameters.ToList();
|
||||
|
||||
var actions = GenerateActions(avatar, actionMenus, parameters);
|
||||
parameters.Add(new AnimatorControllerParameter()
|
||||
{
|
||||
name = DIRECT_BLEND_TREE_PARAM,
|
||||
type = AnimatorControllerParameterType.Float,
|
||||
defaultFloat = 1,
|
||||
});
|
||||
|
||||
controller.parameters = parameters.ToArray();
|
||||
|
||||
int layersToInsert = 2; // TODO
|
||||
|
||||
var rootBlendTree = GenerateRootBlendLayer(actions);
|
||||
AdjustAllBehaviors(controller, b =>
|
||||
{
|
||||
if (b is VRCAnimatorLayerControl lc && lc.playable == VRC_AnimatorLayerControl.BlendableLayer.FX)
|
||||
{
|
||||
lc.layer += layersToInsert;
|
||||
}
|
||||
});
|
||||
foreach (var layer in controller.layers)
|
||||
{
|
||||
layer.syncedLayerIndex += layersToInsert;
|
||||
}
|
||||
|
||||
var layerList = controller.layers.ToList();
|
||||
layerList.Insert(0, rootBlendTree);
|
||||
rootBlendTree.defaultWeight = 1;
|
||||
|
||||
controller.layers = layerList.ToArray();
|
||||
|
||||
// Generate blendshape base layer including anything we're animating in our blendtree
|
||||
layerList.Insert(0, GenerateBlendshapeBaseLayer(avatar));
|
||||
controller.layers = layerList.ToArray();
|
||||
|
||||
foreach (var action in avatar.GetComponentsInChildren<MenuAction>(true))
|
||||
{
|
||||
Object.DestroyImmediate((UnityEngine.Object) action);
|
||||
}
|
||||
}
|
||||
|
||||
private AnimatorControllerLayer GenerateBlendshapeBaseLayer(VRCAvatarDescriptor avatar)
|
||||
{
|
||||
AnimatorControllerLayer layer = new AnimatorControllerLayer();
|
||||
AnimationClip clip = new AnimationClip();
|
||||
foreach (var renderer in avatar.GetComponentsInChildren<SkinnedMeshRenderer>())
|
||||
{
|
||||
if (!renderer.sharedMesh) continue;
|
||||
int nShapes = renderer.sharedMesh.blendShapeCount;
|
||||
for (int i = 0; i < nShapes; i++)
|
||||
{
|
||||
float value = renderer.GetBlendShapeWeight(i);
|
||||
if (value > 0.000001f)
|
||||
{
|
||||
clip.SetCurve(
|
||||
RuntimeUtil.AvatarRootPath(renderer.gameObject),
|
||||
typeof(SkinnedMeshRenderer),
|
||||
"blendShape." + renderer.sharedMesh.GetBlendShapeName(i),
|
||||
AnimationCurve.Constant(0, 1, value)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_context.SaveAsset(clip);
|
||||
|
||||
layer.stateMachine = new AnimatorStateMachine();
|
||||
_context.SaveAsset(layer.stateMachine);
|
||||
|
||||
var state = layer.stateMachine.AddState("Base");
|
||||
state.motion = clip;
|
||||
state.writeDefaultValues = false;
|
||||
|
||||
layer.defaultWeight = 1;
|
||||
layer.name = "Write blendshape defaults";
|
||||
|
||||
return layer;
|
||||
}
|
||||
|
||||
private void AdjustAllBehaviors(AnimatorController controller, Action<StateMachineBehaviour> action)
|
||||
{
|
||||
HashSet<object> visited = new HashSet<object>();
|
||||
foreach (var layer in controller.layers)
|
||||
{
|
||||
VisitStateMachine(layer.stateMachine);
|
||||
}
|
||||
|
||||
void VisitStateMachine(AnimatorStateMachine layerStateMachine)
|
||||
{
|
||||
if (!visited.Add(layerStateMachine)) return;
|
||||
foreach (var state in layerStateMachine.states)
|
||||
{
|
||||
foreach (var behaviour in state.state.behaviours)
|
||||
{
|
||||
action(behaviour);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var child in layerStateMachine.stateMachines)
|
||||
{
|
||||
VisitStateMachine(child.stateMachine);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private AnimatorControllerLayer GenerateRootBlendLayer(List<BlendTree> actions)
|
||||
{
|
||||
var motion = new BlendTree();
|
||||
motion.name = "Menu Actions (generated)";
|
||||
motion.blendParameter = DIRECT_BLEND_TREE_PARAM;
|
||||
motion.blendType = BlendTreeType.Direct;
|
||||
motion.children = actions.Select(a => new ChildMotion()
|
||||
{
|
||||
motion = a,
|
||||
directBlendParameter = DIRECT_BLEND_TREE_PARAM,
|
||||
timeScale = 1,
|
||||
}).ToArray();
|
||||
|
||||
var layer = new AnimatorControllerLayer();
|
||||
layer.name = "Menu Actions (generated)";
|
||||
layer.defaultWeight = 1;
|
||||
layer.blendingMode = AnimatorLayerBlendingMode.Override;
|
||||
layer.stateMachine = new AnimatorStateMachine();
|
||||
layer.stateMachine.name = "Menu Actions (generated)";
|
||||
//layer.stateMachine.hideFlags = HideFlags.HideInHierarchy;
|
||||
_context.SaveAsset(layer.stateMachine);
|
||||
|
||||
var rootState = layer.stateMachine.AddState("Root");
|
||||
rootState.motion = motion;
|
||||
// WD off causes other write default states to fail to write their defaults (facial expressions get stuck
|
||||
// on e.g. kikyo)
|
||||
rootState.writeDefaultValues = true;
|
||||
|
||||
return layer;
|
||||
}
|
||||
|
||||
private List<BlendTree> GenerateActions(
|
||||
VRCAvatarDescriptor descriptor,
|
||||
IEnumerable<ModularAvatarMenuItem> items,
|
||||
List<AnimatorControllerParameter> acParameters)
|
||||
{
|
||||
var expParams = descriptor.expressionParameters;
|
||||
if (expParams != null)
|
||||
{
|
||||
expParams = Object.Instantiate(expParams);
|
||||
_context.SaveAsset(expParams);
|
||||
}
|
||||
else
|
||||
{
|
||||
expParams = ScriptableObject.CreateInstance<VRCExpressionParameters>();
|
||||
expParams.name = "Expression Parameters";
|
||||
_context.SaveAsset(expParams);
|
||||
descriptor.expressionParameters = expParams;
|
||||
}
|
||||
|
||||
List<VRCExpressionParameters.Parameter> expParameters =
|
||||
expParams.parameters?.ToList() ?? new List<VRCExpressionParameters.Parameter>();
|
||||
List<BlendTree> blendTrees = new List<BlendTree>();
|
||||
|
||||
Dictionary<Component, List<ModularAvatarMenuItem>> groupedItems =
|
||||
new Dictionary<Component, List<ModularAvatarMenuItem>>();
|
||||
|
||||
foreach (var item in items)
|
||||
{
|
||||
List<ModularAvatarMenuItem> group;
|
||||
if (item.controlGroup)
|
||||
{
|
||||
if (!groupedItems.TryGetValue(item.controlGroup, out group))
|
||||
{
|
||||
group = new List<ModularAvatarMenuItem>();
|
||||
groupedItems.Add(item.controlGroup, group);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
group = new List<ModularAvatarMenuItem>();
|
||||
groupedItems.Add(item, group);
|
||||
}
|
||||
|
||||
group.Add(item);
|
||||
}
|
||||
|
||||
Dictionary<MenuCurveBinding, Component> bindings = new Dictionary<MenuCurveBinding, Component>();
|
||||
|
||||
int paramIndex = 0;
|
||||
foreach (var kvp in groupedItems)
|
||||
{
|
||||
// sort default first
|
||||
ModularAvatarMenuItem defaultItem = null;
|
||||
if (kvp.Key is ControlGroup cg)
|
||||
{
|
||||
defaultItem = cg.defaultValue;
|
||||
if (defaultItem == null || defaultItem.controlGroup != cg) defaultItem = null;
|
||||
}
|
||||
|
||||
var group = kvp.Value;
|
||||
group.Sort((a, b) =>
|
||||
(b == defaultItem).CompareTo(a == defaultItem));
|
||||
|
||||
// Generate parameter
|
||||
var paramname = "_MA/A/" + kvp.Key.gameObject.name + "/" + (paramIndex++);
|
||||
|
||||
var expParamType = group.Count > 1
|
||||
? VRCExpressionParameters.ValueType.Int
|
||||
: VRCExpressionParameters.ValueType.Bool;
|
||||
|
||||
if (defaultItem == null)
|
||||
{
|
||||
group.Insert(0, null);
|
||||
}
|
||||
|
||||
bool isSaved, isSynced;
|
||||
if (kvp.Key is ControlGroup cg_)
|
||||
{
|
||||
isSaved = cg_.isSaved;
|
||||
isSynced = cg_.isSynced;
|
||||
}
|
||||
else if (kvp.Key is ModularAvatarMenuItem menuItem)
|
||||
{
|
||||
isSaved = menuItem.isSaved;
|
||||
isSynced = menuItem.isSynced;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception("Unknown type " + kvp.Key.GetType());
|
||||
}
|
||||
|
||||
expParameters.Add(new VRCExpressionParameters.Parameter()
|
||||
{
|
||||
name = paramname,
|
||||
defaultValue = 0, // TODO
|
||||
valueType = expParamType,
|
||||
saved = isSaved,
|
||||
networkSynced = isSynced
|
||||
});
|
||||
acParameters.Add(new AnimatorControllerParameter()
|
||||
{
|
||||
name = paramname,
|
||||
type = AnimatorControllerParameterType.Float,
|
||||
defaultFloat = 0, // TODO
|
||||
});
|
||||
|
||||
for (int i = 0; i < group.Count; i++)
|
||||
{
|
||||
if (group[i] == null) continue;
|
||||
var control = group[i].Control;
|
||||
control.parameter = new VRCExpressionsMenu.Control.Parameter() {name = paramname};
|
||||
control.value = i;
|
||||
}
|
||||
|
||||
var blendTree = new BlendTree();
|
||||
blendTree.name = paramname;
|
||||
blendTree.blendParameter = paramname;
|
||||
blendTree.blendType = BlendTreeType.Simple1D;
|
||||
blendTree.useAutomaticThresholds = false;
|
||||
|
||||
List<ChildMotion> children = new List<ChildMotion>();
|
||||
|
||||
List<Motion> motions = GenerateMotions(group, bindings, kvp.Key);
|
||||
|
||||
for (int i = 0; i < motions.Count; i++)
|
||||
{
|
||||
children.Add(new ChildMotion()
|
||||
{
|
||||
motion = motions[i],
|
||||
position = new Vector2(i, 0),
|
||||
threshold = i - 0.1f,
|
||||
timeScale = 1,
|
||||
});
|
||||
children.Add(new ChildMotion()
|
||||
{
|
||||
motion = motions[i],
|
||||
position = new Vector2(i, 0),
|
||||
threshold = i + 0.1f,
|
||||
timeScale = 1,
|
||||
});
|
||||
}
|
||||
|
||||
blendTree.children = children.ToArray();
|
||||
|
||||
_context.SaveAsset(blendTree);
|
||||
blendTrees.Add(blendTree);
|
||||
}
|
||||
|
||||
expParams.parameters = expParameters.ToArray();
|
||||
descriptor.expressionParameters = expParams;
|
||||
|
||||
return blendTrees;
|
||||
}
|
||||
|
||||
void MergeCurves(
|
||||
IDictionary<MenuCurveBinding, (Component, AnimationCurve)> curves,
|
||||
Component controller,
|
||||
Func<SwitchedMenuAction, IDictionary<MenuCurveBinding, AnimationCurve>> getCurves,
|
||||
bool ignoreDuplicates
|
||||
)
|
||||
{
|
||||
if (controller == null) return;
|
||||
|
||||
foreach (var action in controller.GetComponents<SwitchedMenuAction>())
|
||||
{
|
||||
var newCurves = getCurves(action);
|
||||
|
||||
foreach (var curvePair in newCurves)
|
||||
{
|
||||
var binding = curvePair.Key;
|
||||
var curve = curvePair.Value;
|
||||
|
||||
if (curves.TryGetValue(binding, out var existing))
|
||||
{
|
||||
if (!ignoreDuplicates)
|
||||
{
|
||||
BuildReport.LogFatal("animation_gen.conflict", new object[]
|
||||
{
|
||||
binding,
|
||||
existing.Item1.gameObject.name,
|
||||
existing.Item1.GetType().Name,
|
||||
controller.gameObject.name,
|
||||
controller.GetType().Name
|
||||
}, binding.target, existing.Item1, controller);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
curves.Add(binding, (controller, curve));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private List<Motion> GenerateMotions(
|
||||
List<ModularAvatarMenuItem> items,
|
||||
Dictionary<MenuCurveBinding, Component> bindings,
|
||||
Component controller
|
||||
)
|
||||
{
|
||||
Dictionary<MenuCurveBinding, Component> newBindings = new Dictionary<MenuCurveBinding, Component>();
|
||||
|
||||
Dictionary<MenuCurveBinding, (Component, AnimationCurve)> inactiveCurves =
|
||||
new Dictionary<MenuCurveBinding, (Component, AnimationCurve)>();
|
||||
|
||||
if (controller is ControlGroup)
|
||||
{
|
||||
MergeCurves(inactiveCurves, controller, a => a.GetCurves(), false);
|
||||
}
|
||||
|
||||
foreach (var item in items)
|
||||
{
|
||||
MergeCurves(inactiveCurves, item, a => a.GetInactiveCurves(), true);
|
||||
}
|
||||
|
||||
var inactiveMotion = CurvesToMotion(inactiveCurves);
|
||||
var sampleItem = items.FirstOrDefault(i => i != null);
|
||||
String groupName = "(unknown group)";
|
||||
if (sampleItem != null)
|
||||
{
|
||||
groupName = (sampleItem.controlGroup != null
|
||||
? sampleItem.controlGroup.gameObject.name
|
||||
: sampleItem.gameObject.name);
|
||||
}
|
||||
|
||||
inactiveMotion.name =
|
||||
groupName
|
||||
+ " (Inactive)";
|
||||
|
||||
List<Motion> motions = new List<Motion>();
|
||||
|
||||
foreach (var item in items)
|
||||
{
|
||||
Dictionary<MenuCurveBinding, (Component, AnimationCurve)> activeCurves;
|
||||
|
||||
Motion clip;
|
||||
|
||||
if (item == null)
|
||||
{
|
||||
activeCurves = inactiveCurves;
|
||||
clip = inactiveMotion;
|
||||
}
|
||||
else
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
clip = CurvesToMotion(activeCurves);
|
||||
clip.name = groupName + " (" + item.gameObject.name + ")";
|
||||
}
|
||||
|
||||
motions.Add(clip);
|
||||
|
||||
foreach (var binding in activeCurves)
|
||||
{
|
||||
if (!newBindings.ContainsKey(binding.Key))
|
||||
{
|
||||
newBindings.Add(binding.Key, binding.Value.Item1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var binding in newBindings)
|
||||
{
|
||||
if (bindings.TryGetValue(binding.Key, out var bindingValue))
|
||||
{
|
||||
BuildReport.LogFatal("animation_gen.duplicate_binding", new object[]
|
||||
{
|
||||
binding.Key
|
||||
}, binding.Value, bindingValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
bindings.Add(binding.Key, binding.Value);
|
||||
}
|
||||
}
|
||||
|
||||
return motions;
|
||||
}
|
||||
|
||||
Motion CurvesToMotion(IDictionary<MenuCurveBinding, (Component, AnimationCurve)> curves)
|
||||
{
|
||||
var clip = new AnimationClip();
|
||||
_context.SaveAsset(clip);
|
||||
foreach (var entry in curves)
|
||||
{
|
||||
clip.SetCurve(
|
||||
RuntimeUtil.AvatarRootPath(entry.Key.target),
|
||||
entry.Key.type,
|
||||
entry.Key.property,
|
||||
entry.Value.Item2 // curve
|
||||
);
|
||||
}
|
||||
|
||||
return clip;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: be5c2a67d3b448c5bd8c439f537a1766
|
||||
timeCreated: 1677335026
|
@ -1,15 +0,0 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace nadena.dev.modular_avatar.core
|
||||
{
|
||||
/// <summary>
|
||||
/// Tag class that marks components that actions can be attached to.
|
||||
///
|
||||
/// Note that this is public due to C# protection rules, but is not a supported API for editor scripts and may
|
||||
/// change in point releases.
|
||||
/// </summary>
|
||||
[DisallowMultipleComponent]
|
||||
public abstract class ActionController : AvatarTagComponent
|
||||
{
|
||||
}
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d26aaffe335843959445c6a976d517a7
|
||||
timeCreated: 1681386441
|
@ -1,4 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 31ce1123a74443c1ba7a126d4b8919b1
|
||||
timeCreated: 1677317241
|
||||
folderAsset: yes
|
@ -1,80 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using UnityEngine;
|
||||
|
||||
namespace nadena.dev.modular_avatar.core
|
||||
{
|
||||
[AddComponentMenu("Modular Avatar/MA Action Blendshape")]
|
||||
[RequireComponent(typeof(ActionController))]
|
||||
public class ActionBlendshape : AvatarTagComponent, SwitchedMenuAction
|
||||
{
|
||||
[Serializable]
|
||||
public class BlendshapeSpec
|
||||
{
|
||||
public AvatarObjectReference target = new AvatarObjectReference();
|
||||
public string blendshape;
|
||||
public float value;
|
||||
}
|
||||
|
||||
public List<BlendshapeSpec> Blendshapes;
|
||||
|
||||
public bool BindsParameter(TargetParameter parameter)
|
||||
{
|
||||
return parameter == TargetParameter.BaseParameter;
|
||||
}
|
||||
|
||||
public ImmutableDictionary<MenuCurveBinding, AnimationCurve> GetCurves()
|
||||
{
|
||||
ImmutableDictionary<MenuCurveBinding, AnimationCurve>.Builder builder =
|
||||
ImmutableDictionary<MenuCurveBinding, AnimationCurve>.Empty.ToBuilder();
|
||||
|
||||
foreach (var spec in Blendshapes)
|
||||
{
|
||||
var target = spec.target?.Get(this);
|
||||
if (target == null) continue;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(spec.blendshape)) continue;
|
||||
|
||||
builder.Add(
|
||||
new MenuCurveBinding(target, typeof(SkinnedMeshRenderer), "blendShape." + spec.blendshape),
|
||||
AnimationCurve.Constant(0, 1, spec.value)
|
||||
);
|
||||
}
|
||||
|
||||
return builder.ToImmutable();
|
||||
}
|
||||
|
||||
public ImmutableDictionary<MenuCurveBinding, AnimationCurve> GetInactiveCurves()
|
||||
{
|
||||
ImmutableDictionary<MenuCurveBinding, AnimationCurve>.Builder builder =
|
||||
ImmutableDictionary<MenuCurveBinding, AnimationCurve>.Empty.ToBuilder();
|
||||
|
||||
foreach (var spec in Blendshapes)
|
||||
{
|
||||
var target = spec.target?.Get(this);
|
||||
if (target == null) continue;
|
||||
|
||||
var targetRenderer = target.GetComponent<SkinnedMeshRenderer>();
|
||||
if (targetRenderer == null) continue;
|
||||
|
||||
var mesh = targetRenderer.sharedMesh;
|
||||
if (mesh == null) continue;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(spec.blendshape)) continue;
|
||||
|
||||
var blendshapeIndex = mesh.GetBlendShapeIndex(spec.blendshape);
|
||||
if (blendshapeIndex < 0) continue;
|
||||
|
||||
var value = targetRenderer.GetBlendShapeWeight(blendshapeIndex);
|
||||
|
||||
builder.Add(
|
||||
new MenuCurveBinding(target, typeof(SkinnedMeshRenderer), "blendShape." + spec.blendshape),
|
||||
AnimationCurve.Constant(0, 1, value)
|
||||
);
|
||||
}
|
||||
|
||||
return builder.ToImmutable();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b91a8e5892cd4d2c85fcd0b2f2c570e1
|
||||
timeCreated: 1682852930
|
@ -1,66 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using Object = System.Object;
|
||||
|
||||
namespace nadena.dev.modular_avatar.core
|
||||
{
|
||||
[AddComponentMenu("Modular Avatar/MA Action Toggle Object")]
|
||||
[RequireComponent(typeof(ActionController))]
|
||||
public class ActionToggleObject : AvatarTagComponent, SwitchedMenuAction
|
||||
{
|
||||
[Serializable]
|
||||
public class ObjectEntry
|
||||
{
|
||||
public AvatarObjectReference target = new AvatarObjectReference();
|
||||
public bool Active;
|
||||
}
|
||||
|
||||
public List<ObjectEntry> Objects;
|
||||
|
||||
protected override void OnValidate()
|
||||
{
|
||||
base.OnValidate();
|
||||
|
||||
if (Objects == null)
|
||||
{
|
||||
Objects = new List<ObjectEntry>();
|
||||
}
|
||||
}
|
||||
|
||||
public ImmutableDictionary<MenuCurveBinding, AnimationCurve> GetCurves()
|
||||
{
|
||||
return Objects.Select(obj =>
|
||||
new KeyValuePair<MenuCurveBinding, AnimationCurve>(
|
||||
new MenuCurveBinding(obj.target.Get(this), typeof(GameObject), "m_IsActive"),
|
||||
AnimationCurve.Constant(0, 1, obj.Active ? 1 : 0))
|
||||
).ToImmutableDictionary();
|
||||
}
|
||||
|
||||
public ImmutableDictionary<MenuCurveBinding, AnimationCurve> GetInactiveCurves()
|
||||
{
|
||||
var builder = ImmutableDictionary<MenuCurveBinding, AnimationCurve>.Empty.ToBuilder();
|
||||
|
||||
foreach (var obj in Objects)
|
||||
{
|
||||
var target = obj.target?.Get(this);
|
||||
|
||||
if (target == null) continue;
|
||||
|
||||
builder.Add(
|
||||
new MenuCurveBinding(target, typeof(GameObject), "m_IsActive"),
|
||||
AnimationCurve.Constant(0, 1, 0)
|
||||
);
|
||||
}
|
||||
|
||||
return builder.ToImmutable();
|
||||
}
|
||||
|
||||
public bool BindsParameter(TargetParameter parameter)
|
||||
{
|
||||
return parameter == TargetParameter.BaseParameter;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fcac4d9412424173bd294ffd5fc5f9db
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: a8edd5bd1a0a64a40aa99cc09fb5f198, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -1,13 +0,0 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace nadena.dev.modular_avatar.core
|
||||
{
|
||||
[AddComponentMenu("Modular Avatar/MA Control Group")]
|
||||
public class ControlGroup : AvatarTagComponent
|
||||
{
|
||||
public bool isSynced = true;
|
||||
public bool isSaved = true;
|
||||
|
||||
public ModularAvatarMenuItem defaultValue;
|
||||
}
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 99c60d83ad614e81a0488d98b83b5c1c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: a8edd5bd1a0a64a40aa99cc09fb5f198, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -1,103 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Immutable;
|
||||
using UnityEngine;
|
||||
|
||||
namespace nadena.dev.modular_avatar.core
|
||||
{
|
||||
public interface MenuAction
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns whether this action binds to the given parameter.
|
||||
/// </summary>
|
||||
/// <param name="parameter"></param>
|
||||
/// <returns></returns>
|
||||
bool BindsParameter(TargetParameter parameter);
|
||||
}
|
||||
|
||||
public interface SwitchedMenuAction : MenuAction
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns the curves applied when this action is active
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
ImmutableDictionary<MenuCurveBinding, AnimationCurve> GetCurves();
|
||||
|
||||
/// <summary>
|
||||
/// Returns the curves applied when this action is inactive (and no other actions override).
|
||||
/// In general, this should depend only on the path and state of the target objects, and not on the
|
||||
/// configuration of the action itself (apart from target selection).
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
ImmutableDictionary<MenuCurveBinding, AnimationCurve> GetInactiveCurves();
|
||||
}
|
||||
|
||||
public enum TargetParameter
|
||||
{
|
||||
BaseParameter,
|
||||
RadialParam,
|
||||
Horizontal,
|
||||
Vertical,
|
||||
Up,
|
||||
Right,
|
||||
Down,
|
||||
Left
|
||||
}
|
||||
|
||||
public static class TargetParameterExtension
|
||||
{
|
||||
public static int Index(this TargetParameter p)
|
||||
{
|
||||
switch (p)
|
||||
{
|
||||
case TargetParameter.BaseParameter:
|
||||
return -1;
|
||||
case TargetParameter.RadialParam: return 0;
|
||||
case TargetParameter.Horizontal: return 0;
|
||||
case TargetParameter.Vertical: return 1;
|
||||
case TargetParameter.Up: return 0;
|
||||
case TargetParameter.Right: return 1;
|
||||
case TargetParameter.Down: return 2;
|
||||
case TargetParameter.Left: return 3;
|
||||
default: throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class MenuCurveBinding
|
||||
{
|
||||
public readonly GameObject target;
|
||||
public readonly Type type;
|
||||
public readonly string property;
|
||||
|
||||
public MenuCurveBinding(GameObject target, Type type, string property)
|
||||
{
|
||||
this.target = target;
|
||||
this.type = type;
|
||||
this.property = property;
|
||||
}
|
||||
|
||||
private bool Equals(MenuCurveBinding other)
|
||||
{
|
||||
return target == other.target && type == other.type && property == other.property;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (ReferenceEquals(null, obj)) return false;
|
||||
if (ReferenceEquals(this, obj)) return true;
|
||||
if (obj.GetType() != this.GetType()) return false;
|
||||
return Equals((MenuCurveBinding) obj);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
var hashCode = (target != null ? target.GetHashCode() : 0);
|
||||
hashCode = (hashCode * 397) ^ (type != null ? type.GetHashCode() : 0);
|
||||
hashCode = (hashCode * 397) ^ (property != null ? property.GetHashCode() : 0);
|
||||
return hashCode;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 33005b045768474d8ae6f789aa03361b
|
||||
timeCreated: 1677595006
|
@ -1,14 +0,0 @@
|
||||
namespace nadena.dev.modular_avatar.core
|
||||
{
|
||||
/// <summary>
|
||||
/// Tag class which overrides the default configuration of a control group. This is primarily used for asset-based
|
||||
/// shared control groups which can't be configured in place.
|
||||
/// </summary>
|
||||
public class SetGroupDefaults : AvatarTagComponent
|
||||
{
|
||||
public bool isSynced;
|
||||
public bool isSaved;
|
||||
public ModularAvatarMenuItem defaultValue;
|
||||
public ControlGroup targetControlGroup;
|
||||
}
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a6629f9f600742c5856a09779ca0bf12
|
||||
timeCreated: 1683264633
|
@ -14,15 +14,13 @@ namespace nadena.dev.modular_avatar.core
|
||||
}
|
||||
|
||||
[AddComponentMenu("Modular Avatar/MA Menu Item")]
|
||||
public class ModularAvatarMenuItem : ActionController, MenuSource
|
||||
public class ModularAvatarMenuItem : AvatarTagComponent, MenuSource
|
||||
{
|
||||
public VRCExpressionsMenu.Control Control;
|
||||
public SubmenuSource MenuSource;
|
||||
|
||||
public GameObject menuSource_otherObjectChildren;
|
||||
|
||||
[FormerlySerializedAs("toggleGroup")] public ControlGroup controlGroup;
|
||||
|
||||
/// <summary>
|
||||
/// If no control group is set (and an action is linked), this controls whether this control is synced.
|
||||
/// </summary>
|
||||
|
@ -14,7 +14,7 @@ AudioImporter:
|
||||
forceToMono: 1
|
||||
normalize: 1
|
||||
preloadAudioData: 1
|
||||
loadInBackground: 1
|
||||
loadInBackground: 0
|
||||
ambisonic: 0
|
||||
3D: 1
|
||||
userData:
|
||||
|
@ -1,173 +0,0 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!1 &186869909411364112
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 186869909411364113}
|
||||
- component: {fileID: 186869909411364116}
|
||||
- component: {fileID: 186869909411364115}
|
||||
- component: {fileID: 186869909411364114}
|
||||
m_Layer: 0
|
||||
m_Name: SimpleToggle
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!4 &186869909411364113
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 186869909411364112}
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 1.19, z: 0.61}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_Children:
|
||||
- {fileID: 186869909814968496}
|
||||
m_Father: {fileID: 0}
|
||||
m_RootOrder: 0
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!114 &186869909411364116
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 186869909411364112}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 7ef83cb0c23d4d7c9d41021e544a1978, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
menuToAppend: {fileID: 0}
|
||||
installTargetMenu: {fileID: 0}
|
||||
--- !u!114 &186869909411364115
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 186869909411364112}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 3b29d45007c5493d926d2cd45a489529, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
Control:
|
||||
name:
|
||||
icon: {fileID: 0}
|
||||
type: 102
|
||||
parameter:
|
||||
name:
|
||||
value: 1
|
||||
style: 0
|
||||
subMenu: {fileID: 0}
|
||||
subParameters: []
|
||||
labels: []
|
||||
MenuSource: 0
|
||||
menuSource_otherObjectChildren: {fileID: 0}
|
||||
controlGroup: {fileID: 0}
|
||||
isSynced: 1
|
||||
isSaved: 1
|
||||
--- !u!114 &186869909411364114
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 186869909411364112}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: fcac4d9412424173bd294ffd5fc5f9db, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
Objects:
|
||||
- target:
|
||||
referencePath: SimpleToggle/Cube
|
||||
Active: 1
|
||||
--- !u!1 &186869909814968463
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 186869909814968496}
|
||||
- component: {fileID: 186869909814968498}
|
||||
- component: {fileID: 186869909814968497}
|
||||
m_Layer: 0
|
||||
m_Name: Cube
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 0
|
||||
--- !u!4 &186869909814968496
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 186869909814968463}
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 0.1, y: 0.1, z: 0.1}
|
||||
m_Children: []
|
||||
m_Father: {fileID: 186869909411364113}
|
||||
m_RootOrder: 0
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!33 &186869909814968498
|
||||
MeshFilter:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 186869909814968463}
|
||||
m_Mesh: {fileID: 10202, guid: 0000000000000000e000000000000000, type: 0}
|
||||
--- !u!23 &186869909814968497
|
||||
MeshRenderer:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 186869909814968463}
|
||||
m_Enabled: 1
|
||||
m_CastShadows: 1
|
||||
m_ReceiveShadows: 1
|
||||
m_DynamicOccludee: 1
|
||||
m_MotionVectors: 1
|
||||
m_LightProbeUsage: 1
|
||||
m_ReflectionProbeUsage: 1
|
||||
m_RayTracingMode: 2
|
||||
m_RenderingLayerMask: 1
|
||||
m_RendererPriority: 0
|
||||
m_Materials:
|
||||
- {fileID: 10303, guid: 0000000000000000f000000000000000, type: 0}
|
||||
m_StaticBatchInfo:
|
||||
firstSubMesh: 0
|
||||
subMeshCount: 0
|
||||
m_StaticBatchRoot: {fileID: 0}
|
||||
m_ProbeAnchor: {fileID: 0}
|
||||
m_LightProbeVolumeOverride: {fileID: 0}
|
||||
m_ScaleInLightmap: 1
|
||||
m_ReceiveGI: 1
|
||||
m_PreserveUVs: 0
|
||||
m_IgnoreNormalsForChartDetection: 0
|
||||
m_ImportantGI: 0
|
||||
m_StitchLightmapSeams: 1
|
||||
m_SelectedEditorRenderState: 3
|
||||
m_MinimumChartSize: 4
|
||||
m_AutoUVMaxDistance: 0.5
|
||||
m_AutoUVMaxAngle: 89
|
||||
m_LightmapParameters: {fileID: 0}
|
||||
m_SortingLayerID: 0
|
||||
m_SortingLayer: 0
|
||||
m_SortingOrder: 0
|
@ -1,7 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 15d0a3b505e42654abed54cfbd603f42
|
||||
PrefabImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -1,28 +0,0 @@
|
||||
# Action: Toggle Object
|
||||
|
||||
The Action Toggle Object component allows you to configure a menu item to toggle on or off specific game objects.
|
||||
|
||||
![Action Toggle Object](action-toggle-object.png)
|
||||
|
||||
## When should I use it?
|
||||
|
||||
When you want to toggle on or off specific game objects in response to an expressions menu item.
|
||||
|
||||
## How do I use it?
|
||||
|
||||
Attach an Action Toggle Object component to the same gameobject as a Menu Item component. Then, drag the gameobjects you want to toggle
|
||||
onto the "objects to show/hide" section, and set the checkboxes to reflect the state those objects should have.
|
||||
|
||||
## Default state
|
||||
|
||||
If no menu item is selected as the "group default", then the initial state of these gameobjects (when no menu item is selected) will be the state of the gameobjects in the editor.
|
||||
Any menu items that do not specify the state of those gameobjects will use that initial editor state.
|
||||
|
||||
If a menu item is selected as the "group default", then that menu item will be initially selected. When you select a different menu item, it will by default use the opposite of the group default menu item; if the group default menu item does not set the state of a menu item, then instead it'll use the initial editor state of these gameobjects.
|
||||
|
||||
In other words, you can build an outfit toggle by setting the "group default" to toggle ON the gameobjects responsible for the initially-active outfit, and then just specifying the gameobjects to turn ON for your other outfits. The initial outfit will automatically turn off when you select some other menu item.
|
||||
|
||||
## Limitations
|
||||
|
||||
* Only one control group (or ungrouped menu item) can toggle any particular game object.
|
||||
* Traditional animators in the FX layer will take priority over Action Toggle Object; conversely, Action Toggle Object will take priority over other playable layers.
|
Binary file not shown.
Before Width: | Height: | Size: 43 KiB |
@ -1,34 +0,0 @@
|
||||
# Control Group
|
||||
|
||||
The control group component allows multiple toggles to be grouped so that only one can be selected at a time.
|
||||
|
||||
![Control Group](control-group.png)
|
||||
|
||||
## When should I use it?
|
||||
|
||||
When you want to create an option that can be in one of several states - for example, an outfit switch.
|
||||
|
||||
## How do I use it?
|
||||
|
||||
Add a control group to a Game Object, and point your [Menu Item](menu-item.md) components at the Control Group.
|
||||
The control group can be added to any game object, as long as it's inside your avatar, and does not contain another
|
||||
MA Menu Item. Feel free to put it somewhere convenient for you.
|
||||
|
||||
You can configure the following options on a control group:
|
||||
* Saved: If set, the current setting of the associated toggles will be preserved upon changing worlds or avatars
|
||||
* Synced: If set, the current setting of the associated toggles will be synced and visible to other players
|
||||
* Initial setting: The initial setting of this control group. If you choose "(none selected)", the default will be to
|
||||
have no toggles selected. Otherwise, the specified toggle will be selected by default. Note that if you set a default
|
||||
toggle, deselecting all toggles won't be possible.
|
||||
|
||||
The "Bound menu items" section allows you to see all the menu items linked to this control group.
|
||||
|
||||
Note that the control group is only used with menu items driving [action components](action-toggle-object.md); for
|
||||
traditional toggles driving animator parameters, simply set those toggles to the same parameter name.
|
||||
|
||||
### Attaching Actions to control groups
|
||||
|
||||
You can attach actions such as [Action Toggle Object](action-toggle-object.md) to a control group. These actions will be
|
||||
applied as defaults when selecting a menu item which does not specify what to do with a particular object. This can be
|
||||
used to, for example, turn off the initial outfit in an outfit selector, so that you don't need to copy those toggles
|
||||
to every alternate outfit.
|
Binary file not shown.
Before Width: | Height: | Size: 99 KiB |
@ -6,7 +6,7 @@ The Menu Item component allows you to define an expressions menu item from withi
|
||||
|
||||
## When should I use it?
|
||||
|
||||
This component can provide a more convenient way to edit and define menu items than defining VRC Expressions Menu assets. You can move menu items around by dragging and dropping them through the hierarchy, and it provides an editor interface that is aware of parameter names defined on [MA Parameters](parameters) components. In combination with [action components](action-toggle-object), you can even define object toggles without having to create any animations or animators.
|
||||
This component can provide a more convenient way to edit and define menu items than defining VRC Expressions Menu assets. You can move menu items around by dragging and dropping them through the hierarchy, and it provides an editor interface that is aware of parameter names defined on [MA Parameters](parameters) components.
|
||||
|
||||
## How do I use it?
|
||||
|
||||
@ -16,7 +16,7 @@ A full tutorial on using the menu editor system is available [here](../tutorials
|
||||
|
||||
:::
|
||||
|
||||
The menu item component defines a single menu item in a larger menu. You can configure the icon, menu type, [control group](control-group), and parameter for the menu item. The name of the menu item will be taken from the name of the containing game object. This lets you see the name of, and rename menu items directly from the hierarchy.
|
||||
The menu item component defines a single menu item in a larger menu. You can configure the icon, menu type, and parameter for the menu item. The name of the menu item will be taken from the name of the containing game object. This lets you see the name of, and rename menu items directly from the hierarchy.
|
||||
|
||||
### Submenus
|
||||
|
||||
@ -34,10 +34,4 @@ In order to define where a menu item will go in the menu, another component will
|
||||
* The menu item can be on the same game object as a [Menu Installer](menu-installer) component.
|
||||
* The menu item can be the child of a [Menu Group](menu-group) object (which would typically be on a game object with a Menu Installer component)
|
||||
|
||||
Unbound menu items have no effect.
|
||||
|
||||
### Using with actions
|
||||
|
||||
If an [action component](action-toggle-object) is on the same object as the menu item, the menu item will be configured to control this action component, instead of controlling an arbitrary parameter. See the action component documentation for details.
|
||||
|
||||
When an action component is present on the same object, you can no longer select the parameter name for the menu item; Modular Avatar will automatically assign a parameter at build time. By default, a boolean parameter will be created; if you attach a [control group](control-group) to the menu item, an int parameter will be used instead.
|
||||
Unbound menu items have no effect.
|
@ -48,59 +48,6 @@ When setting parameters, you can click the arrow next to the parameter name box
|
||||
|
||||
![Parameter search](param-search.png)
|
||||
|
||||
## Toggles
|
||||
|
||||
Modular Avatar also supports creating simple toggle animations directly from the hierarchy. First, let's look at a simple ON/OFF toggle.
|
||||
|
||||
![Simple toggle](simple-toggle.png)
|
||||
|
||||
This object is available as the "Simple Toggle" sample. As you can see, all we've done is add a "MA Action Toggle Object" to our MA Menu Item, set the menu item to "Toggle", and added the objects we want to show or hide.
|
||||
|
||||
The checkbox next to the cube indicates that the cube should show when the toggle is on, and hide when the toggle is off. If you clear the checkbox, then the cube will be visible when the menu toggle is off, and hidden when on.
|
||||
|
||||
:::tip
|
||||
|
||||
You can drag-and-drop objects directly to the object list to easily add them!
|
||||
|
||||
:::
|
||||
|
||||
### Multi-option toggles
|
||||
|
||||
In some cases you might want more complex toggles. This can be done by adding a "MA Control Group" to group the toggles. Let's look at an example.
|
||||
|
||||
![Sample control group object](control-group.png)
|
||||
|
||||
Here we have a clothing menu which has a control group on it. The main job of the control group is to to tie together the menu items, so that only one can be selected at a time. Here's what those look like:
|
||||
|
||||
![Clothing menu item: Default](clothes-0.png)
|
||||
![Clothing menu item: Blanchir](clothes-1.png)
|
||||
![Clothing menu item: SailorOnepiece](clothes-2.png)
|
||||
|
||||
As you can see, each of these menu items has a "Toggle" type, and a "MA Action Toggle Object" component.
|
||||
What's different here is that we've specified the control group object under "Control Group".
|
||||
When you specify a control group, only one menu item out of the control group can be selected at a time. This essentially means they'll be bound to the same parameter.
|
||||
|
||||
The control group can be placed on any object, provided that you don't have a Menu Item on that same game object. Feel
|
||||
free to put it wherever it makes sense in your hierarchy.
|
||||
|
||||
We've also set an additional Action Toggle Object on the control group itself. These toggles will be applied by default -
|
||||
this lets us turn off the default Kikyo outfit in all of our additional outfits that we're adding.
|
||||
|
||||
You can select one item out of your control group to be the default item on the control group. If you select the "(none
|
||||
selected)" option, then it'll be possible to turn off all toggles, selecting the control group defaults.
|
||||
|
||||
Finally, you can set on the control group whether the parameter is saved and/or synced.
|
||||
|
||||
### Limitations
|
||||
|
||||
This feature is in preview and has a number of limitations you should be aware of:
|
||||
|
||||
1. Objects can only be controlled by one control group or ungrouped toggle at this time.
|
||||
2. Only GameObjects can be toggled; there is not yet support for toggling individual components, blend shapes, etc.
|
||||
3. A toggle cannot both control actions and traditional animators at the same time.
|
||||
|
||||
These limitations will be improved on in future releases.
|
||||
|
||||
## Using on reusable assets
|
||||
|
||||
You can use the new menu item controls on reusable assets as well. Take a look at the Fingerpen or SimpleToggle assets for an example.
|
||||
|
@ -1,19 +0,0 @@
|
||||
# Action: Toggle Object
|
||||
|
||||
Action Toggle Objectコンポーネントでは、特定のゲームオブジェクトをオンまたはオフに切り替えるようにメニュー項目を設定できます。
|
||||
|
||||
![Action Toggle Object](action-toggle-object.png)
|
||||
|
||||
## いつ使うもの?
|
||||
|
||||
丸メニュー(Expressions Menu)の選択に応じて、特定のゲームオブジェクトをON/OFFしたいとき。
|
||||
|
||||
## 使い方
|
||||
|
||||
[Menu Item](menu-item)コンポーネントのあるゲームオブジェクトにAction Toggle Objectを追加しましょう。
|
||||
そのあと、トグルしたいゲームオブジェクトをリストにドラッグアンドドロップして、非表示にしたい場合はチェックを外しましょう。
|
||||
|
||||
## 制限
|
||||
|
||||
* 特定のオブジェクトには、一つのコントロールグループ(またはグループが設定されてないMenu Item)しか操作できません。
|
||||
* 通常のFXアニメーションがAction Toggle Objectより優先されます。逆に、FX以外のレイヤーだとAction Toggle Objectが優先される場合があります。
|
Binary file not shown.
Before Width: | Height: | Size: 58 KiB |
@ -1,33 +0,0 @@
|
||||
# Control Group
|
||||
|
||||
Control Groupコンポーネントでは、複数のトグルをグループ化して、一度に1つだけが選択されるようにできます。
|
||||
|
||||
![Control Group](control-group.png)
|
||||
|
||||
## いつ使うの?
|
||||
|
||||
衣装変更スイッチなど、複数の状態から1つを選択できるオプションを作成する場合に使用します。
|
||||
|
||||
## どう使うの?
|
||||
|
||||
まずはGame ObjectにControl Groupを追加して、[Menu Item](menu-item.md)コンポーネントをControl Groupに向けてください。
|
||||
Control Groupは、アバター内にある任意のGame Objectに追加できますが、他のMA Menu Itemと同じオブジェクトには追加できません。
|
||||
わかりやすい場所に追加してください。
|
||||
|
||||
コントロールグループには以下の設定があります。
|
||||
|
||||
* 「保存する」 このオプションを有効にすると、ワールド移動やアバター変更時に、関連するトグルの現在の設定が保存されます。
|
||||
* 「同期する」 このオプションを有効にすると、選択が他のプレイヤーにも同期されます。
|
||||
* 「初期値」このコントロールグループの初期設定です。 「(どれも選択されない)」を選択すると、デフォルトではトグルが選択されていない状態になります。
|
||||
それ以外の場合、指定したトグルがデフォルトで選択されます。 ただし、デフォルトのトグルを設定した場合、すべてのトグルを選択解除することはできません。
|
||||
|
||||
「関連付けされたメニューアイテム」には、このコントロールグループにリンクされているすべてのメニュー項目を確認できます。
|
||||
|
||||
なお、Control Groupは、[action components](action-toggle-object.md)を使用しているメニュー項目とのみ使用されます。
|
||||
アニメーターパラメーターを駆動する通常のトグルの場合は、単にそれらのトグルを同じパラメーター名に設定してください。
|
||||
|
||||
### コントロールグループにアクションを追加
|
||||
|
||||
コントロールグループに[Action Toggle Object](action-toggle-object.md)などのアクションを追加できます。
|
||||
これらのアクションは、特定のオブジェクトに対して何を行うかを指定しないメニュー項目を選択したときに、デフォルトとして適用されます。
|
||||
例えば、衣装選択で最初の衣装をオフにすることで、それぞれの代替衣装にデフォルト衣装消し設定をいちいち追加する必要がなくなります。
|
Binary file not shown.
Before Width: | Height: | Size: 90 KiB |
@ -8,7 +8,6 @@ MA Menu Itemコンポーネントは、Unityのヒエラルキー内でExpressio
|
||||
|
||||
このコンポーネントを使うことで、VRC Expressions Menuアセットで定義するよりも簡単にメニュー項目を定義できます。
|
||||
ヒエラルキー内でメニュー項目をドラッグアンドドロップすることで、移動できたり、[MA Parameters](parameters)コンポーネントで定義されたパラメーター名を意識したエディターUIを提供します。
|
||||
[アクションコンポーネント](action-toggle-object)と組み合わせることで、アニメーションやアニメーターを作成することなく、オブジェクトのトグルを定義することもできます。
|
||||
|
||||
## どう使うの?
|
||||
|
||||
@ -19,7 +18,6 @@ MA Menu Itemコンポーネントは、Unityのヒエラルキー内でExpressio
|
||||
:::
|
||||
|
||||
メニューアイテムコンポーネントは、メニュー内の単一の項目を定義します。
|
||||
アイコン、メニュータイプ、[コントロールグループ](control-group)、パラメーターなどを設定できます。
|
||||
メニュー項目の名前は、ゲームオブジェクトの名前から取得されます。これでヒエラルキーからメニュー項目の名前を確認したり、名前を変更したりできます。
|
||||
|
||||
### サブメニューについて
|
||||
@ -41,9 +39,3 @@ MA Menu Itemコンポーネントは、Unityのヒエラルキー内でExpressio
|
||||
* メニュー項目を[Menu Group](menu-group)オブジェクトの子に配置する(Menu Installerコンポーネントがあるゲームオブジェクトに配置される場合が多い)。
|
||||
|
||||
配置されないメニュー項目は効果がありません。
|
||||
|
||||
### Actionと併用
|
||||
|
||||
メニュー項目と同じオブジェクトに[アクションコンポーネント](action-toggle-object)がある場合、メニュー項目は任意のパラメーターを制御するのではなく、このアクションコンポーネントを制御するように設定されます。詳細については、[アクションコンポーネントのドキュメント](action-toggle-object)を参照してください。
|
||||
|
||||
同じオブジェクトにアクションコンポーネントがある場合、メニュー項目のパラメーター名を選択できなくなります。その代わりに、Modular Avatarはビルド時にパラメーターを自動的に割り当てます。デフォルトでは、ブールパラメーターが作成されます。メニュー項目に[コントロールグループ](control-group)をアタッチすると、intパラメーターが使用されます。
|
@ -50,59 +50,6 @@ When you do this, a new `Avatar Menu` object will be added to your avatar, conta
|
||||
|
||||
![Parameter search](param-search.png)
|
||||
|
||||
## トグルを作成
|
||||
|
||||
Modular Avatarにはヒエラルキーから簡単なトグルアニメーションを作る機能もあります。まずは簡単なON/OFFトグルを見てみましょう。
|
||||
|
||||
![Simple toggle](simple-toggle.png)
|
||||
|
||||
このオブジェクトは「Simple Toggle」というサンプルとして同封されます。見ての通り、MA Menu ItemにMA Action Toggle Objectを追加し、メニューアイテムをToggleタイプにし、表示・非表示にするオブジェクトを設定しただけです。
|
||||
|
||||
Cubeの隣のチェックは、トグルがONになったときは表示するべきで、OFFになったときは非表示にするべきという意味です。チェックを外すと、逆にメニューがOFFのときに表示し、ONのときは非表示になります。
|
||||
|
||||
:::tip
|
||||
|
||||
オブジェクトリストにドラッグアンドドロップでオブジェクトを簡単に追加できます。
|
||||
|
||||
:::
|
||||
|
||||
### 複数選択トグル
|
||||
|
||||
もうすこし複雑なトグルを作りたいときもあるでしょう。複数のトグルを一つのグループにするためには「MA Control Group」も作ります。例を見てみましょう。
|
||||
|
||||
![Sample control group object](control-group.png)
|
||||
|
||||
こちらはControl Groupがついている衣装切り替えメニューです。Control groupの主な仕事はメニューアイテムを一括りにして、同時に一つまでしか選択できないようにするためにあります。各メニューアイテムも見てみましょう。
|
||||
|
||||
![Clothing menu item: Default](clothes-0.png)
|
||||
![Clothing menu item: Blanchir](clothes-1.png)
|
||||
![Clothing menu item: SailorOnepiece](clothes-2.png)
|
||||
|
||||
見ての通り、どれも「Toggle」タイプで、「MA Action Toggle Object」コンポーネントがついています。
|
||||
違うのは、Control Groupのオブジェクトを「コントロールグループ」に指定しているところです。
|
||||
コントロールグループを指定すると、その中から一つのメニューアイテムしか設定できないようになります。同じパラメーターで連動するというわけです。
|
||||
|
||||
翻訳:コントロールグループは、同じゲームオブジェクトにMA Menu Itemがない限り、どこにでも置けます。ヒエラルキーのどこにでも置いても構いません。
|
||||
自分にとってわかりやすい所に置きましょう。
|
||||
|
||||
また、コントロールグループにもAction Toggle Objectを追加しました。こちらのトグルはデフォルトで設定されます。これでほかの衣装を選択しているとき、
|
||||
桔梗ちゃんのデフォルト衣装を簡単に切ることができます。
|
||||
|
||||
コントロールグループに連動するアイテムのうちから一つを初期設定にできます。設定しない場合は、どれも選択しないという状態がデフォルトになります。
|
||||
何も選択しない状態では、コントロールグループのデフォルトが適用されます。
|
||||
|
||||
最後に、コントロールグループのほうに保存・同期設定を調整できます。
|
||||
|
||||
### 制限
|
||||
|
||||
この機能は開発途中のもので、いくつか制限があります。
|
||||
|
||||
1. 一つのオブジェクトは一つのコントロールグループまたはグループに入っていないトグルにしか操作できない。
|
||||
2. 現在、GameObjectしかトグルできません。コンポーネント単位のON/OFFやブレンドシェープの操作は未実装です。
|
||||
3. 一つのトグルでアクションと通常のアニメーターを両方操作できません。
|
||||
|
||||
今後のリリースで改善していく予定です。
|
||||
|
||||
## 配布アセットなどでの応用
|
||||
|
||||
新しいメニューアイテムシステムを配布アセットなどでも利用できます。サンプルとしては、FingerpenやSimpleToggleアセットにご参照ください。
|
||||
|
Loading…
Reference in New Issue
Block a user