feat: support unsynced/saved toggles in new menu system (#276)
* chore: add support for synced/saved settings on menu actions * feat: move action defaults to control group * chore: finish the control group ui updates * docs: update tutorial * docs: update control group documentation * docs/ui: menu install target UI and docs
@ -138,7 +138,7 @@ namespace nadena.dev.modular_avatar.editor.ErrorReporting
|
|||||||
private static List<ErrorLog> CheckInternal(ModularAvatarMenuInstaller mi)
|
private static List<ErrorLog> CheckInternal(ModularAvatarMenuInstaller mi)
|
||||||
{
|
{
|
||||||
// TODO - check that target menu is in the avatar
|
// TODO - check that target menu is in the avatar
|
||||||
if (mi.menuToAppend == null && mi.GetComponent<MenuSourceComponent>() == null)
|
if (mi.menuToAppend == null && mi.GetComponent<MenuSource>() == null)
|
||||||
{
|
{
|
||||||
return new List<ErrorLog>()
|
return new List<ErrorLog>()
|
||||||
{
|
{
|
||||||
|
@ -2,19 +2,68 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using UnityEditor;
|
using UnityEditor;
|
||||||
|
using UnityEditor.IMGUI.Controls;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using static nadena.dev.modular_avatar.core.editor.Localization;
|
using static nadena.dev.modular_avatar.core.editor.Localization;
|
||||||
|
|
||||||
namespace nadena.dev.modular_avatar.core.editor
|
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))]
|
[CustomEditor(typeof(ControlGroup))]
|
||||||
internal class ControlGroupInspector : MAEditorBase
|
internal class ControlGroupInspector : MAEditorBase
|
||||||
{
|
{
|
||||||
private bool _showInner;
|
private bool _showInner;
|
||||||
|
private SerializedProperty _isSynced, _isSaved, _defaultValue;
|
||||||
|
private ControlGroupDefaultDropdown _dropdown;
|
||||||
|
|
||||||
private void OnEnable()
|
private void OnEnable()
|
||||||
{
|
{
|
||||||
EditorApplication.hierarchyChanged += Invalidate;
|
EditorApplication.hierarchyChanged += Invalidate;
|
||||||
|
|
||||||
|
_isSynced = serializedObject.FindProperty(nameof(ControlGroup.isSynced));
|
||||||
|
_isSaved = serializedObject.FindProperty(nameof(ControlGroup.isSaved));
|
||||||
|
_defaultValue = serializedObject.FindProperty(nameof(ControlGroup.defaultValue));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnDisable()
|
private void OnDisable()
|
||||||
@ -31,11 +80,22 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
var menuItems = avatar.GetComponentsInChildren<ModularAvatarMenuItem>(true);
|
var menuItems = avatar.GetComponentsInChildren<ModularAvatarMenuItem>(true);
|
||||||
|
|
||||||
_menuItemActions = new List<Action>();
|
_menuItemActions = new List<Action>();
|
||||||
|
var filteredMenuItems = new List<ModularAvatarMenuItem>();
|
||||||
foreach (var menuItem in menuItems.Where(item => item.controlGroup == target))
|
foreach (var menuItem in menuItems.Where(item => item.controlGroup == target))
|
||||||
{
|
{
|
||||||
var node = CreateMenuItemNode(menuItem);
|
var node = CreateMenuItemNode(menuItem);
|
||||||
_menuItemActions.Add(node);
|
_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)
|
private Action CreateMenuItemNode(ModularAvatarMenuItem menuItem)
|
||||||
@ -85,9 +145,43 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Rect _dropdownButtonRect;
|
||||||
|
|
||||||
protected override void OnInnerInspectorGUI()
|
protected override void OnInnerInspectorGUI()
|
||||||
{
|
{
|
||||||
if (_menuItemActions == null) Invalidate();
|
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"));
|
_showInner = EditorGUILayout.Foldout(_showInner, G("control_group.foldout.menu_items"));
|
||||||
if (_showInner)
|
if (_showInner)
|
||||||
@ -108,6 +202,8 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
serializedObject.ApplyModifiedProperties();
|
||||||
|
|
||||||
Localization.ShowLanguageUI();
|
Localization.ShowLanguageUI();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,16 @@
|
|||||||
|
using UnityEditor;
|
||||||
|
|
||||||
|
namespace nadena.dev.modular_avatar.core.editor
|
||||||
|
{
|
||||||
|
[CustomEditor(typeof(ModularAvatarMenuInstallTarget))]
|
||||||
|
internal class MenuInstallTargetEditor : MAEditorBase
|
||||||
|
{
|
||||||
|
protected override void OnInnerInspectorGUI()
|
||||||
|
{
|
||||||
|
serializedObject.Update();
|
||||||
|
EditorGUILayout.PropertyField(
|
||||||
|
serializedObject.FindProperty(nameof(ModularAvatarMenuInstallTarget.installer)));
|
||||||
|
serializedObject.ApplyModifiedProperties();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 4039f724f1af412c98e176c44f0d7734
|
||||||
|
timeCreated: 1681547765
|
@ -353,7 +353,7 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
foreach (var t in targets)
|
foreach (var t in targets)
|
||||||
{
|
{
|
||||||
var installer = (ModularAvatarMenuInstaller) t;
|
var installer = (ModularAvatarMenuInstaller) t;
|
||||||
if (installer.GetComponent<MenuSourceComponent>() || installer.menuToAppend == null) continue;
|
if (installer.GetComponent<MenuSource>() != null || installer.menuToAppend == null) continue;
|
||||||
|
|
||||||
var menu = installer.menuToAppend;
|
var menu = installer.menuToAppend;
|
||||||
if (menu.controls.Count == 0)
|
if (menu.controls.Count == 0)
|
||||||
|
@ -30,7 +30,6 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
private readonly SerializedProperty _value;
|
private readonly SerializedProperty _value;
|
||||||
private readonly SerializedProperty _submenu;
|
private readonly SerializedProperty _submenu;
|
||||||
private readonly SerializedProperty _controlGroup;
|
private readonly SerializedProperty _controlGroup;
|
||||||
private readonly SerializedProperty _isDefault;
|
|
||||||
|
|
||||||
private readonly ParameterGUI _parameterGUI;
|
private readonly ParameterGUI _parameterGUI;
|
||||||
|
|
||||||
@ -46,6 +45,7 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
|
|
||||||
private readonly SerializedProperty _prop_submenuSource;
|
private readonly SerializedProperty _prop_submenuSource;
|
||||||
private readonly SerializedProperty _prop_otherObjSource;
|
private readonly SerializedProperty _prop_otherObjSource;
|
||||||
|
private readonly SerializedProperty _prop_isSynced, _prop_isSaved;
|
||||||
|
|
||||||
public bool AlwaysExpandContents = false;
|
public bool AlwaysExpandContents = false;
|
||||||
public bool ExpandContents = false;
|
public bool ExpandContents = false;
|
||||||
@ -92,7 +92,6 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
|
|
||||||
_name = gameObjects.FindProperty("m_Name");
|
_name = gameObjects.FindProperty("m_Name");
|
||||||
_controlGroup = obj.FindProperty(nameof(ModularAvatarMenuItem.controlGroup));
|
_controlGroup = obj.FindProperty(nameof(ModularAvatarMenuItem.controlGroup));
|
||||||
_isDefault = obj.FindProperty(nameof(ModularAvatarMenuItem.isDefault));
|
|
||||||
|
|
||||||
var control = obj.FindProperty(nameof(ModularAvatarMenuItem.Control));
|
var control = obj.FindProperty(nameof(ModularAvatarMenuItem.Control));
|
||||||
|
|
||||||
@ -110,6 +109,8 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
|
|
||||||
_prop_submenuSource = obj.FindProperty(nameof(ModularAvatarMenuItem.MenuSource));
|
_prop_submenuSource = obj.FindProperty(nameof(ModularAvatarMenuItem.MenuSource));
|
||||||
_prop_otherObjSource = obj.FindProperty(nameof(ModularAvatarMenuItem.menuSource_otherObjectChildren));
|
_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);
|
_previewGUI = new MenuPreviewGUI(redraw);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -133,7 +134,6 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
_submenu = _control.FindPropertyRelative(nameof(VRCExpressionsMenu.Control.subMenu));
|
_submenu = _control.FindPropertyRelative(nameof(VRCExpressionsMenu.Control.subMenu));
|
||||||
|
|
||||||
_controlGroup = null;
|
_controlGroup = null;
|
||||||
_isDefault = null;
|
|
||||||
|
|
||||||
_parameterGUI = new ParameterGUI(parameterReference, parameter, redraw);
|
_parameterGUI = new ParameterGUI(parameterReference, parameter, redraw);
|
||||||
|
|
||||||
@ -142,6 +142,8 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
|
|
||||||
_prop_submenuSource = null;
|
_prop_submenuSource = null;
|
||||||
_prop_otherObjSource = null;
|
_prop_otherObjSource = null;
|
||||||
|
_prop_isSynced = null;
|
||||||
|
_prop_isSaved = null;
|
||||||
_previewGUI = new MenuPreviewGUI(redraw);
|
_previewGUI = new MenuPreviewGUI(redraw);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -165,12 +167,11 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
if (_hasActions)
|
if (_hasActions)
|
||||||
{
|
{
|
||||||
EditorGUILayout.PropertyField(_controlGroup, G("menuitem.prop.control_group"));
|
EditorGUILayout.PropertyField(_controlGroup, G("menuitem.prop.control_group"));
|
||||||
if (_controlGroup.hasMultipleDifferentValues || _controlGroup.objectReferenceValue != null)
|
|
||||||
|
if (!_controlGroup.hasMultipleDifferentValues && _controlGroup.objectReferenceValue == null)
|
||||||
{
|
{
|
||||||
using (new EditorGUI.DisabledScope(_obj.isEditingMultipleObjects))
|
EditorGUILayout.PropertyField(_prop_isSynced, G("menuitem.prop.is_synced"));
|
||||||
{
|
EditorGUILayout.PropertyField(_prop_isSaved, G("menuitem.prop.is_saved"));
|
||||||
EditorGUILayout.PropertyField(_isDefault, G("menuitem.prop.is_default"));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -102,6 +102,10 @@
|
|||||||
"menuitem.prop.submenu_source.tooltip": "Where to find the items to put inside this submenu",
|
"menuitem.prop.submenu_source.tooltip": "Where to find the items to put inside this submenu",
|
||||||
"menuitem.prop.source_override": "Source object override",
|
"menuitem.prop.source_override": "Source object override",
|
||||||
"menuitem.prop.source_override.tooltip": "If specified, this object will be used as the source for the contents of the submenu. Otherwise, children of this menu item will be used.",
|
"menuitem.prop.source_override.tooltip": "If specified, this object will be used as the source for the contents of the submenu. Otherwise, children of this menu item will be used.",
|
||||||
|
"menuitem.prop.is_saved": "Saved",
|
||||||
|
"menuitem.prop.is_saved.tooltip": "If true, the value of this menu item will be saved when you change avatars or worlds.",
|
||||||
|
"menuitem.prop.is_synced": "Synced",
|
||||||
|
"menuitem.prop.is_synced.tooltip": "If true, the value of this menu item will be synced to other players across the network.",
|
||||||
"menuitem.param.rotation": "Parameter: Rotation",
|
"menuitem.param.rotation": "Parameter: Rotation",
|
||||||
"menuitem.param.rotation.tooltip": "The parameter to set based on the rotation of this menu item",
|
"menuitem.param.rotation.tooltip": "The parameter to set based on the rotation of this menu item",
|
||||||
"menuitem.param.horizontal": "Parameter: Horizontal",
|
"menuitem.param.horizontal": "Parameter: Horizontal",
|
||||||
@ -120,6 +124,12 @@
|
|||||||
"menuitem.param.controlled_by_action": "<controlled by action>",
|
"menuitem.param.controlled_by_action": "<controlled by action>",
|
||||||
"control_group.foldout.actions": "Actions",
|
"control_group.foldout.actions": "Actions",
|
||||||
"control_group.foldout.menu_items": "Bound menu items",
|
"control_group.foldout.menu_items": "Bound menu items",
|
||||||
|
"control_group.is_saved": "Saved",
|
||||||
|
"control_group.is_saved.tooltip": "If true, the value of this menu item will be saved when you change avatars or worlds.",
|
||||||
|
"control_group.is_synced": "Synced",
|
||||||
|
"control_group.is_synced.tooltip": "If true, the value of this menu item will be synced to other players across the network.",
|
||||||
|
"control_group.default_value": "Initial setting",
|
||||||
|
"control_group.default_value.unset": "(none selected)",
|
||||||
"menuitem.prop.control_group": "Control Group",
|
"menuitem.prop.control_group": "Control Group",
|
||||||
"menuitem.prop.control_group.tooltip": "Only one toggle in a given group can be selected at the same time",
|
"menuitem.prop.control_group.tooltip": "Only one toggle in a given group can be selected at the same time",
|
||||||
"menuitem.prop.is_default": "Is Group Default",
|
"menuitem.prop.is_default": "Is Group Default",
|
||||||
|
@ -99,6 +99,10 @@
|
|||||||
"menuitem.prop.submenu_source.tooltip": "このサブメニューの内容をどこから引用するべきかを指定",
|
"menuitem.prop.submenu_source.tooltip": "このサブメニューの内容をどこから引用するべきかを指定",
|
||||||
"menuitem.prop.source_override": "引用元オブジェクト",
|
"menuitem.prop.source_override": "引用元オブジェクト",
|
||||||
"menuitem.prop.source_override.tooltip": "指定した場合は、指定したオブジェクトの子をメニューの内容として指定します。指定されてない場合はこのオブジェクト直下の子を使用します。",
|
"menuitem.prop.source_override.tooltip": "指定した場合は、指定したオブジェクトの子をメニューの内容として指定します。指定されてない場合はこのオブジェクト直下の子を使用します。",
|
||||||
|
"menuitem.prop.is_saved": "保存する",
|
||||||
|
"menuitem.prop.is_saved.tooltip": "有効になっていると、アバター変更やワールド移動するときこの設定が保持されます。",
|
||||||
|
"menuitem.prop.is_synced": "同期する",
|
||||||
|
"menuitem.prop.is_synced.tooltip": "有効の場合はほかのプレイヤーに同期されます。",
|
||||||
"menuitem.param.rotation": "回転パラメーター名",
|
"menuitem.param.rotation": "回転パラメーター名",
|
||||||
"menuitem.param.rotation.tooltip": "このメニューアイテムの回転に連動するべきパラメーター",
|
"menuitem.param.rotation.tooltip": "このメニューアイテムの回転に連動するべきパラメーター",
|
||||||
"menuitem.param.horizontal": "横パラメーター名",
|
"menuitem.param.horizontal": "横パラメーター名",
|
||||||
@ -117,6 +121,12 @@
|
|||||||
"menuitem.param.controlled_by_action": "<アクションで制御されています>",
|
"menuitem.param.controlled_by_action": "<アクションで制御されています>",
|
||||||
"control_group.foldout.actions": "アクション",
|
"control_group.foldout.actions": "アクション",
|
||||||
"control_group.foldout.menu_items": "関連付けされたメニューアイテム",
|
"control_group.foldout.menu_items": "関連付けされたメニューアイテム",
|
||||||
|
"control_group.is_saved": "保存する",
|
||||||
|
"control_group.is_saved.tooltip": "有効になっていると、アバター変更やワールド移動するときこの設定が保持されます。",
|
||||||
|
"control_group.is_synced": "同期する",
|
||||||
|
"control_group.is_synced.tooltip": "有効の場合はほかのプレイヤーに同期されます。",
|
||||||
|
"control_group.default_value": "初期値",
|
||||||
|
"control_group.default_value.unset": "(どれも選択されない)",
|
||||||
"menuitem.prop.control_group": "コントロールグループ",
|
"menuitem.prop.control_group": "コントロールグループ",
|
||||||
"menuitem.prop.control_group.tooltip": "同じグループ内では、一つのトグルしか起動できません",
|
"menuitem.prop.control_group.tooltip": "同じグループ内では、一つのトグルしか起動できません",
|
||||||
"menuitem.prop.is_default": "グループの初期設定にする",
|
"menuitem.prop.is_default": "グループの初期設定にする",
|
||||||
|
@ -70,7 +70,7 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
|
|
||||||
controller.parameters = parameters.ToArray();
|
controller.parameters = parameters.ToArray();
|
||||||
|
|
||||||
int layersToInsert = 1; // TODO
|
int layersToInsert = 2; // TODO
|
||||||
|
|
||||||
var rootBlendTree = GenerateRootBlendLayer(actions);
|
var rootBlendTree = GenerateRootBlendLayer(actions);
|
||||||
AdjustAllBehaviors(controller, b =>
|
AdjustAllBehaviors(controller, b =>
|
||||||
@ -86,8 +86,8 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
}
|
}
|
||||||
|
|
||||||
var layerList = controller.layers.ToList();
|
var layerList = controller.layers.ToList();
|
||||||
//layerList.Insert(0, GenerateBlendshapeBaseLayer(avatar));
|
layerList.Insert(0, GenerateBlendshapeBaseLayer(avatar));
|
||||||
//rootBlendTree.defaultWeight = 1;
|
rootBlendTree.defaultWeight = 1;
|
||||||
layerList.Insert(0, rootBlendTree);
|
layerList.Insert(0, rootBlendTree);
|
||||||
layerList[1].defaultWeight = 1;
|
layerList[1].defaultWeight = 1;
|
||||||
controller.layers = layerList.ToArray();
|
controller.layers = layerList.ToArray();
|
||||||
@ -214,8 +214,8 @@ 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>();
|
||||||
|
|
||||||
Dictionary<Component, List<ModularAvatarMenuItem>> groupedItems =
|
Dictionary<ActionController, List<ModularAvatarMenuItem>> groupedItems =
|
||||||
new Dictionary<Component, List<ModularAvatarMenuItem>>();
|
new Dictionary<ActionController, List<ModularAvatarMenuItem>>();
|
||||||
|
|
||||||
foreach (var item in items)
|
foreach (var item in items)
|
||||||
{
|
{
|
||||||
@ -243,8 +243,16 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
foreach (var kvp in groupedItems)
|
foreach (var kvp in groupedItems)
|
||||||
{
|
{
|
||||||
// sort default first
|
// 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;
|
var group = kvp.Value;
|
||||||
group.Sort((a, b) => b.isDefault.CompareTo(a.isDefault));
|
group.Sort((a, b) =>
|
||||||
|
(b == defaultItem).CompareTo(a == defaultItem));
|
||||||
|
|
||||||
// Generate parameter
|
// Generate parameter
|
||||||
var paramname = "_MA/A/" + kvp.Key.gameObject.name + "/" + (paramIndex++);
|
var paramname = "_MA/A/" + kvp.Key.gameObject.name + "/" + (paramIndex++);
|
||||||
@ -253,12 +261,20 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
? VRCExpressionParameters.ValueType.Int
|
? VRCExpressionParameters.ValueType.Int
|
||||||
: VRCExpressionParameters.ValueType.Bool;
|
: VRCExpressionParameters.ValueType.Bool;
|
||||||
|
|
||||||
|
if (defaultItem == null)
|
||||||
|
{
|
||||||
|
group.Insert(0, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isSaved = kvp.Key.isSavedProp, isSynced = kvp.Key.isSyncedProp;
|
||||||
|
|
||||||
expParameters.Add(new VRCExpressionParameters.Parameter()
|
expParameters.Add(new VRCExpressionParameters.Parameter()
|
||||||
{
|
{
|
||||||
name = paramname,
|
name = paramname,
|
||||||
defaultValue = 0, // TODO
|
defaultValue = 0, // TODO
|
||||||
valueType = expParamType,
|
valueType = expParamType,
|
||||||
saved = false, // TODO
|
saved = isSaved,
|
||||||
|
networkSynced = isSynced
|
||||||
});
|
});
|
||||||
acParameters.Add(new AnimatorControllerParameter()
|
acParameters.Add(new AnimatorControllerParameter()
|
||||||
{
|
{
|
||||||
@ -267,12 +283,12 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
defaultFloat = 0, // TODO
|
defaultFloat = 0, // TODO
|
||||||
});
|
});
|
||||||
|
|
||||||
var hasDefault = group[0].isDefault;
|
|
||||||
for (int i = 0; i < group.Count; i++)
|
for (int i = 0; i < group.Count; i++)
|
||||||
{
|
{
|
||||||
|
if (group[i] == null) continue;
|
||||||
var control = group[i].Control;
|
var control = group[i].Control;
|
||||||
control.parameter = new VRCExpressionsMenu.Control.Parameter() {name = paramname};
|
control.parameter = new VRCExpressionsMenu.Control.Parameter() {name = paramname};
|
||||||
control.value = hasDefault ? i : i + 1;
|
control.value = i;
|
||||||
}
|
}
|
||||||
|
|
||||||
var blendTree = new BlendTree();
|
var blendTree = new BlendTree();
|
||||||
@ -283,12 +299,7 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
|
|
||||||
List<ChildMotion> children = new List<ChildMotion>();
|
List<ChildMotion> children = new List<ChildMotion>();
|
||||||
|
|
||||||
List<Motion> motions = GenerateMotions(group, bindings, out var inactiveMotion);
|
List<Motion> motions = GenerateMotions(group, bindings, kvp.Key);
|
||||||
|
|
||||||
if (!hasDefault)
|
|
||||||
{
|
|
||||||
motions.Insert(0, inactiveMotion);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < motions.Count; i++)
|
for (int i = 0; i < motions.Count; i++)
|
||||||
{
|
{
|
||||||
@ -322,12 +333,14 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
|
|
||||||
void MergeCurves(
|
void MergeCurves(
|
||||||
IDictionary<MenuCurveBinding, (Component, AnimationCurve)> curves,
|
IDictionary<MenuCurveBinding, (Component, AnimationCurve)> curves,
|
||||||
ModularAvatarMenuItem item,
|
ActionController controller,
|
||||||
Func<SwitchedMenuAction, IDictionary<MenuCurveBinding, AnimationCurve>> getCurves,
|
Func<SwitchedMenuAction, IDictionary<MenuCurveBinding, AnimationCurve>> getCurves,
|
||||||
bool ignoreDuplicates
|
bool ignoreDuplicates
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
foreach (var action in item.GetComponents<SwitchedMenuAction>())
|
if (controller == null) return;
|
||||||
|
|
||||||
|
foreach (var action in controller.GetComponents<SwitchedMenuAction>())
|
||||||
{
|
{
|
||||||
var newCurves = getCurves(action);
|
var newCurves = getCurves(action);
|
||||||
|
|
||||||
@ -345,14 +358,14 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
binding,
|
binding,
|
||||||
existing.Item1.gameObject.name,
|
existing.Item1.gameObject.name,
|
||||||
existing.Item1.GetType().Name,
|
existing.Item1.GetType().Name,
|
||||||
item.gameObject.name,
|
controller.gameObject.name,
|
||||||
item.GetType().Name
|
controller.GetType().Name
|
||||||
}, binding.target, existing.Item1, item);
|
}, binding.target, existing.Item1, controller);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
curves.Add(binding, (item, curve));
|
curves.Add(binding, (controller, curve));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -361,40 +374,34 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
private List<Motion> GenerateMotions(
|
private List<Motion> GenerateMotions(
|
||||||
List<ModularAvatarMenuItem> items,
|
List<ModularAvatarMenuItem> items,
|
||||||
Dictionary<MenuCurveBinding, Component> bindings,
|
Dictionary<MenuCurveBinding, Component> bindings,
|
||||||
out Motion inactiveMotion)
|
ActionController controller
|
||||||
|
)
|
||||||
{
|
{
|
||||||
Dictionary<MenuCurveBinding, Component> newBindings = new Dictionary<MenuCurveBinding, Component>();
|
Dictionary<MenuCurveBinding, Component> newBindings = new Dictionary<MenuCurveBinding, Component>();
|
||||||
|
|
||||||
Dictionary<MenuCurveBinding, (Component, AnimationCurve)> inactiveCurves =
|
Dictionary<MenuCurveBinding, (Component, AnimationCurve)> inactiveCurves =
|
||||||
new Dictionary<MenuCurveBinding, (Component, AnimationCurve)>();
|
new Dictionary<MenuCurveBinding, (Component, AnimationCurve)>();
|
||||||
|
|
||||||
var defaultItems = items.Where(i => i.isDefault).ToList();
|
if (controller is ControlGroup)
|
||||||
if (defaultItems.Count > 1)
|
|
||||||
{
|
{
|
||||||
BuildReport.LogFatal("animation_gen.multiple_defaults",
|
MergeCurves(inactiveCurves, controller, a => a.GetCurves(), false);
|
||||||
strings: Array.Empty<object>(),
|
|
||||||
objects: defaultItems.ToArray<UnityEngine.Object>()
|
|
||||||
);
|
|
||||||
defaultItems.RemoveRange(1, defaultItems.Count - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (defaultItems.Count > 0)
|
|
||||||
{
|
|
||||||
MergeCurves(inactiveCurves, defaultItems[0], a => a.GetInactiveCurves(true), false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var item in items)
|
foreach (var item in items)
|
||||||
{
|
{
|
||||||
if (defaultItems.Count == 0 || defaultItems[0] != item)
|
MergeCurves(inactiveCurves, item, a => a.GetInactiveCurves(false), true);
|
||||||
{
|
}
|
||||||
MergeCurves(inactiveCurves, item, a => a.GetInactiveCurves(false), 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 = CurvesToMotion(inactiveCurves);
|
|
||||||
var groupName = (items[0].controlGroup != null
|
|
||||||
? items[0].controlGroup.gameObject.name
|
|
||||||
: items[0].gameObject.name);
|
|
||||||
inactiveMotion.name =
|
inactiveMotion.name =
|
||||||
groupName
|
groupName
|
||||||
+ " (Inactive)";
|
+ " (Inactive)";
|
||||||
@ -403,20 +410,32 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
|
|
||||||
foreach (var item in items)
|
foreach (var item in items)
|
||||||
{
|
{
|
||||||
Dictionary<MenuCurveBinding, (Component, AnimationCurve)> activeCurves =
|
Dictionary<MenuCurveBinding, (Component, AnimationCurve)> activeCurves;
|
||||||
new Dictionary<MenuCurveBinding, (Component, AnimationCurve)>();
|
|
||||||
|
|
||||||
MergeCurves(activeCurves, item, a => a.GetCurves(), false);
|
Motion clip;
|
||||||
foreach (var kvp in inactiveCurves)
|
|
||||||
|
if (item == null)
|
||||||
{
|
{
|
||||||
if (!activeCurves.ContainsKey(kvp.Key))
|
activeCurves = inactiveCurves;
|
||||||
|
clip = inactiveMotion;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
activeCurves = new Dictionary<MenuCurveBinding, (Component, AnimationCurve)>();
|
||||||
|
|
||||||
|
MergeCurves(activeCurves, item, a => a.GetCurves(), false);
|
||||||
|
foreach (var kvp in inactiveCurves)
|
||||||
{
|
{
|
||||||
activeCurves.Add(kvp.Key, kvp.Value);
|
if (!activeCurves.ContainsKey(kvp.Key))
|
||||||
|
{
|
||||||
|
activeCurves.Add(kvp.Key, kvp.Value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
clip = CurvesToMotion(activeCurves);
|
||||||
|
clip.name = groupName + " (" + item.gameObject.name + ")";
|
||||||
}
|
}
|
||||||
|
|
||||||
var clip = CurvesToMotion(activeCurves);
|
|
||||||
clip.name = groupName + " (" + item.gameObject.name + ")";
|
|
||||||
motions.Add(clip);
|
motions.Add(clip);
|
||||||
|
|
||||||
foreach (var binding in activeCurves)
|
foreach (var binding in activeCurves)
|
||||||
@ -430,12 +449,12 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
|
|
||||||
foreach (var binding in newBindings)
|
foreach (var binding in newBindings)
|
||||||
{
|
{
|
||||||
if (bindings.ContainsKey(binding.Key))
|
if (bindings.TryGetValue(binding.Key, out var bindingValue))
|
||||||
{
|
{
|
||||||
BuildReport.LogFatal("animation_gen.duplicate_binding", new object[]
|
BuildReport.LogFatal("animation_gen.duplicate_binding", new object[]
|
||||||
{
|
{
|
||||||
binding.Key
|
binding.Key
|
||||||
}, binding.Value, bindings[binding.Key]);
|
}, binding.Value, bindingValue);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -94,7 +94,7 @@ namespace nadena.dev.modular_avatar.core.editor.menu
|
|||||||
|
|
||||||
BuildReport.ReportingObject(installer, () =>
|
BuildReport.ReportingObject(installer, () =>
|
||||||
{
|
{
|
||||||
var menuSourceComp = installer.GetComponent<MenuSourceComponent>();
|
var menuSourceComp = installer.GetComponent<MenuSource>();
|
||||||
if (menuSourceComp != null)
|
if (menuSourceComp != null)
|
||||||
{
|
{
|
||||||
PushNode(menuSourceComp);
|
PushNode(menuSourceComp);
|
||||||
|
@ -0,0 +1,17 @@
|
|||||||
|
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
|
||||||
|
{
|
||||||
|
internal abstract bool isSyncedProp { get; }
|
||||||
|
internal abstract bool isSavedProp { get; }
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: d26aaffe335843959445c6a976d517a7
|
||||||
|
timeCreated: 1681386441
|
@ -7,8 +7,8 @@ using Object = System.Object;
|
|||||||
|
|
||||||
namespace nadena.dev.modular_avatar.core
|
namespace nadena.dev.modular_avatar.core
|
||||||
{
|
{
|
||||||
[RequireComponent(typeof(ModularAvatarMenuItem))]
|
|
||||||
[AddComponentMenu("Modular Avatar/MA Action Toggle Object")]
|
[AddComponentMenu("Modular Avatar/MA Action Toggle Object")]
|
||||||
|
[RequireComponent(typeof(ActionController))]
|
||||||
public class ActionToggleObject : AvatarTagComponent, SwitchedMenuAction
|
public class ActionToggleObject : AvatarTagComponent, SwitchedMenuAction
|
||||||
{
|
{
|
||||||
[Serializable]
|
[Serializable]
|
||||||
|
@ -3,7 +3,14 @@
|
|||||||
namespace nadena.dev.modular_avatar.core
|
namespace nadena.dev.modular_avatar.core
|
||||||
{
|
{
|
||||||
[AddComponentMenu("Modular Avatar/MA Control Group")]
|
[AddComponentMenu("Modular Avatar/MA Control Group")]
|
||||||
public class ControlGroup : AvatarTagComponent
|
public class ControlGroup : ActionController
|
||||||
{
|
{
|
||||||
|
public bool isSynced = true;
|
||||||
|
public bool isSaved = true;
|
||||||
|
|
||||||
|
public ModularAvatarMenuItem defaultValue;
|
||||||
|
|
||||||
|
internal override bool isSyncedProp => isSynced;
|
||||||
|
internal override bool isSavedProp => isSaved;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -88,7 +88,7 @@ namespace nadena.dev.modular_avatar.core.menu
|
|||||||
{
|
{
|
||||||
foreach (Transform t in root.transform)
|
foreach (Transform t in root.transform)
|
||||||
{
|
{
|
||||||
var source = t.GetComponent<MenuSourceComponent>();
|
var source = t.GetComponent<MenuSource>();
|
||||||
if (source != null) context.PushNode(source);
|
if (source != null) context.PushNode(source);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,7 @@ namespace nadena.dev.modular_avatar.core
|
|||||||
}
|
}
|
||||||
|
|
||||||
[AddComponentMenu("Modular Avatar/MA Menu Item")]
|
[AddComponentMenu("Modular Avatar/MA Menu Item")]
|
||||||
public class ModularAvatarMenuItem : MenuSourceComponent
|
public class ModularAvatarMenuItem : ActionController, MenuSource
|
||||||
{
|
{
|
||||||
public VRCExpressionsMenu.Control Control;
|
public VRCExpressionsMenu.Control Control;
|
||||||
public SubmenuSource MenuSource;
|
public SubmenuSource MenuSource;
|
||||||
@ -22,19 +22,30 @@ namespace nadena.dev.modular_avatar.core
|
|||||||
public GameObject menuSource_otherObjectChildren;
|
public GameObject menuSource_otherObjectChildren;
|
||||||
|
|
||||||
[FormerlySerializedAs("toggleGroup")] public ControlGroup controlGroup;
|
[FormerlySerializedAs("toggleGroup")] public ControlGroup controlGroup;
|
||||||
public bool isDefault;
|
|
||||||
|
/// <summary>
|
||||||
|
/// If no control group is set (and an action is linked), this controls whether this control is synced.
|
||||||
|
/// </summary>
|
||||||
|
public bool isSynced = true;
|
||||||
|
|
||||||
|
public bool isSaved = true;
|
||||||
|
|
||||||
|
internal override bool isSyncedProp => isSynced;
|
||||||
|
internal override bool isSavedProp => isSaved;
|
||||||
|
|
||||||
protected override void OnValidate()
|
protected override void OnValidate()
|
||||||
{
|
{
|
||||||
base.OnValidate();
|
base.OnValidate();
|
||||||
|
|
||||||
|
RuntimeUtil.InvalidateMenu();
|
||||||
|
|
||||||
if (Control == null)
|
if (Control == null)
|
||||||
{
|
{
|
||||||
Control = new VRCExpressionsMenu.Control();
|
Control = new VRCExpressionsMenu.Control();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Visit(NodeContext context)
|
public void Visit(NodeContext context)
|
||||||
{
|
{
|
||||||
if (Control == null)
|
if (Control == null)
|
||||||
{
|
{
|
||||||
|
@ -62,6 +62,15 @@
|
|||||||
"dependencies": {},
|
"dependencies": {},
|
||||||
"url": "https://packages.unity.com"
|
"url": "https://packages.unity.com"
|
||||||
},
|
},
|
||||||
|
"com.unity.postprocessing": {
|
||||||
|
"version": "3.1.1",
|
||||||
|
"depth": 1,
|
||||||
|
"source": "registry",
|
||||||
|
"dependencies": {
|
||||||
|
"com.unity.modules.physics": "1.0.0"
|
||||||
|
},
|
||||||
|
"url": "https://packages.unity.com"
|
||||||
|
},
|
||||||
"com.unity.settings-manager": {
|
"com.unity.settings-manager": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"depth": 1,
|
"depth": 1,
|
||||||
@ -165,6 +174,20 @@
|
|||||||
"dependencies": {},
|
"dependencies": {},
|
||||||
"hash": "ba9c16e482e7a376db18e9a85e24fabc04649d37"
|
"hash": "ba9c16e482e7a376db18e9a85e24fabc04649d37"
|
||||||
},
|
},
|
||||||
|
"dev.onevr.vrworldtoolkit": {
|
||||||
|
"version": "file:dev.onevr.vrworldtoolkit",
|
||||||
|
"depth": 0,
|
||||||
|
"source": "embedded",
|
||||||
|
"dependencies": {
|
||||||
|
"com.unity.postprocessing": "3.1.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"lyuma.av3emulator": {
|
||||||
|
"version": "file:lyuma.av3emulator",
|
||||||
|
"depth": 0,
|
||||||
|
"source": "embedded",
|
||||||
|
"dependencies": {}
|
||||||
|
},
|
||||||
"nadena.dev.modular-avatar": {
|
"nadena.dev.modular-avatar": {
|
||||||
"version": "file:nadena.dev.modular-avatar",
|
"version": "file:nadena.dev.modular-avatar",
|
||||||
"depth": 0,
|
"depth": 0,
|
||||||
@ -173,6 +196,12 @@
|
|||||||
"com.unity.nuget.newtonsoft-json": "2.0.0"
|
"com.unity.nuget.newtonsoft-json": "2.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"vrchat.blackstartx.gesture-manager": {
|
||||||
|
"version": "file:vrchat.blackstartx.gesture-manager",
|
||||||
|
"depth": 0,
|
||||||
|
"source": "embedded",
|
||||||
|
"dependencies": {}
|
||||||
|
},
|
||||||
"com.unity.modules.ai": {
|
"com.unity.modules.ai": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"depth": 0,
|
"depth": 0,
|
||||||
|
@ -5,21 +5,30 @@
|
|||||||
},
|
},
|
||||||
"com.vrchat.core.vpm-resolver": {
|
"com.vrchat.core.vpm-resolver": {
|
||||||
"version": "0.1.13"
|
"version": "0.1.13"
|
||||||
|
},
|
||||||
|
"vrchat.blackstartx.gesture-manager": {
|
||||||
|
"version": "3.8.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"com.vrchat.avatars": {
|
"com.vrchat.avatars": {
|
||||||
"version": "3.1.11",
|
"version": "3.1.13",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"com.vrchat.base": "3.1.11"
|
"com.vrchat.base": "3.1.13"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"com.vrchat.base": {
|
"com.vrchat.base": {
|
||||||
"version": "3.1.11",
|
"version": "3.1.13",
|
||||||
"dependencies": {}
|
"dependencies": {}
|
||||||
},
|
},
|
||||||
"com.vrchat.core.vpm-resolver": {
|
"com.vrchat.core.vpm-resolver": {
|
||||||
"version": "0.1.17"
|
"version": "0.1.17"
|
||||||
|
},
|
||||||
|
"vrchat.blackstartx.gesture-manager": {
|
||||||
|
"version": "3.8.3",
|
||||||
|
"dependencies": {
|
||||||
|
"com.vrchat.avatars": "3.1.x"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -619,7 +619,19 @@ PlayerSettings:
|
|||||||
webGLThreadsSupport: 0
|
webGLThreadsSupport: 0
|
||||||
webGLWasmStreaming: 0
|
webGLWasmStreaming: 0
|
||||||
scriptingDefineSymbols:
|
scriptingDefineSymbols:
|
||||||
1: VRC_SDK_VRCSDK3
|
1: VRC_SDK_VRCSDK3;UNITY_POST_PROCESSING_STACK_V2
|
||||||
|
7: UNITY_POST_PROCESSING_STACK_V2
|
||||||
|
13: UNITY_POST_PROCESSING_STACK_V2
|
||||||
|
14: UNITY_POST_PROCESSING_STACK_V2
|
||||||
|
19: UNITY_POST_PROCESSING_STACK_V2
|
||||||
|
21: UNITY_POST_PROCESSING_STACK_V2
|
||||||
|
25: UNITY_POST_PROCESSING_STACK_V2
|
||||||
|
27: UNITY_POST_PROCESSING_STACK_V2
|
||||||
|
28: UNITY_POST_PROCESSING_STACK_V2
|
||||||
|
29: UNITY_POST_PROCESSING_STACK_V2
|
||||||
|
30: UNITY_POST_PROCESSING_STACK_V2
|
||||||
|
32: UNITY_POST_PROCESSING_STACK_V2
|
||||||
|
33: UNITY_POST_PROCESSING_STACK_V2
|
||||||
platformArchitecture: {}
|
platformArchitecture: {}
|
||||||
scriptingBackend: {}
|
scriptingBackend: {}
|
||||||
il2cppCompilerConfiguration: {}
|
il2cppCompilerConfiguration: {}
|
||||||
|
@ -10,7 +10,25 @@ When you want to create an option that can be in one of several states - for exa
|
|||||||
|
|
||||||
## How do I use it?
|
## How do I use it?
|
||||||
|
|
||||||
The control group component itself has no configuration; simply add it to a Game Object, and point your [Menu Item](menu-item.md) components at the Control Group.
|
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. Feel free to put it somewhere convenient for you.
|
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.
|
||||||
|
|
||||||
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.
|
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.
|
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 99 KiB |
18
docs/docs/reference/menu-install-target.md
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
# Menu Install Target
|
||||||
|
|
||||||
|
The Menu Install Target component is a component used to support the "Select Menu" button on the [MA Menu Installer](menu-installer) component.
|
||||||
|
It "pulls" the menu from the MA Menu Installer component, and installs it based on the position of the game object it is
|
||||||
|
attached to.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## When should I use it?
|
||||||
|
|
||||||
|
Modular Avatar will create this component when necessary, when you use the "select menu" button on the
|
||||||
|
[MA Menu Installer](menu-installer) component. In most cases it is not necessary to create it manually.
|
||||||
|
|
||||||
|
## What does it do?
|
||||||
|
|
||||||
|
This component will override the target menu option on the menu installer that is selected; the menu installer will
|
||||||
|
instead act as if its menu had been copy-pasted to the location of the Menu Install Target. This allows for prefabs that
|
||||||
|
use Menu Installers to be integrated into the [object-based menu system](../tutorials/menu).
|
BIN
docs/docs/reference/menu-install-target.png
Normal file
After Width: | Height: | Size: 34 KiB |
Before Width: | Height: | Size: 154 KiB After Width: | Height: | Size: 52 KiB |
@ -70,25 +70,33 @@ In some cases you might want more complex toggles. This can be done by adding a
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
Here we have a clothing menu which has a control group on it. The control group has no settings on it; it's just there to tie together the menu items. Here's what those look like:
|
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:
|
||||||
|
|
||||||

|

|
||||||

|

|
||||||

|

|
||||||
|
|
||||||
As you can see, each of these menu items has a "Toggle" type, and a "MA Action Toggle Object" component. The "MA Action Toggle Object" component has a checkbox next to each object, which indicates whether that object should be shown when the menu item is selected.
|
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", and on the default object, we selected "Is Group Default".
|
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.
|
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.
|
||||||
|
|
||||||
You can select one item out of your control group to be the default item. When you do this, the default item will be selected initially, and anything that it selects will be reversed when you select a different item. In this example, because we selected the "Kikyo_Blouse", "Kikyo_Coat", and "Kikyo_Skirt" objects ON in the default item, these will be toggled off when we select Blanchir or SailorOnepiece. You can override this on the individual other items by adding that item in explicitly.
|
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
|
### Limitations
|
||||||
|
|
||||||
This feature is in preview and has a number of limitations you should be aware of:
|
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.
|
1. Objects can only be controlled by one control group or ungrouped toggle at this time.
|
||||||
2. Currently, these toggles will not be saved. This means that if you change avatars or move between worlds, your toggles will not be saved.
|
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.
|
3. A toggle cannot both control actions and traditional animators at the same time.
|
||||||
|
|
||||||
These limitations will be improved on in future releases.
|
These limitations will be improved on in future releases.
|
||||||
|
Before Width: | Height: | Size: 78 KiB After Width: | Height: | Size: 80 KiB |
@ -72,24 +72,33 @@ Cubeの隣のチェックは、トグルがONになったときは表示す
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
こちらはControl Groupがついている衣装切り替えメニューです。Control group自体には設定項目がなく、メニューアイテムを一括りにするためだけにあります。各メニューアイテムも見てみましょう。
|
こちらはControl Groupがついている衣装切り替えメニューです。Control groupの主な仕事はメニューアイテムを一括りにして、同時に一つまでしか選択できないようにするためにあります。各メニューアイテムも見てみましょう。
|
||||||
|
|
||||||

|

|
||||||

|

|
||||||

|

|
||||||
|
|
||||||
見ての通り、どれも「Toggle」タイプで、「MA Action Toggle Object」コンポーネントがついています。違うのは、Control Groupのオブジェクトを「コントロールグループ」に指定し、デフォルトの衣装に「グループの初期設定にする」をチェックしています。
|
見ての通り、どれも「Toggle」タイプで、「MA Action Toggle Object」コンポーネントがついています。
|
||||||
|
違うのは、Control Groupのオブジェクトを「コントロールグループ」に指定しているところです。
|
||||||
コントロールグループを指定すると、その中から一つのメニューアイテムしか設定できないようになります。同じパラメーターで連動するというわけです。
|
コントロールグループを指定すると、その中から一つのメニューアイテムしか設定できないようになります。同じパラメーターで連動するというわけです。
|
||||||
|
|
||||||
コントロールグループに連動するアイテムのうちから一つを初期設定にできます。すると、そのトグルが最初から設定される状態になり、そのトグルで表示されるものが他のトグルでは非表示になります(その逆もしかり)。この例の場合、「Kikyo_Blouse」、「Kikyo_Coat」、「Kikyo_Skirt」を初期項目でONにしたので、BlanchirとSailorOnepieceでは無効かされます。この挙動が不要の場合は、ほかの項目に該当オブジェクトを追加し、手動で設定できます。
|
翻訳:コントロールグループは、同じゲームオブジェクトにMA Menu Itemがない限り、どこにでも置けます。ヒエラルキーのどこにでも置いても構いません。
|
||||||
|
自分にとってわかりやすい所に置きましょう。
|
||||||
|
|
||||||
|
また、コントロールグループにもAction Toggle Objectを追加しました。こちらのトグルはデフォルトで設定されます。これでほかの衣装を選択しているとき、
|
||||||
|
桔梗ちゃんのデフォルト衣装を簡単に切ることができます。
|
||||||
|
|
||||||
|
コントロールグループに連動するアイテムのうちから一つを初期設定にできます。設定しない場合は、どれも選択しないという状態がデフォルトになります。
|
||||||
|
何も選択しない状態では、コントロールグループのデフォルトが適用されます。
|
||||||
|
|
||||||
|
最後に、コントロールグループのほうに保存・同期設定を調整できます。
|
||||||
|
|
||||||
### 制限
|
### 制限
|
||||||
|
|
||||||
この機能は開発途中のもので、いくつか制限があります。
|
この機能は開発途中のもので、いくつか制限があります。
|
||||||
|
|
||||||
1. 一つのオブジェクトは一つのコントロールグループまたはグループに入っていないトグルにしか操作できない。
|
1. 一つのオブジェクトは一つのコントロールグループまたはグループに入っていないトグルにしか操作できない。
|
||||||
2. 現在、トグルの状態が保存されません。アバター変更・ワールド移動で状態が保持されないということです。
|
2. 現在、GameObjectしかトグルできません。コンポーネント単位のON/OFFやブレンドシェープの操作は未実装です。
|
||||||
3. 一つのトグルでアクションと通常のアニメーターを両方操作できません。
|
3. 一つのトグルでアクションと通常のアニメーターを両方操作できません。
|
||||||
|
|
||||||
今後のリリースで改善していく予定です。
|
今後のリリースで改善していく予定です。
|
||||||
|