// Updates a layer value, need to do this cause the CustomAnimLayer is a struct and not a class. public void UpdateLayer(int index, VRCAvatarDescriptor.CustomAnimLayer layer) { if (index >= _avatar.baseAnimationLayers.Length) { _avatar.specialAnimationLayers[index - _avatar.baseAnimationLayers.Length] = layer; } else { _avatar.baseAnimationLayers[index] = layer; } }
public AvatarDefinition(VRCAvatarDescriptor descriptor) { Name = descriptor.name; AvatarDescriptor = descriptor; VrcExpressionParameters = AvatarDescriptor.expressionParameters; if (VrcExpressionParameters != null) { foreach (var parameter in VrcExpressionParameters.parameters) { AddParameter(parameter); } } if (descriptor.expressionsMenu != null) { AddMenu(descriptor.expressionsMenu); } var animators = descriptor.baseAnimationLayers; for (var i = 0; i < animators.Length; i++) { VRCAvatarDescriptor.CustomAnimLayer animLayer = animators[i]; if (animLayer.isDefault) { continue; } AnimatorDefinition.AnimatorType type = AnimatorDefinition.AnimatorType.Action; switch (i) { case 0: type = AnimatorDefinition.AnimatorType.Action; break; case 1: type = AnimatorDefinition.AnimatorType.Additive; break; case 2: type = AnimatorDefinition.AnimatorType.Base; break; case 3: type = AnimatorDefinition.AnimatorType.Gesture; break; case 4: type = AnimatorDefinition.AnimatorType.FX; break; } AddAnimator(animLayer.animatorController as AnimatorController, type); } }
public static void SetAnimationLayer(this VRCAvatarDescriptor avatar, VRCAvatarDescriptor.AnimLayerType layer, VRCAvatarDescriptor.CustomAnimLayer animation) { switch (layer) { case VRCAvatarDescriptor.AnimLayerType.Base: case VRCAvatarDescriptor.AnimLayerType.Additive: case VRCAvatarDescriptor.AnimLayerType.Gesture: case VRCAvatarDescriptor.AnimLayerType.Action: case VRCAvatarDescriptor.AnimLayerType.FX: var(_, bIndex) = avatar.baseAnimationLayers.Select((w, i) => (Value: w, Index: i)).First(w => w.Value.type == layer); avatar.baseAnimationLayers[bIndex] = animation; break; case VRCAvatarDescriptor.AnimLayerType.SpecialIK: case VRCAvatarDescriptor.AnimLayerType.Sitting: case VRCAvatarDescriptor.AnimLayerType.TPose: case VRCAvatarDescriptor.AnimLayerType.IKPose: var(_, sIndex) = avatar.specialAnimationLayers.Select((w, i) => (Value: w, Index: i)).First(w => w.Value.type == layer); avatar.specialAnimationLayers[sIndex] = animation; break; default: throw new ArgumentOutOfRangeException(nameof(layer), layer, null); } }
public void GenerateInventory() { string generatedDirPath = $"Assets/Merlin/Inventory/_generated/{descriptorGUID}"; if (!Directory.Exists(generatedDirPath)) { Directory.CreateDirectory(generatedDirPath); } // Generate the stage parameters for the inventory toggles VRCExpressionParameters inventoryStageParams; string stageParameterPath = $"{generatedDirPath}/customStageParams.asset"; if (basisStageParameters != null) { AssetDatabase.CopyAsset(AssetDatabase.GetAssetPath(basisStageParameters), stageParameterPath); inventoryStageParams = AssetDatabase.LoadAssetAtPath <VRCExpressionParameters>(stageParameterPath); } else { inventoryStageParams = ScriptableObject.CreateInstance <VRCExpressionParameters>(); AssetDatabase.CreateAsset(inventoryStageParams, $"{generatedDirPath}/customStageParams.asset"); } List <VRCExpressionParameters.Parameter> originalParams = new List <VRCExpressionParameters.Parameter>(); if (inventoryStageParams.parameters != null) { foreach (VRCExpressionParameters.Parameter param in inventoryStageParams.parameters) { if (!string.IsNullOrEmpty(param.name)) { originalParams.Add(new VRCExpressionParameters.Parameter() { name = param.name, valueType = param.valueType }); } } } if (inventorySlots.Length + originalParams.Count > 128) { Debug.LogError($"Cannot have more than {128 - originalParams.Count} inventory slots"); return; } VRCExpressionParameters.Parameter[] basisParameters = inventoryStageParams.parameters; inventoryStageParams.parameters = new VRCExpressionParameters.Parameter[inventorySlots.Length]; for (int i = 0; i < originalParams.Count; ++i) { inventoryStageParams.parameters[i] = originalParams[i]; } for (int i = originalParams.Count; i < inventorySlots.Length + originalParams.Count; ++i) { inventoryStageParams.parameters[i] = new VRCExpressionParameters.Parameter() { name = $"GenInventorySlot{i - originalParams.Count}", valueType = VRCExpressionParameters.ValueType.Bool } } ; // for (int i = originalParams.Count + inventorySlots.Length; i < 16; ++i) // Clear out empty params // inventoryStageParams.parameters[i] = new VRCExpressionParameters.Parameter() { name = "", valueType = VRCExpressionParameters.ValueType.Float }; // Generate menu asset for (int menuNum = 0; menuNum < 8; ++menuNum) { VRCExpressionsMenu menuAsset; string menuPath = $"{generatedDirPath}/expressionMenu{menuNum}.asset"; if (basisMenu) { AssetDatabase.CopyAsset(AssetDatabase.GetAssetPath(basisMenu), menuPath); menuAsset = AssetDatabase.LoadAssetAtPath <VRCExpressionsMenu>(menuPath); } else { menuAsset = ScriptableObject.CreateInstance <VRCExpressionsMenu>(); AssetDatabase.CreateAsset(menuAsset, menuPath); } int offset = menuNum * 8; if (offset >= inventorySlots.Length) { break; } for (int i = 0; i < 8; ++i) { if (i + offset >= inventorySlots.Length) { break; } menuAsset.controls.Add(new VRCExpressionsMenu.Control() { icon = inventorySlots[i + offset].slotIcon, name = inventorySlots[i + offset].slotName, parameter = new VRCExpressionsMenu.Control.Parameter() { name = $"GenInventorySlot{i+offset}" }, type = VRCExpressionsMenu.Control.ControlType.Toggle, value = 1, }); } } // Generate controller AnimatorController controller; string controllerPath = $"{generatedDirPath}/inventoryController.controller"; if (basisAnimator) { AssetDatabase.CopyAsset(AssetDatabase.GetAssetPath(basisAnimator), controllerPath); controller = AssetDatabase.LoadAssetAtPath <AnimatorController>(controllerPath); } else { controller = AnimatorController.CreateAnimatorControllerAtPath(controllerPath); } AnimationClip[] inventoryClips = new AnimationClip[inventorySlots.Length]; AnimationClip[] inventoryResetClips = new AnimationClip[inventorySlots.Length]; // Generate layer mask AvatarMask maskEverything = new AvatarMask(); for (int i = 0; i < (int)AvatarMaskBodyPart.LastBodyPart; ++i) { maskEverything.SetHumanoidBodyPartActive((AvatarMaskBodyPart)i, false); } maskEverything.name = "maskEverythingMask"; AssetDatabase.AddObjectToAsset(maskEverything, controller); // Generate animation clips for (int i = 0; i < inventorySlots.Length; ++i) { InventorySlot slot = inventorySlots[i]; // Set initial object state // foreach (GameObject toggleObject in slot.slotToggleItems) // if (toggleObject) // toggleObject.SetActive(slot.startEnabled); string animationClipPath = $"{generatedDirPath}/Animations/_toggle{i}.anim"; AnimationClip toggleClip = GenerateToggleClip(slot.slotToggleItems, !slot.startEnabled); AnimationClip resetClip = GenerateToggleClip(slot.slotToggleItems, slot.startEnabled); //AssetDatabase.CreateAsset(toggleClip, animationClipPath); inventoryClips[i] = toggleClip; inventoryResetClips[i] = resetClip; toggleClip.name = $"toggleAnim{i}"; AssetDatabase.AddObjectToAsset(toggleClip, controller); resetClip.name = $"resetAnim{i}"; AssetDatabase.AddObjectToAsset(resetClip, controller); } // Generate controller layers for (int i = 0; i < inventorySlots.Length; ++i) { string paramName = $"GenInventorySlot{i}"; controller.AddParameter(paramName, AnimatorControllerParameterType.Bool); string layerName = $"GenToggleLayer{i}"; AnimatorControllerLayer toggleLayer = new AnimatorControllerLayer(); toggleLayer.name = layerName; toggleLayer.defaultWeight = 1f; toggleLayer.stateMachine = new AnimatorStateMachine(); toggleLayer.stateMachine.name = toggleLayer.name; toggleLayer.stateMachine.hideFlags = HideFlags.HideInHierarchy; toggleLayer.avatarMask = maskEverything; if (AssetDatabase.GetAssetPath(controller) != "") { AssetDatabase.AddObjectToAsset(toggleLayer.stateMachine, AssetDatabase.GetAssetPath(controller)); } AnimatorStateMachine stateMachine = toggleLayer.stateMachine; AnimatorState nullState = stateMachine.AddState("Null State", stateMachine.entryPosition + new Vector3(200f, 0f)); AnimatorState toggleState = stateMachine.AddState("Toggle Triggered", stateMachine.entryPosition + new Vector3(500f, 0f)); nullState.motion = inventoryResetClips[i]; toggleState.motion = inventoryClips[i]; AnimatorStateTransition toToggle = nullState.AddTransition(toggleState); toToggle.exitTime = 0f; toToggle.hasExitTime = false; toToggle.hasFixedDuration = true; toToggle.duration = 0f; AnimatorStateTransition toNull = toggleState.AddTransition(nullState); toNull.exitTime = 0f; toNull.hasExitTime = false; toNull.hasFixedDuration = true; toNull.duration = 0f; toToggle.AddCondition(AnimatorConditionMode.If, 0f, paramName); toNull.AddCondition(AnimatorConditionMode.IfNot, 0f, paramName); controller.AddLayer(toggleLayer); } // Setup layers on the avatar descriptor VRCAvatarDescriptor descriptor = GetComponent <VRCAvatarDescriptor>(); // descriptor.expressionsMenu = menuAsset; descriptor.expressionParameters = inventoryStageParams; VRCAvatarDescriptor.CustomAnimLayer layer = new VRCAvatarDescriptor.CustomAnimLayer(); layer.isDefault = false; layer.animatorController = controller; layer.type = inventoryAnimLayer; for (int i = 0; i < descriptor.baseAnimationLayers.Length; ++i) { if (descriptor.baseAnimationLayers[i].type == inventoryAnimLayer) { descriptor.baseAnimationLayers[i] = layer; break; } } AssetDatabase.SaveAssets(); }
// Updates a layer value, need to do this cause the CustomAnimLayer is a struct and not a class. public void UpdateLayer(int index, VRCAvatarDescriptor.CustomAnimLayer layer) { _avatar.baseAnimationLayers[index] = layer; }
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); }
public static int LayerSort(VRCAvatarDescriptor.CustomAnimLayer x, VRCAvatarDescriptor.CustomAnimLayer y) => SortValue[x.type] - SortValue[y.type];
public void Inspector_Body() { //Avatar EditorGUILayout.BeginVertical(GUI.skin.box); EditorGUI.indentLevel += 1; { script.avatarDescriptor = (VRCAvatarDescriptor)EditorGUILayout.ObjectField("Avatar", script.avatarDescriptor, typeof(VRCAvatarDescriptor), true); } EditorGUI.indentLevel -= 1; EditorGUILayout.EndVertical(); //Menu Actions EditorGUILayout.BeginVertical(GUI.skin.box); EditorGUI.indentLevel += 1; { EditorGUILayout.BeginHorizontal(); script.menuActions = (MenuActions)EditorGUILayout.ObjectField("Menu Actions", script.menuActions, typeof(MenuActions), false); EditorGUILayout.EndHorizontal(); } EditorGUI.indentLevel -= 1; EditorGUILayout.EndVertical(); //Non-Menu Actions EditorGUILayout.BeginVertical(GUI.skin.box); EditorGUI.indentLevel += 1; EditorGUILayout.LabelField("Other Actions"); { if (GUILayout.Button("Add")) { script.otherActions.Add(null); } for (int i = 0; i < script.otherActions.Count; i++) { EditorGUILayout.BeginHorizontal(); { //Reference script.otherActions[i] = (NonMenuActions)EditorGUILayout.ObjectField("Actions", script.otherActions[i], typeof(NonMenuActions), false); //Delete if (GUILayout.Button("X", GUILayout.Width(32))) { script.otherActions.RemoveAt(i); i -= 1; } } EditorGUILayout.EndHorizontal(); } } EditorGUI.indentLevel -= 1; EditorGUILayout.EndVertical(); EditorBase.Divider(); //Build EditorGUI.BeginDisabledGroup(script.ReturnAnyScriptableObject() == null || script.avatarDescriptor == null); if (GUILayout.Button("Build Avatar Data", GUILayout.Height(32))) { BaseActions.BuildAvatarData(script.avatarDescriptor, script); } EditorGUI.EndDisabledGroup(); //Build Options EditorGUILayout.BeginVertical(GUI.skin.box); EditorGUI.indentLevel += 1; { script.foldoutBuildOptions = EditorGUILayout.Foldout(script.foldoutBuildOptions, "Built Options"); if (script.foldoutBuildOptions) { //Ignore Lists DrawStringList(ref script.foldoutIgnoreLayers, "Ignore Layers", script.ignoreLayers); DrawStringList(ref script.foldoutIgnoreParameters, "Ignore Parameters", script.ignoreParameters); void DrawStringList(ref bool foldout, string title, List <string> list) { EditorGUI.indentLevel += 1; foldout = EditorGUILayout.Foldout(foldout, BaseActionsEditor.Title(title, list.Count > 0)); if (foldout) { //Add GUILayout.BeginHorizontal(); GUILayout.Space(EditorGUI.indentLevel * 10); if (GUILayout.Button("Add")) { list.Add(null); } GUILayout.EndHorizontal(); //Layers for (int i = 0; i < list.Count; i++) { EditorGUILayout.BeginHorizontal(); list[i] = EditorGUILayout.TextField(list[i]); if (GUILayout.Button("X", GUILayout.Width(32))) { list.RemoveAt(i); i--; } EditorGUILayout.EndHorizontal(); } } EditorGUI.indentLevel -= 1; } //Parameter Defaults { EditorGUI.indentLevel += 1; script.foldoutParameterDefaults = EditorGUILayout.Foldout(script.foldoutParameterDefaults, BaseActionsEditor.Title("Paramater Defaults", script.parameterDefaults.Count > 0)); if (script.foldoutParameterDefaults) { //Add GUILayout.BeginHorizontal(); GUILayout.Space(EditorGUI.indentLevel * 10); if (GUILayout.Button("Add")) { script.parameterDefaults.Add(new AvatarActions.ParamDefault()); } GUILayout.EndHorizontal(); //Layers for (int i = 0; i < script.parameterDefaults.Count; i++) { var value = script.parameterDefaults[i]; EditorGUILayout.BeginHorizontal(); value.name = EditorGUILayout.TextField(value.name); value.value = EditorGUILayout.FloatField(value.value); script.parameterDefaults[i] = value; if (GUILayout.Button("X", GUILayout.Width(32))) { script.parameterDefaults.RemoveAt(i); i--; } EditorGUILayout.EndHorizontal(); } } EditorGUI.indentLevel -= 1; } } } EditorGUI.indentLevel -= 1; EditorGUILayout.EndVertical(); //Build Data EditorGUILayout.BeginVertical(GUI.skin.box); EditorGUI.indentLevel += 1; script.foldoutBuildData = EditorGUILayout.Foldout(script.foldoutBuildData, "Built Data"); if (script.foldoutBuildData && script.avatarDescriptor != null) { void AnimationController(VRCAvatarDescriptor.AnimLayerType animLayerType, string name) { VRCAvatarDescriptor.CustomAnimLayer descLayer = new VRCAvatarDescriptor.CustomAnimLayer(); foreach (var layer in script.avatarDescriptor.baseAnimationLayers) { if (layer.type == animLayerType) { descLayer = layer; break; } } var controller = descLayer.animatorController as UnityEditor.Animations.AnimatorController; EditorGUI.BeginChangeCheck(); controller = (UnityEditor.Animations.AnimatorController)EditorGUILayout.ObjectField(name, controller, typeof(UnityEditor.Animations.AnimatorController), false); if (EditorGUI.EndChangeCheck()) { descLayer.animatorController = controller; descLayer.isDefault = false; } } EditorGUILayout.HelpBox("Objects built and linked on the avatar descriptor. Anything referenced here will be modified and possibly destroyed by the compiling process.", MessageType.Info); AnimationController(VRCAvatarDescriptor.AnimLayerType.Action, "Action Controller"); AnimationController(VRCAvatarDescriptor.AnimLayerType.FX, "FX Controller"); script.avatarDescriptor.expressionsMenu = (ExpressionsMenu)EditorGUILayout.ObjectField("Expressions Menu", script.avatarDescriptor.expressionsMenu, typeof(ExpressionsMenu), false); script.avatarDescriptor.expressionParameters = (ExpressionParameters)EditorGUILayout.ObjectField("Expression Parameters", script.avatarDescriptor.expressionParameters, typeof(ExpressionParameters), false); } EditorGUI.indentLevel -= 1; EditorGUILayout.EndVertical(); }