private static void SliderFloat(LyumaAv3Menu menu, VRCExpressionsMenu.Control.Parameter subParam, string intent, float left, float right) { if (subParam == null || subParam.name == "") { EditorGUI.BeginDisabledGroup(true); EditorGUILayout.Slider(intent, 0, left, right); EditorGUI.EndDisabledGroup(); return; } menu.UserFloat(subParam.name, EditorGUILayout.Slider(intent + " (" + subParam.name + ")", menu.FindFloat(subParam.name), left, right)); }
public ParameterDefinition AddParameter(VRCExpressionsMenu.Control.Parameter parameter) { return(Children.AddChild(new ParameterDefinition(this, parameter.name))); }
/// <summary> /// Creates a copy of the avatar descriptor's topmost menu asset or creates one if it doesn't exist, adds the provided menu as a submenu, /// assigns the new topmost menu asset, and stores it in the specified directory. /// </summary> /// <param name="descriptor">The avatar descriptor to add the submenu to.</param> /// <param name="menuToAdd">The menu to add, which will become a submenu of the topmost menu.</param> /// <param name="controlName">The name of the submenu control for the menu to add.</param> /// <param name="directory">The unique directory to store the new topmost menu asset, ex. "Assets/MyCoolScript/GeneratedAssets/725638/".</param> /// <param name="controlParameter">Optionally, the parameter to trigger when the submenu is opened.</param> /// <param name="icon"> Optionally, the icon to display on this submenu. </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 AddSubMenu(VRCAvatarDescriptor descriptor, VRCExpressionsMenu menuToAdd, string controlName, string directory, VRCExpressionsMenu.Control.Parameter controlParameter = null, Texture2D icon = null, bool overwrite = true) { if (descriptor == null) { Debug.LogError("Couldn't add the menu, the avatar descriptor is null!"); return; } else if ((menuToAdd == null) || (controlName == null) || (controlName == "")) { Debug.LogError("Couldn't add the menu, it or the name of its control is null!"); return; } else if ((directory == null) || (directory == "")) { Debug.Log("Directory was not specified, storing new topmost menu in " + defaultDirectory); directory = defaultDirectory; } descriptor.customExpressions = true; VRCExpressionsMenu topMenu = ScriptableObject.CreateInstance <VRCExpressionsMenu>(); if (descriptor.expressionsMenu == null) { AssetDatabase.CreateAsset(topMenu, directory + "Menu Topmost.asset"); } else { if (descriptor.expressionsMenu.controls.Count == 8) { Debug.LogWarning("Couldn't add menu. Please have an available slot in your avatar's topmost Expression Menu."); return; } string path = (directory + descriptor.expressionsMenu.name + ".asset"); path = (overwrite) ? path : AssetDatabase.GenerateUniqueAssetPath(path); if (AssetDatabase.GetAssetPath(descriptor.expressionsMenu) != path) // if we have not made a copy yet { // CopyAsset with two identical strings yields exception AssetDatabase.CopyAsset(AssetDatabase.GetAssetPath(descriptor.expressionsMenu), path); } topMenu = AssetDatabase.LoadAssetAtPath(path, typeof(VRCExpressionsMenu)) as VRCExpressionsMenu; } List <VRCExpressionsMenu.Control> controlList = topMenu.controls; for (int i = 0; i < controlList.Count; i++) { if (controlList[i].name.Equals(controlName) && controlList[i].type.Equals(VRCExpressionsMenu.Control.ControlType.SubMenu)) { // if a control for a submenu exists with the same name, replace the submenu controlList[i].subMenu = menuToAdd; controlList[i].parameter = controlParameter; controlList[i].icon = icon; EditorUtility.SetDirty(topMenu); AssetDatabase.SaveAssets(); AssetDatabase.Refresh(); descriptor.expressionsMenu = topMenu; return; } } VRCExpressionsMenu.Control control = new VRCExpressionsMenu.Control { name = controlName, type = VRCExpressionsMenu.Control.ControlType.SubMenu, subMenu = menuToAdd, parameter = controlParameter, icon = icon }; controlList.Add(control); topMenu.controls = controlList; EditorUtility.SetDirty(topMenu); AssetDatabase.SaveAssets(); AssetDatabase.Refresh(); descriptor.expressionsMenu = topMenu; }
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!"); }