private void AddParameter() { for (int i = 0; i < expressionParams.parameters.Length; i++) { if (expressionParams.parameters[i].name == "ToggleMarker" || expressionParams.parameters[i].name.Trim().Length == 0) { expressionParams.parameters[i].name = "ToggleMarker"; expressionParams.parameters[i].valueType = VRCExpressionParameters.ValueType.Bool; EditorUtility.SetDirty(expressionParams); return; } } int size = GetParameterSize(); if (size < 128) { int n = expressionParams.parameters.Length; VRCExpressionParameters.Parameter[] newParams = new VRCExpressionParameters.Parameter[n + 1]; for (int i = 0; i < n; i++) { newParams[i] = expressionParams.parameters[i]; } newParams[n] = new VRCExpressionParameters.Parameter(); newParams[n].name = "ToggleMarker"; newParams[n].valueType = VRCExpressionParameters.ValueType.Bool; expressionParams.parameters = newParams; EditorUtility.SetDirty(expressionParams); return; } Debug.LogError("Could not create Avatar 3.0 parameter. You may be out of paramters, try creating it manually."); return; }
private static int GetParamIndex(string paramName) { VRCExpressionParameters.Parameter[] parameters = new VRCExpressionParameters.Parameter[0]; if (VRCPlayer .field_Internal_Static_VRCPlayer_0?.prop_VRCAvatarManager_0?.prop_VRCAvatarDescriptor_0?.expressionParameters?.parameters != null) { parameters = VRCPlayer .field_Internal_Static_VRCPlayer_0 .prop_VRCAvatarManager_0.prop_VRCAvatarDescriptor_0.expressionParameters .parameters; } else { return(-1); } var index = -1; for (var i = 0; i < parameters.Length; i++) { VRCExpressionParameters.Parameter param = parameters[i]; if (param.name == null) { return(-1); } if (param.name == paramName) { index = i; } } return(index); }
public static bool GetBoolParameter(string paramName) { if (AnimatorParameters != null && !string.IsNullOrEmpty(paramName)) { foreach (var animatorParameter in AnimatorParameters) { VRCExpressionParameters.Parameter expressionParameter = animatorParameter.field_Public_Parameter_0; if (expressionParameter.name == paramName && expressionParameter.valueType == VRCExpressionParameters.ValueType.Bool) { return(animatorParameter?.field_Public_AvatarParameter_0?.prop_Boolean_0 ?? false); } } } return(false); }
public static int GetIntParameter(string paramName) { if (AnimatorParameters != null && !string.IsNullOrEmpty(paramName)) { foreach (var animatorParameter in AnimatorParameters) { VRCExpressionParameters.Parameter expressionParameter = animatorParameter.field_Public_Parameter_0; if (expressionParameter.name == paramName && expressionParameter.valueType == VRCExpressionParameters.ValueType.Int) { return(animatorParameter?.field_Public_AvatarParameter_0?.prop_Int32_0 ?? 0); } } } return(0); }
public static bool AddBitKeys(VRCExpressionParameters parameters) { List <VRCExpressionParameters.Parameter> paramList = parameters.parameters.ToList(); for (int i = 0; i < KeyCount; ++i) { string bitKeyName = $"BitKey{i}"; int index = Array.FindIndex(parameters.parameters, p => p.name == bitKeyName); if (index != -1) { Debug.Log($"Found BitKey in params {bitKeyName}"); parameters.parameters[index].saved = true; parameters.parameters[index].defaultValue = 0; parameters.parameters[index].valueType = VRCExpressionParameters.ValueType.Bool; } else { Debug.Log($"Adding BitKey in params {bitKeyName}"); var newParam = new VRCExpressionParameters.Parameter { name = bitKeyName, saved = true, defaultValue = 0, valueType = VRCExpressionParameters.ValueType.Bool }; paramList.Add(newParam); } } parameters.parameters = paramList.ToArray(); int remainingCost = VRCExpressionParameters.MAX_PARAMETER_COST - parameters.CalcTotalCost();; Debug.Log(remainingCost); if (remainingCost < 0) { Debug.LogError("Adding BitKeys took up too many parameters!"); EditorUtility.DisplayDialog("Adding BitKeys took up too many parameters!", "Go to your VRCExpressionParameters and remove some unnecessary parameters to make room for the 32 BitKey bools and run this again.", "Okay"); return(false); } EditorUtility.SetDirty(parameters); return(true); }
private ParameterValueType GetParameterType(VRCExpressionParameters.Parameter parameter) { switch (parameter.valueType) { case VRCExpressionParameters.ValueType.Float: return(ParameterValueType.Float); case VRCExpressionParameters.ValueType.Int: return(ParameterValueType.Int); case VRCExpressionParameters.ValueType.Bool: return(ParameterValueType.Bool); default: throw new ArgumentOutOfRangeException(); } }
public override void Build(MenuActions.MenuAction parent) { var controller = GetController(AnimationLayer.FX); //Define volume param { var param = new VRCExpressionParameters.Parameter(); param.name = parameter; param.valueType = VRCExpressionParameters.ValueType.Float; param.defaultValue = 0; param.saved = false; DefineExpressionParameter(param); } //Define parameters on controller AddParameter(controller, "Viseme", AnimatorControllerParameterType.Int, 0); AddParameter(controller, parameter, AnimatorControllerParameterType.Float, 0); BuildDriverLayer(); BuildAnimationLayer(); }
private static void ParameterFixes(VRCExpressionParameters.Parameter param, VRCExpressionParameters expressionParameters, JustHaiToggleGroup @group) { if (param == null) { EditorGUILayout.HelpBox("Expression Parameter does not exist", MessageType.Error); if (GUILayout.Button("Fix: Create Expression Parameter")) { var newParameters = new VRCExpressionParameters.Parameter[expressionParameters.parameters.Length + 1]; expressionParameters.parameters.CopyTo(newParameters, 0); expressionParameters.parameters = newParameters; expressionParameters.parameters[expressionParameters.parameters.Length - 1] = new VRCExpressionParameters.Parameter() { name = @group.parameterName, defaultValue = @group.hintEnabled ? 1f : 0f, saved = true, valueType = VRCExpressionParameters.ValueType.Bool }; ForceSaveAsset(expressionParameters); } } else if (param.valueType != VRCExpressionParameters.ValueType.Bool) { EditorGUILayout.HelpBox($"Expression Parameter exists but it is not a Bool (currently: {param.valueType})", MessageType.Error); if (GUILayout.Button("Fix: Change Expression Parameter Type to Bool")) { param.valueType = VRCExpressionParameters.ValueType.Bool; param.defaultValue = @group.hintEnabled ? 1f : 0f; ForceSaveAsset(expressionParameters); } } else if (param.defaultValue != (@group.hintEnabled ? 1f : 0f)) { EditorGUILayout.HelpBox($"Expression Parameter default value is not the same", MessageType.Warning); if (GUILayout.Button("Fix: Change Expression Parameter Default Value")) { param.defaultValue = @group.hintEnabled ? 1f : 0f; ForceSaveAsset(expressionParameters); } } }
public static VRCExpressionParameters.Parameter AddVRCExpressionsParameter(VRCAvatarDescriptor avatarDescriptor, VRCExpressionParameters.ValueType type, string name, List <Object> dirtyAssets) { VRCExpressionParameters parameters = avatarDescriptor.expressionParameters; VRCExpressionParameters.Parameter parameter = parameters.FindParameter(name); if (parameter == null) { var list = parameters.parameters.Where(e => e != null && !string.IsNullOrEmpty(e.name)).ToList(); parameter = new VRCExpressionParameters.Parameter { name = name, valueType = type, }; list.Add(parameter); parameters.parameters = list.ToArray(); } dirtyAssets.Add(parameters); dirtyAssets.Add(avatarDescriptor.gameObject); return(parameter); }
public static bool Add(this VRCExpressionParameters parameters, VRCExpressionParameters.Parameter parameter) { var emptyFirstIndex = 0; for (var i = 0; i < parameters.parameters.Length; i++) { if (!string.IsNullOrEmpty(parameters.parameters[i].name)) { continue; } emptyFirstIndex = i; break; } if (emptyFirstIndex == parameters.parameters.Length) { return(false); } parameters.parameters[emptyFirstIndex] = parameter; return(true); }
public static void AddParameter(this VRCExpressionParameters source, VRCExpressionParameters.Parameter parameter) { var so = new SerializedObject(source); so.Update(); var sourceParameters = so.FindProperty(nameof(source.parameters)); sourceParameters.arraySize += 1; var idx = sourceParameters.arraySize - 1; sourceParameters.InsertArrayElementAtIndex(idx); var obj = sourceParameters.GetArrayElementAtIndex(idx); obj.FindPropertyRelative("name").stringValue = parameter.name; obj.FindPropertyRelative("valueType").intValue = (int)parameter.valueType; obj.FindPropertyRelative("defaultValue").floatValue = parameter.defaultValue; obj.FindPropertyRelative("saved").boolValue = parameter.saved; so.ApplyModifiedProperties(); }
public static Vrc3Param CreateParamFromNothing(VRCExpressionParameters.Parameter parameter) { return(new Vrc3ParamExternal(parameter.name, ModuleVrc3Styles.Data.TypeOf[parameter.valueType])); }
public VrcParameterDefinition Create(AvatarDefinition parent, VRCExpressionParameters.Parameter parameter) { return(Create(parent, parameter.name, GetParameterType(parameter))); }
/// <summary> /// Creates a copy of the avatar descriptor's parameter asset or creates one if it doesn't exist, adds a provided parameter, /// assigns the new parameter asset, and stores it in the specified directory. /// </summary> /// <param name="descriptor">The avatar descriptor to add the parameter to.</param> /// <param name="parameter">The parameter to add.</param> /// <param name="directory">The unique directory to store the new parameter asset, ex. "Assets/MyCoolScript/GeneratedAssets/725638/".</param> /// <param name="overwrite">Optionally, choose to not overwrite an asset of the same name in directory. See class for more info.</param> public static void AddParameter(VRCAvatarDescriptor descriptor, VRCExpressionParameters.Parameter parameter, string directory, bool overwrite = true) { if (descriptor == null) { Debug.LogError("Couldn't add the parameter, the avatar descriptor is null!"); return; } else if ((parameter == null) || (parameter.name == null)) { Debug.LogError("Couldn't add the parameter, it or its name is null!"); return; } else if ((directory == null) || (directory == "")) { Debug.Log("Directory was not specified, storing new parameters asset in " + defaultDirectory); directory = defaultDirectory; } descriptor.customExpressions = true; VRCExpressionParameters parameters = ScriptableObject.CreateInstance <VRCExpressionParameters>(); parameters.parameters = new VRCExpressionParameters.Parameter[0]; if (descriptor.expressionParameters == null) { AssetDatabase.CreateAsset(parameters, directory + "Parameters.asset"); } else { if ((descriptor.expressionParameters.CalcTotalCost() + VRCExpressionParameters.TypeCost(parameter.valueType)) > VRCExpressionParameters.MAX_PARAMETER_COST) { Debug.LogError("Couldn't add parameter '" + parameter.name + "', not enough memory free in the avatar's parameter asset!"); return; } string path = (directory + descriptor.expressionParameters.name + ".asset"); path = (overwrite) ? path : AssetDatabase.GenerateUniqueAssetPath(path); if (AssetDatabase.GetAssetPath(descriptor.expressionParameters) != path) // if we have not made a copy yet { // CopyAsset with two identical strings yields exception AssetDatabase.CopyAsset(AssetDatabase.GetAssetPath(descriptor.expressionParameters), path); } parameters = AssetDatabase.LoadAssetAtPath(path, typeof(VRCExpressionParameters)) as VRCExpressionParameters; } if (parameters.FindParameter(parameter.name) == null) { int count = parameters.parameters.Length; VRCExpressionParameters.Parameter[] parameterArray = new VRCExpressionParameters.Parameter[count + 1]; for (int i = 0; i < count; i++) { parameterArray[i] = parameters.GetParameter(i); } parameterArray[count] = parameter; parameters.parameters = parameterArray; } EditorUtility.SetDirty(parameters); AssetDatabase.SaveAssets(); AssetDatabase.Refresh(); descriptor.expressionParameters = parameters; }
public void Generate() { // Unique directory setup, named after avatar Directory.CreateDirectory("Assets/VRLabs/GeneratedAssets/Marker/"); AssetDatabase.Refresh(); // Folder name cannot contain these chars string cleanedName = string.Join("", descriptor.name.Split('/', '?', '<', '>', '\\', ':', '*', '|', '\"')); string guid = AssetDatabase.CreateFolder("Assets/VRLabs/GeneratedAssets/Marker", cleanedName); string directory = AssetDatabase.GUIDToAssetPath(guid) + "/"; // Install layers, parameters, and menu before prefab setup // FX layer if (useIndexFinger) { AssetDatabase.CopyAsset("Assets/VRLabs/Marker/Resources/M_FX (Finger).controller", directory + "FXtemp.controller"); } else { AssetDatabase.CopyAsset("Assets/VRLabs/Marker/Resources/M_FX.controller", directory + "FXtemp.controller"); } AnimatorController FX = AssetDatabase.LoadAssetAtPath(directory + "FXtemp.controller", typeof(AnimatorController)) as AnimatorController; // remove controller layers before merging to avatar, corresponding to setup if (leftHanded) { RemoveLayer(FX, "M_Marker R"); } else { RemoveLayer(FX, "M_Marker L"); } if (!brushSize) { RemoveLayer(FX, "M_Size"); RemoveParameter(FX, "M_Size"); } if (!eraserSize) { RemoveLayer(FX, "M_EraserSize"); RemoveParameter(FX, "M_EraserSize"); } if (!localSpace) { RemoveLayer(FX, "M_Space"); RemoveParameter(FX, "M_Space"); RemoveLayer(FX, "M_Cull"); } else { RemoveLayer(FX, "M_SpaceSimple"); RemoveParameter(FX, "M_SpaceSimple"); RemoveLayer(FX, "M_CullSimple"); } if (writeDefaults) { AV3ManagerFunctions.SetWriteDefaults(FX); } if (gestureToDraw != 3) // uses fingerpoint by default { ChangeGestureCondition(FX, 0, gestureToDraw); } // Set parameter driver on 'Clear' state to reset local space AnimatorState state = FX.layers[0].stateMachine.states.FirstOrDefault(s => s.state.name.Equals("Clear")).state; VRCAvatarParameterDriver driver = (VRCAvatarParameterDriver)state.behaviours[0]; string driverParamName = localSpace ? "M_Space" : "M_SpaceSimple"; VRC.SDKBase.VRC_AvatarParameterDriver.Parameter param = new VRC.SDKBase.VRC_AvatarParameterDriver.Parameter() { name = driverParamName, type = VRC.SDKBase.VRC_AvatarParameterDriver.ChangeType.Set, value = 0f }; driver.parameters.Add(param); EditorUtility.SetDirty(FX); AV3ManagerFunctions.MergeToLayer(descriptor, FX, AV3ManagerFunctions.PlayableLayer.FX, directory); AssetDatabase.DeleteAsset(AssetDatabase.GetAssetPath(FX)); // delete temporary FX layer // Gesture layer AssetDatabase.CopyAsset("Assets/VRLabs/Marker/Resources/M_Gesture.controller", directory + "gestureTemp.controller"); // to modify AnimatorController gesture = AssetDatabase.LoadAssetAtPath(directory + "gestureTemp.controller", typeof(AnimatorController)) as AnimatorController; if (descriptor.baseAnimationLayers[2].isDefault == true || descriptor.baseAnimationLayers[2].animatorController == null) { AssetDatabase.CopyAsset(path_defaultGesture, directory + "Gesture.controller"); AnimatorController gestureOriginal = AssetDatabase.LoadAssetAtPath(directory + "Gesture.controller", typeof(AnimatorController)) as AnimatorController; descriptor.customExpressions = true; descriptor.baseAnimationLayers[2].isDefault = false; descriptor.baseAnimationLayers[2].animatorController = gestureOriginal; if (writeDefaults) { AV3ManagerFunctions.SetWriteDefaults(gestureOriginal); EditorUtility.SetDirty(gestureOriginal); } } gesture.RemoveLayer((leftHanded) ? 1 : 0); if (useIndexFinger) { // use different hand animations for (int i = 0; i < 3; i++) { if (gesture.layers[0].stateMachine.states[i].state.motion.name == "M_Gesture") { gesture.layers[0].stateMachine.states[i].state.motion = AssetDatabase.LoadAssetAtPath("Assets/VRLabs/Marker/Resources/Animations/Gesture/M_Gesture (Finger).anim", typeof(AnimationClip)) as AnimationClip; } else if (gesture.layers[0].stateMachine.states[i].state.motion.name == "M_Gesture Draw") { gesture.layers[0].stateMachine.states[i].state.motion = AssetDatabase.LoadAssetAtPath("Assets/VRLabs/Marker/Resources/Animations/Gesture/M_Gesture Draw (Finger).anim", typeof(AnimationClip)) as AnimationClip; } } } if (gestureToDraw != 3) { ChangeGestureCondition(gesture, 0, gestureToDraw); } if (writeDefaults) { AV3ManagerFunctions.SetWriteDefaults(gesture, true); } EditorUtility.SetDirty(gesture); AV3ManagerFunctions.MergeToLayer(descriptor, gesture, AV3ManagerFunctions.PlayableLayer.Gesture, directory); AssetDatabase.DeleteAsset(AssetDatabase.GetAssetPath(gesture)); // delete temporary gesture layer // layer weight control from merged layer may need index set correctly AnimatorController avatarGesture = (AnimatorController)descriptor.baseAnimationLayers[2].animatorController; for (int i = 0; i < avatarGesture.layers.Length; i++) { // the controls' layer is normally 3 (AllParts, LeftHand, RightHand, >>>M_Gesture<<<) if (avatarGesture.layers[i].name.Contains("M_Gesture") && (i != 3)) { for (int j = 0; j < 3; j++) { if (avatarGesture.layers[i].stateMachine.states[j].state.behaviours.Length != 0) { VRCAnimatorLayerControl ctrl = (VRCAnimatorLayerControl)avatarGesture.layers[i].stateMachine.states[j].state.behaviours[0]; ctrl.layer = i; } } } } EditorUtility.SetDirty(avatarGesture); // Parameters VRCExpressionParameters.Parameter p_marker = new VRCExpressionParameters.Parameter { name = "M_Marker", valueType = VRCExpressionParameters.ValueType.Bool, saved = false }, p_eraser = new VRCExpressionParameters.Parameter { name = "M_Eraser", valueType = VRCExpressionParameters.ValueType.Bool, saved = false }, p_clear = new VRCExpressionParameters.Parameter { name = "M_Clear", valueType = VRCExpressionParameters.ValueType.Bool, saved = false }, p_color = new VRCExpressionParameters.Parameter { name = "M_Color", valueType = VRCExpressionParameters.ValueType.Float, saved = true }; AV3ManagerFunctions.AddParameter(descriptor, p_marker, directory); AV3ManagerFunctions.AddParameter(descriptor, p_eraser, directory); AV3ManagerFunctions.AddParameter(descriptor, p_clear, directory); AV3ManagerFunctions.AddParameter(descriptor, p_color, directory); if (localSpace) { VRCExpressionParameters.Parameter p_space = new VRCExpressionParameters.Parameter { name = "M_Space", valueType = VRCExpressionParameters.ValueType.Int, saved = false }; AV3ManagerFunctions.AddParameter(descriptor, p_space, directory); } else { VRCExpressionParameters.Parameter p_spaceSimple = new VRCExpressionParameters.Parameter { name = "M_SpaceSimple", valueType = VRCExpressionParameters.ValueType.Bool, saved = false }; AV3ManagerFunctions.AddParameter(descriptor, p_spaceSimple, directory); } if (brushSize) { VRCExpressionParameters.Parameter p_size = new VRCExpressionParameters.Parameter { name = "M_Size", valueType = VRCExpressionParameters.ValueType.Float, saved = false }; AV3ManagerFunctions.AddParameter(descriptor, p_size, directory); } if (eraserSize) { VRCExpressionParameters.Parameter p_eraserSize = new VRCExpressionParameters.Parameter { name = "M_EraserSize", valueType = VRCExpressionParameters.ValueType.Float, saved = false }; AV3ManagerFunctions.AddParameter(descriptor, p_eraserSize, directory); } VRCExpressionParameters.Parameter p_menu = new VRCExpressionParameters.Parameter { name = "M_Menu", valueType = VRCExpressionParameters.ValueType.Bool, saved = false }; AV3ManagerFunctions.AddParameter(descriptor, p_menu, directory); // handle menu instancing AssetDatabase.CopyAsset("Assets/VRLabs/Marker/Resources/M_Menu.asset", directory + "Marker Menu.asset"); VRCExpressionsMenu markerMenu = AssetDatabase.LoadAssetAtPath(directory + "Marker Menu.asset", typeof(VRCExpressionsMenu)) as VRCExpressionsMenu; if (!localSpace) // change from submenu to 1 toggle { VRCExpressionsMenu.Control.Parameter pm_spaceSimple = new VRCExpressionsMenu.Control.Parameter { name = "M_SpaceSimple" }; markerMenu.controls[6].type = VRCExpressionsMenu.Control.ControlType.Toggle; markerMenu.controls[6].parameter = pm_spaceSimple; markerMenu.controls[6].subMenu = null; // or else the submenu is still there internally, SDK complains } else { AssetDatabase.CopyAsset("Assets/VRLabs/Marker/Resources/M_Menu Space.asset", directory + "Marker Space Submenu.asset"); VRCExpressionsMenu subMenu = AssetDatabase.LoadAssetAtPath(directory + "Marker Space Submenu.asset", typeof(VRCExpressionsMenu)) as VRCExpressionsMenu; if (localSpaceFullBody == 0) // remove left and right foot controls { subMenu.controls.RemoveAt(7); subMenu.controls.RemoveAt(6); } markerMenu.controls[6].subMenu = subMenu; EditorUtility.SetDirty(subMenu); } if (!brushSize) { RemoveMenuControl(markerMenu, "Brush Size"); } if (!eraserSize) { RemoveMenuControl(markerMenu, "Eraser Size"); } EditorUtility.SetDirty(markerMenu); VRCExpressionsMenu.Control.Parameter pm_menu = new VRCExpressionsMenu.Control.Parameter { name = "M_Menu" }; Texture2D markerIcon = AssetDatabase.LoadAssetAtPath("Assets/VRLabs/Marker/Resources/Icons/M_Icon_Menu.png", typeof(Texture2D)) as Texture2D; AV3ManagerFunctions.AddSubMenu(descriptor, markerMenu, "Marker", directory, pm_menu, markerIcon); // setup in scene GameObject marker = PrefabUtility.InstantiatePrefab(AssetDatabase.LoadAssetAtPath("Assets/VRLabs/Marker/Resources/Marker.prefab", typeof(GameObject))) as GameObject; if (PrefabUtility.IsPartOfPrefabInstance(marker)) { PrefabUtility.UnpackPrefabInstance(marker, PrefabUnpackMode.Completely, InteractionMode.AutomatedAction); } marker.transform.SetParent(avatar.transform, false); Transform system = marker.transform.Find("System"); Transform targets = marker.transform.Find("Targets"); Transform markerTarget = targets.Find("MarkerTarget"); Transform markerModel = targets.Find("Model"); Transform eraser = system.Find("Eraser"); Transform local = marker.transform.Find("World").Find("Local"); // constrain cull object to avatar Transform cull = marker.transform.Find("Cull"); cull.GetComponent <ParentConstraint>().SetSource(0, new ConstraintSource { sourceTransform = descriptor.transform, weight = 1f }); if (useIndexFinger) { DestroyImmediate(markerTarget.GetChild(0).gameObject); // destroy Flip Transform indexDistal = leftHanded ? avatar.GetBoneTransform(HumanBodyBones.LeftIndexDistal) : avatar.GetBoneTransform(HumanBodyBones.RightIndexDistal); // prefer the end bone of the index finger if it exists if (indexDistal.Find(indexDistal.gameObject.name + "_end") != null) { markerTarget.SetParent(indexDistal.Find(indexDistal.gameObject.name + "_end"), true); } else { markerTarget.SetParent(indexDistal, false); } } else // using model: scale Model to target freely, and until script is destroyed, scale System to target uniformly with X-axis { markerModel.SetParent(marker.transform); // move it out of Targets hierarchy if (leftHanded) { markerTarget.SetParent(avatar.GetBoneTransform(HumanBodyBones.LeftHand), true); } else { markerTarget.SetParent(avatar.GetBoneTransform(HumanBodyBones.RightHand), true); } ((Marker)target).markerModel = markerModel; // to turn its mesh renderer off when script is finished } markerTarget.localPosition = Vector3.zero; markerTarget.localRotation = Quaternion.Euler(0f, 0f, 0f); HumanBodyBones[] bones = { HumanBodyBones.Hips, HumanBodyBones.Chest, HumanBodyBones.Head, HumanBodyBones.LeftHand, HumanBodyBones.RightHand, HumanBodyBones.LeftFoot, HumanBodyBones.RightFoot }; ParentConstraint localConstraint = local.GetComponent <ParentConstraint>(); localConstraint.SetSource(0, new ConstraintSource { sourceTransform = avatar.transform, weight = 1f }); if (localSpace) { for (int i = 0; i < 5; i++) { localConstraint.SetSource(i + 1, new ConstraintSource { sourceTransform = avatar.GetBoneTransform(bones[i]), weight = 0f }); } if (localSpaceFullBody == 1) { for (int i = 5; i < 7; i++) { localConstraint.SetSource(i + 1, new ConstraintSource { sourceTransform = avatar.GetBoneTransform(bones[i]), weight = 0f }); } } } DestroyImmediate(targets.gameObject); // remove the "Targets" container object when finished // set anything not adjustable to a medium-ish amount if (!eraserSize) { eraser.transform.localScale = new Vector3(0.05f, 0.05f, 0.05f); } if (!brushSize) { ParticleSystem.MinMaxCurve size = new ParticleSystem.MinMaxCurve(0.024f); Transform draw = system.transform.Find("Draw"); Transform preview = draw.GetChild(0); ParticleSystem.MainModule main = draw.GetComponent <ParticleSystem>().main; main.startSize = size; main = preview.GetComponent <ParticleSystem>().main; main.startSize = size; } // scale MarkerTarget, which controls prefab size, according to a (normalized) worldspace distance between avatar hips and head Transform hips = avatar.GetBoneTransform(HumanBodyBones.Hips); Transform head = avatar.GetBoneTransform(HumanBodyBones.Head); Vector3 dist = (head.position - hips.position); float normalizedDist = (Math.Max(Math.Max(dist.x, dist.y), dist.z) / KSIVL_UNIT); float newScale = markerTarget.localScale.x * normalizedDist; markerTarget.localScale = new Vector3(newScale, newScale, newScale); ((Marker)target).system = system; ((Marker)target).markerTarget = markerTarget; ((Marker)target).finished = true; AssetDatabase.SaveAssets(); AssetDatabase.Refresh(); Debug.Log("Successfully generated Marker!"); }
public VrcParameterDefinition AddParameter(VRCExpressionParameters.Parameter parameter) { return(Children.AddChild(ParameterFactory.Create(this, parameter))); }
void DrawParameterDropDown(SerializedProperty parameter, string name, bool allowBool = true) { var parameterName = parameter.FindPropertyRelative("name"); VRCExpressionParameters.Parameter param = null; string value = parameterName.stringValue; bool parameterFound = false; EditorGUILayout.BeginHorizontal(); { if (activeDescriptor != null) { //Dropdown int currentIndex; if (string.IsNullOrEmpty(value)) { currentIndex = -1; parameterFound = true; } else { currentIndex = -2; for (int i = 0; i < GetExpressionParametersCount(); i++) { var item = activeDescriptor.GetExpressionParameter(i); if (item.name == value) { param = item; parameterFound = true; currentIndex = i; break; } } } //Dropdown EditorGUI.BeginChangeCheck(); currentIndex = EditorGUILayout.Popup(name, currentIndex + 1, parameterNames); if (EditorGUI.EndChangeCheck()) { if (currentIndex == 0) { parameterName.stringValue = ""; } else { parameterName.stringValue = GetExpressionParameter(currentIndex - 1).name; } } } else { EditorGUI.BeginDisabledGroup(true); EditorGUILayout.Popup(0, new string[0]); EditorGUI.EndDisabledGroup(); } //Text field parameterName.stringValue = EditorGUILayout.TextField(parameterName.stringValue, GUILayout.MaxWidth(200)); } EditorGUILayout.EndHorizontal(); if (!parameterFound) { EditorGUILayout.HelpBox("Parameter not found on the active avatar descriptor.", MessageType.Warning); } if (!allowBool && param != null && param.valueType == ExpressionParameters.ValueType.Bool) { EditorGUILayout.HelpBox("Bool parameters not valid for this choice.", MessageType.Error); } }
public override void ValidateFeatures(VRC_AvatarDescriptor avatar, Animator anim, AvatarPerformanceStats perfStats) { //Create avatar debug hashset VRCAvatarDescriptor avatarSDK3 = avatar as VRCAvatarDescriptor; if (avatarSDK3 != null) { avatarSDK3.animationHashSet.Clear(); foreach (VRCAvatarDescriptor.CustomAnimLayer animLayer in avatarSDK3.baseAnimationLayers) { AnimatorController controller = animLayer.animatorController as AnimatorController; if (controller != null) { foreach (AnimatorControllerLayer layer in controller.layers) { ProcessStateMachine(layer.stateMachine, ""); void ProcessStateMachine(AnimatorStateMachine stateMachine, string prefix) { //Update prefix prefix = prefix + stateMachine.name + "."; //States foreach (var state in stateMachine.states) { VRCAvatarDescriptor.DebugHash hash = new VRCAvatarDescriptor.DebugHash(); string fullName = prefix + state.state.name; hash.hash = Animator.StringToHash(fullName); hash.name = fullName.Remove(0, layer.stateMachine.name.Length + 1); avatarSDK3.animationHashSet.Add(hash); } //Sub State Machines foreach (var subMachine in stateMachine.stateMachines) { ProcessStateMachine(subMachine.stateMachine, prefix); } } } } } } //Validate Playable Layers if (avatarSDK3 != null && avatarSDK3.customizeAnimationLayers) { VRCAvatarDescriptor.CustomAnimLayer gestureLayer = avatarSDK3.baseAnimationLayers[2]; if (anim != null && anim.isHuman && gestureLayer.animatorController != null && gestureLayer.type == VRCAvatarDescriptor.AnimLayerType.Gesture && !gestureLayer.isDefault) { AnimatorController controller = gestureLayer.animatorController as AnimatorController; if (controller != null && controller.layers[0].avatarMask == null) { _builder.OnGUIError(avatar, "Gesture Layer needs valid mask on first animator layer", delegate { OpenAnimatorControllerWindow(controller); }, null); } } } //Expression menu images if (avatarSDK3 != null) { bool ValidateTexture(Texture2D texture) { string path = AssetDatabase.GetAssetPath(texture); TextureImporter importer = AssetImporter.GetAtPath(path) as TextureImporter; if (importer == null) { return(true); } TextureImporterPlatformSettings settings = importer.GetDefaultPlatformTextureSettings(); //Max texture size if ((texture.width > MAX_ACTION_TEXTURE_SIZE || texture.height > MAX_ACTION_TEXTURE_SIZE) && settings.maxTextureSize > MAX_ACTION_TEXTURE_SIZE) { return(false); } //Compression if (settings.textureCompression == TextureImporterCompression.Uncompressed) { return(false); } //Success return(true); } void FixTexture(Texture2D texture) { string path = AssetDatabase.GetAssetPath(texture); TextureImporter importer = AssetImporter.GetAtPath(path) as TextureImporter; if (importer == null) { return; } TextureImporterPlatformSettings settings = importer.GetDefaultPlatformTextureSettings(); //Max texture size if (texture.width > MAX_ACTION_TEXTURE_SIZE || texture.height > MAX_ACTION_TEXTURE_SIZE) { settings.maxTextureSize = Math.Min(settings.maxTextureSize, MAX_ACTION_TEXTURE_SIZE); } //Compression if (settings.textureCompression == TextureImporterCompression.Uncompressed) { settings.textureCompression = TextureImporterCompression.Compressed; } //Set & Reimport importer.SetPlatformTextureSettings(settings); AssetDatabase.ImportAsset(path); } //Find all textures List <Texture2D> textures = new List <Texture2D>(); List <VRCExpressionsMenu> menuStack = new List <VRCExpressionsMenu>(); FindTextures(avatarSDK3.expressionsMenu); void FindTextures(VRCExpressionsMenu menu) { if (menu == null || menuStack.Contains(menu)) //Prevent recursive menu searching { return; } menuStack.Add(menu); //Check controls foreach (VRCExpressionsMenu.Control control in menu.controls) { AddTexture(control.icon); if (control.labels != null) { foreach (VRCExpressionsMenu.Control.Label label in control.labels) { AddTexture(label.icon); } } if (control.subMenu != null) { FindTextures(control.subMenu); } } void AddTexture(Texture2D texture) { if (texture != null) { textures.Add(texture); } } } //Validate bool isValid = true; foreach (Texture2D texture in textures) { if (!ValidateTexture(texture)) { isValid = false; } } if (!isValid) { _builder.OnGUIError(avatar, "Images used for Actions & Moods are too large.", delegate { Selection.activeObject = avatar.gameObject; }, FixTextures); } //Fix void FixTextures() { foreach (Texture2D texture in textures) { FixTexture(texture); } } } //Expression menu parameters if (avatarSDK3 != null) { //Check for expression menu/parameters object if (avatarSDK3.expressionsMenu != null || avatarSDK3.expressionParameters != null) { //Menu if (avatarSDK3.expressionsMenu == null) { _builder.OnGUIError(avatar, "VRCExpressionsMenu object reference is missing.", delegate { Selection.activeObject = avatarSDK3; }, null); } //Parameters if (avatarSDK3.expressionParameters == null) { _builder.OnGUIError(avatar, "VRCExpressionParameters object reference is missing.", delegate { Selection.activeObject = avatarSDK3; }, null); } } //Check if parameters is valid if (avatarSDK3.expressionParameters != null && avatarSDK3.expressionParameters.CalcTotalCost() > VRCExpressionParameters.MAX_PARAMETER_COST) { _builder.OnGUIError(avatar, "VRCExpressionParameters has too many parameters defined.", delegate { Selection.activeObject = avatarSDK3.expressionParameters; }, null); } //Find all existing parameters if (avatarSDK3.expressionsMenu != null && avatarSDK3.expressionParameters != null) { List <VRCExpressionsMenu> menuStack = new List <VRCExpressionsMenu>(); List <string> parameters = new List <string>(); List <VRCExpressionsMenu> selects = new List <VRCExpressionsMenu>(); FindParameters(avatarSDK3.expressionsMenu); void FindParameters(VRCExpressionsMenu menu) { if (menu == null || menuStack.Contains(menu)) //Prevent recursive menu searching { return; } menuStack.Add(menu); //Check controls foreach (VRCExpressionsMenu.Control control in menu.controls) { AddParameter(control.parameter); if (control.subParameters != null) { foreach (VRCExpressionsMenu.Control.Parameter subParameter in control.subParameters) { AddParameter(subParameter); } } if (control.subMenu != null) { FindParameters(control.subMenu); } } void AddParameter(VRCExpressionsMenu.Control.Parameter parameter) { if (parameter != null) { parameters.Add(parameter.name); selects.Add(menu); } } } //Validate parameters for (int i = 0; i < parameters.Count; i++) { string parameter = parameters[i]; VRCExpressionsMenu select = selects[i]; //Find bool exists = string.IsNullOrEmpty(parameter) || avatarSDK3.expressionParameters.FindParameter(parameter) != null; if (!exists) { _builder.OnGUIError(avatar, "VRCExpressionsMenu uses a parameter that is not defined.\nParameter: " + parameter, delegate { Selection.activeObject = select; }, null); } } //Validate param choices foreach (var menu in menuStack) { foreach (var control in menu.controls) { bool isValid = true; if (control.type == VRCExpressionsMenu.Control.ControlType.FourAxisPuppet) { isValid &= ValidateNonBoolParam(control.subParameters[0].name); isValid &= ValidateNonBoolParam(control.subParameters[1].name); isValid &= ValidateNonBoolParam(control.subParameters[2].name); isValid &= ValidateNonBoolParam(control.subParameters[3].name); } else if (control.type == VRCExpressionsMenu.Control.ControlType.RadialPuppet) { isValid &= ValidateNonBoolParam(control.subParameters[0].name); } else if (control.type == VRCExpressionsMenu.Control.ControlType.TwoAxisPuppet) { isValid &= ValidateNonBoolParam(control.subParameters[0].name); isValid &= ValidateNonBoolParam(control.subParameters[1].name); } if (!isValid) { _builder.OnGUIError(avatar, "VRCExpressionsMenu uses an invalid parameter for a control.\nControl: " + control.name, delegate { Selection.activeObject = menu; }, null); } } bool ValidateNonBoolParam(string name) { VRCExpressionParameters.Parameter param = string.IsNullOrEmpty(name) ? null : avatarSDK3.expressionParameters.FindParameter(name); if (param != null && param.valueType == VRCExpressionParameters.ValueType.Bool) { return(false); } return(true); } } } } IEnumerable <Component> componentsToRemove = AvatarValidation.FindIllegalComponents(avatar.gameObject); HashSet <string> componentsToRemoveNames = new HashSet <string>(); IEnumerable <Component> toRemove = componentsToRemove as Component[] ?? componentsToRemove.ToArray(); foreach (Component c in toRemove) { if (componentsToRemoveNames.Contains(c.GetType().Name) == false) { componentsToRemoveNames.Add(c.GetType().Name); } } if (componentsToRemoveNames.Count > 0) { _builder.OnGUIError(avatar, "The following component types are found on the Avatar and will be removed by the client: " + string.Join(", ", componentsToRemoveNames.ToArray()), delegate { ShowRestrictedComponents(toRemove); }, delegate { FixRestrictedComponents(toRemove); }); } List <AudioSource> audioSources = avatar.gameObject.GetComponentsInChildren <AudioSource>(true).ToList(); if (audioSources.Count > 0) { _builder.OnGUIWarning(avatar, "Audio sources found on Avatar, they will be adjusted to safe limits, if necessary.", GetAvatarSubSelectAction(avatar, typeof(AudioSource)), null); } List <VRCStation> stations = avatar.gameObject.GetComponentsInChildren <VRCStation>(true).ToList(); if (stations.Count > 0) { _builder.OnGUIWarning(avatar, "Stations found on Avatar, they will be adjusted to safe limits, if necessary.", GetAvatarSubSelectAction(avatar, typeof(VRCStation)), null); } if (VRCSdkControlPanel.HasSubstances(avatar.gameObject)) { _builder.OnGUIWarning(avatar, "This avatar has one or more Substance materials, which is not supported and may break in-game. Please bake your Substances to regular materials.", () => { Selection.objects = VRCSdkControlPanel.GetSubstanceObjects(avatar.gameObject); }, null); } CheckAvatarMeshesForLegacyBlendShapesSetting(avatar); #if UNITY_ANDROID IEnumerable <Shader> illegalShaders = AvatarValidation.FindIllegalShaders(avatar.gameObject); foreach (Shader s in illegalShaders) { _builder.OnGUIError(avatar, "Avatar uses unsupported shader '" + s.name + "'. You can only use the shaders provided in 'VRChat/Mobile' for Quest avatars.", delegate() { Selection.activeObject = avatar.gameObject; }, null); } #endif foreach (AvatarPerformanceCategory perfCategory in Enum.GetValues(typeof(AvatarPerformanceCategory))) { if (perfCategory == AvatarPerformanceCategory.Overall || perfCategory == AvatarPerformanceCategory.PolyCount || perfCategory == AvatarPerformanceCategory.AABB || perfCategory == AvatarPerformanceCategory.AvatarPerformanceCategoryCount) { continue; } Action show = null; switch (perfCategory) { case AvatarPerformanceCategory.AnimatorCount: show = GetAvatarSubSelectAction(avatar, typeof(Animator)); break; case AvatarPerformanceCategory.AudioSourceCount: show = GetAvatarSubSelectAction(avatar, typeof(AudioSource)); break; case AvatarPerformanceCategory.BoneCount: show = GetAvatarSubSelectAction(avatar, typeof(SkinnedMeshRenderer)); break; case AvatarPerformanceCategory.ClothCount: show = GetAvatarSubSelectAction(avatar, typeof(Cloth)); break; case AvatarPerformanceCategory.ClothMaxVertices: show = GetAvatarSubSelectAction(avatar, typeof(Cloth)); break; case AvatarPerformanceCategory.LightCount: show = GetAvatarSubSelectAction(avatar, typeof(Light)); break; case AvatarPerformanceCategory.LineRendererCount: show = GetAvatarSubSelectAction(avatar, typeof(LineRenderer)); break; case AvatarPerformanceCategory.MaterialCount: show = GetAvatarSubSelectAction(avatar, new[] { typeof(MeshRenderer), typeof(SkinnedMeshRenderer) }); break; case AvatarPerformanceCategory.MeshCount: show = GetAvatarSubSelectAction(avatar, new[] { typeof(MeshRenderer), typeof(SkinnedMeshRenderer) }); break; case AvatarPerformanceCategory.ParticleCollisionEnabled: show = GetAvatarSubSelectAction(avatar, typeof(ParticleSystem)); break; case AvatarPerformanceCategory.ParticleMaxMeshPolyCount: show = GetAvatarSubSelectAction(avatar, typeof(ParticleSystem)); break; case AvatarPerformanceCategory.ParticleSystemCount: show = GetAvatarSubSelectAction(avatar, typeof(ParticleSystem)); break; case AvatarPerformanceCategory.ParticleTotalCount: show = GetAvatarSubSelectAction(avatar, typeof(ParticleSystem)); break; case AvatarPerformanceCategory.ParticleTrailsEnabled: show = GetAvatarSubSelectAction(avatar, typeof(ParticleSystem)); break; case AvatarPerformanceCategory.PhysicsColliderCount: show = GetAvatarSubSelectAction(avatar, typeof(Collider)); break; case AvatarPerformanceCategory.PhysicsRigidbodyCount: show = GetAvatarSubSelectAction(avatar, typeof(Rigidbody)); break; case AvatarPerformanceCategory.PolyCount: show = GetAvatarSubSelectAction(avatar, new[] { typeof(MeshRenderer), typeof(SkinnedMeshRenderer) }); break; case AvatarPerformanceCategory.SkinnedMeshCount: show = GetAvatarSubSelectAction(avatar, typeof(SkinnedMeshRenderer)); break; case AvatarPerformanceCategory.TrailRendererCount: show = GetAvatarSubSelectAction(avatar, typeof(TrailRenderer)); break; } // we can only show these buttons if DynamicBone is installed Type dynamicBoneType = typeof(AvatarValidation).Assembly.GetType("DynamicBone"); Type dynamicBoneColliderType = typeof(AvatarValidation).Assembly.GetType("DynamicBoneCollider"); if ((dynamicBoneType != null) && (dynamicBoneColliderType != null)) { switch (perfCategory) { case AvatarPerformanceCategory.DynamicBoneColliderCount: show = GetAvatarSubSelectAction(avatar, dynamicBoneColliderType); break; case AvatarPerformanceCategory.DynamicBoneCollisionCheckCount: show = GetAvatarSubSelectAction(avatar, dynamicBoneColliderType); break; case AvatarPerformanceCategory.DynamicBoneComponentCount: show = GetAvatarSubSelectAction(avatar, dynamicBoneType); break; case AvatarPerformanceCategory.DynamicBoneSimulatedBoneCount: show = GetAvatarSubSelectAction(avatar, dynamicBoneType); break; } } OnGUIPerformanceInfo(avatar, perfStats, perfCategory, show, null); } _builder.OnGUILink(avatar, "Avatar Optimization Tips", VRCSdkControlPanel.AVATAR_OPTIMIZATION_TIPS_URL); }
private void ShowMenuFoldout(VRCExpressionsMenu menu, string title) { if (menu == null) { return; } bool b = expandedMenus.Contains(menu); // Remove before we go any further, prevents infinite recursion on cyclic menus. expandedMenus.Remove(menu); if (!EditorGUILayout.Foldout(b, title, true)) { return; } EditorGUILayout.BeginVertical(); GUILayout.BeginHorizontal(); GUILayout.Space(EditorGUI.indentLevel * 17); Debug.Log(expressionParams.parameters.Length + ":" + menu.controls.Count); int size = 0; for (int i = 0; i < expressionParams.parameters.Length; i++) { size += VRCExpressionParameters.TypeCost(expressionParams.parameters[i].valueType); } if (menu.controls.Count < expressionParams.parameters.Length) { if (GUILayout.Button("Add Marker Here", GUILayout.Width(130))) { InstallMarker(ref menu); } } else { if (size < VRCExpressionParameters.MAX_PARAMETER_COST) { if (GUILayout.Button("enlarge parameters", GUILayout.Width(130))) { if (size < VRCExpressionParameters.MAX_PARAMETER_COST) { VRCExpressionParameters.Parameter[] newParams = new VRCExpressionParameters.Parameter[expressionParams.parameters.Length + 1]; for (int i = 0; i < expressionParams.parameters.Length; i++) { newParams[i] = expressionParams.parameters[i]; } newParams[expressionParams.parameters.Length] = new VRCExpressionParameters.Parameter(); expressionParams.parameters = newParams; EditorUtility.SetDirty(expressionParams); } } } else { GUILayout.Label("No room.", GUILayout.Width(130)); } } GUILayout.EndHorizontal(); EditorGUI.indentLevel++; foreach (var child in menu.controls) { if (child.type == ControlType.SubMenu) { ShowMenuFoldout(child.subMenu, child.name); } } EditorGUI.indentLevel--; EditorGUILayout.EndVertical(); // Okay, it's safe to add the menu back. expandedMenus.Add(menu); }
private bool VRC_Actions(string songLibrary, List <AudioClip> libAssets, Avatar8TrackEditor _8Track) { //--- --- VRC Expression Parameters --- --- if (null != expressionParameters) { VRCExpressionParameters.Parameter volumeParam = new VRCExpressionParameters.Parameter() { name = _8Track.VolumeEPName, valueType = VRCExpressionParameters.ValueType.Float }; VRCExpressionParameters.Parameter trackParam = new VRCExpressionParameters.Parameter() { name = _8Track.TrackEPName, valueType = VRCExpressionParameters.ValueType.Int }; VRCExpressionParameters.Parameter[] parameters = expressionParameters.parameters; bool hasTrack = false; bool hasVolume = false; foreach (VRCExpressionParameters.Parameter parameter in parameters) { if (parameter.name == _8TrackEditor.VolumeEPName) { hasVolume = true; } else if (parameter.name == _8TrackEditor.TrackEPName) { hasTrack = true; } } if (!hasVolume) { ArrayUtility.Add <VRCExpressionParameters.Parameter>(ref expressionParameters.parameters, volumeParam); } if (!hasTrack) { ArrayUtility.Add <VRCExpressionParameters.Parameter>(ref expressionParameters.parameters, trackParam); } UnityEditor.EditorUtility.SetDirty(expressionParameters); } //--- --- VRC Expression Menu --- --- VRCExpressionsMenu mainMenu = new VRCExpressionsMenu() { name = _8Track._8TrackObject.name + " Menu" }; mainMenu.controls.Add(new VRCExpressionsMenu.Control() { icon = _8Track.StopIcon, name = "Stop", parameter = new VRCExpressionsMenu.Control.Parameter() { name = _8Track.TrackEPName }, type = VRCExpressionsMenu.Control.ControlType.Toggle, value = 0 }); mainMenu.controls.Add(new VRCExpressionsMenu.Control() { icon = _8Track.VolumeIcon, name = "Volume", subParameters = new VRCExpressionsMenu.Control.Parameter[] { new VRCExpressionsMenu.Control.Parameter() { name = _8Track.VolumeEPName } }, type = VRCExpressionsMenu.Control.ControlType.RadialPuppet }); VRCExpressionsMenu disk = new VRCExpressionsMenu() { name = "Disk " + 1 }; mainMenu.controls.Add(new VRCExpressionsMenu.Control() { icon = _8Track.DiskIcon, name = disk.name, type = VRCExpressionsMenu.Control.ControlType.SubMenu, subMenu = disk }); int menuCount = 3; VRCExpressionsMenu menu = mainMenu; List <VRCExpressionsMenu> menus = new List <VRCExpressionsMenu>(); int diskCount = 0; List <VRCExpressionsMenu> disks = new List <VRCExpressionsMenu> { disk }; int trackNumber = 0; do { if (libAssets.Count <= trackNumber) { break; } string trackName = libAssets[trackNumber].name; trackNumber++; if (diskCount == 8) { VRCExpressionsMenu newDisk = new VRCExpressionsMenu() { name = "Disk " + (disks.Count + 1) }; if (7 == menuCount && 0 < libAssets.Count - (trackNumber)) { VRCExpressionsMenu newMenu = new VRCExpressionsMenu() { name = "More..." }; menu.controls.Add(new VRCExpressionsMenu.Control() { icon = _8Track.FolderIcon, name = newMenu.name, type = VRCExpressionsMenu.Control.ControlType.SubMenu, subMenu = newMenu }); menus.Add(newMenu); menu = newMenu; menuCount = 0; } menu.controls.Add(new VRCExpressionsMenu.Control() { icon = _8Track.DiskIcon, name = newDisk.name, type = VRCExpressionsMenu.Control.ControlType.SubMenu, subMenu = newDisk }); menuCount++; disks.Add(newDisk); disk = newDisk; diskCount = 0; } disk.controls.Add(new VRCExpressionsMenu.Control() { icon = _8Track.TrackIcon, name = trackName, parameter = new VRCExpressionsMenu.Control.Parameter() { name = _8Track.TrackEPName }, type = VRCExpressionsMenu.Control.ControlType.Toggle, value = trackNumber }); diskCount++; } while(true); foreach (VRCExpressionsMenu d in disks) { CreateAsset(_8Track.AudioClip, d, d.name + ".asset"); EditorUtility.SetDirty(d); } CreateAsset(_8Track.AudioClip, mainMenu, mainMenu.name + ".asset"); EditorUtility.SetDirty(mainMenu); for (int i = 0; i < menus.Count; i++) { CreateAsset(_8TrackEditor.AudioClip, menus[i], _8Track._8TrackObject.name + " Menu " + i + ".asset"); } if (null != expressionsMenu) { VRCExpressionsMenu.Control toggle = GetMenuControl(); expressionsMenu.controls.Remove(toggle); expressionsMenu.controls.Add(new VRCExpressionsMenu.Control() { icon = _8Track.FolderIcon, name = _8TrackEditor._8TrackObject.name, type = VRCExpressionsMenu.Control.ControlType.SubMenu, subMenu = mainMenu }); EditorUtility.SetDirty(expressionsMenu); } EditorUtility.SetDirty(expressionParameters); AssetDatabase.SaveAssets(); return(true); }