public override void OnInspectorGUI() { if (avatarDescriptor == null) { avatarDescriptor = (VRCSDK2.VRC_AvatarDescriptor)target; } if (pipelineManager == null) { pipelineManager = avatarDescriptor.GetComponent <VRC.Core.PipelineManager>(); if (pipelineManager == null) { avatarDescriptor.gameObject.AddComponent <VRC.Core.PipelineManager>(); } } // DrawDefaultInspector(); avatarDescriptor.ViewPosition = EditorGUILayout.Vector3Field("View Position", avatarDescriptor.ViewPosition); //avatarDescriptor.Name = EditorGUILayout.TextField("Avatar Name", avatarDescriptor.Name); avatarDescriptor.Animations = (VRCSDK2.VRC_AvatarDescriptor.AnimationSet)EditorGUILayout.EnumPopup("Default Animation Set", avatarDescriptor.Animations); avatarDescriptor.CustomStandingAnims = (AnimatorOverrideController)EditorGUILayout.ObjectField("Custom Standing Anims", avatarDescriptor.CustomStandingAnims, typeof(AnimatorOverrideController), true, null); avatarDescriptor.CustomSittingAnims = (AnimatorOverrideController)EditorGUILayout.ObjectField("Custom Sitting Anims", avatarDescriptor.CustomSittingAnims, typeof(AnimatorOverrideController), true, null); avatarDescriptor.ScaleIPD = EditorGUILayout.Toggle("Scale IPD", avatarDescriptor.ScaleIPD); avatarDescriptor.lipSync = (VRCSDK2.VRC_AvatarDescriptor.LipSyncStyle)EditorGUILayout.EnumPopup("Lip Sync", avatarDescriptor.lipSync); switch (avatarDescriptor.lipSync) { case VRCSDK2.VRC_AvatarDescriptor.LipSyncStyle.Default: if (GUILayout.Button("Auto Detect!")) { AutoDetectLipSync(); } break; case VRCSDK2.VRC_AvatarDescriptor.LipSyncStyle.JawFlapBlendShape: avatarDescriptor.VisemeSkinnedMesh = (SkinnedMeshRenderer)EditorGUILayout.ObjectField("Face Mesh", avatarDescriptor.VisemeSkinnedMesh, typeof(SkinnedMeshRenderer), true); if (avatarDescriptor.VisemeSkinnedMesh != null) { DetermineBlendShapeNames(); int current = -1; for (int b = 0; b < blendShapeNames.Count; ++b) { if (avatarDescriptor.MouthOpenBlendShapeName == blendShapeNames[b]) { current = b; } } string title = "Jaw Flap Blend Shape"; int next = EditorGUILayout.Popup(title, current, blendShapeNames.ToArray()); if (next >= 0) { avatarDescriptor.MouthOpenBlendShapeName = blendShapeNames[next]; } } break; case VRCSDK2.VRC_AvatarDescriptor.LipSyncStyle.JawFlapBone: avatarDescriptor.lipSyncJawBone = (Transform)EditorGUILayout.ObjectField("Jaw Bone", avatarDescriptor.lipSyncJawBone, typeof(Transform), true); break; case VRCSDK2.VRC_AvatarDescriptor.LipSyncStyle.VisemeBlendShape: avatarDescriptor.VisemeSkinnedMesh = (SkinnedMeshRenderer)EditorGUILayout.ObjectField("Face Mesh", avatarDescriptor.VisemeSkinnedMesh, typeof(SkinnedMeshRenderer), true); if (avatarDescriptor.VisemeSkinnedMesh != null) { DetermineBlendShapeNames(); FillvisBlendShapes(); if (avatarDescriptor.VisemeBlendShapes == null || avatarDescriptor.VisemeBlendShapes.Length != (int)VRCSDK2.VRC_AvatarDescriptor.Viseme.Count) { avatarDescriptor.VisemeBlendShapes = new string[(int)VRCSDK2.VRC_AvatarDescriptor.Viseme.Count]; for (int i = 0; i < visBlendShapes.Count; ++i) { int current = -1; for (int b = 0; b < blendShapeNames.Count; ++b) { if (visBlendShapes[i] == blendShapeNames[b]) { current = b; } else if (blendShapeNames[b] == "vrc.v_ee" && visBlendShapes[i] == "vrc.v_e") { current = b; } } string title = "Viseme: " + visBlendShapes[i].Replace("vrc.v_", ""); int next = EditorGUILayout.Popup(title, current, blendShapeNames.ToArray()); if (next >= 0) { avatarDescriptor.VisemeBlendShapes[i] = blendShapeNames[next]; } } break; } for (int i = 0; i < (int)VRCSDK2.VRC_AvatarDescriptor.Viseme.Count; ++i) { int current = -1; for (int b = 0; b < blendShapeNames.Count; ++b) { if (avatarDescriptor.VisemeBlendShapes[i] == blendShapeNames[b]) { current = b; } } string title = "Viseme: " + ((VRCSDK2.VRC_AvatarDescriptor.Viseme)i).ToString(); int next = EditorGUILayout.Popup(title, current, blendShapeNames.ToArray()); if (next >= 0) { avatarDescriptor.VisemeBlendShapes[i] = blendShapeNames[next]; } } } break; } EditorGUILayout.LabelField("Unity Version", avatarDescriptor.unityVersion); }
void OnGUIAvatarCheck(VRCSDK2.VRC_AvatarDescriptor avatar) { AvatarPerformanceStats perfStats = AvatarPerformance.CalculatePerformanceStats(avatar.Name, avatar.gameObject); OnGUIPerformanceInfo(avatar, perfStats, AvatarPerformanceCategory.Overall); OnGUIPerformanceInfo(avatar, perfStats, AvatarPerformanceCategory.PolyCount); OnGUIPerformanceInfo(avatar, perfStats, AvatarPerformanceCategory.AABB); var eventHandler = avatar.GetComponentInChildren <VRCSDK2.VRC_EventHandler>(); if (eventHandler != null) { OnGUIError(avatar, "This avatar contains an EventHandler, which is not currently supported in VRChat."); } if (avatar.lipSync == VRCSDK2.VRC_AvatarDescriptor.LipSyncStyle.VisemeBlendShape && avatar.VisemeSkinnedMesh == null) { OnGUIError(avatar, "This avatar uses Visemes but the Face Mesh is not specified."); } var anim = avatar.GetComponent <Animator>(); if (anim == null) { OnGUIWarning(avatar, "This avatar does not contain an animator, and will not animate in VRChat."); } else if (anim.isHuman == false) { OnGUIWarning(avatar, "This avatar is not imported as a humanoid rig and will not play VRChat's provided animation set."); } else if (avatar.gameObject.activeInHierarchy == false) { OnGUIError(avatar, "Your avatar is disabled in the scene hierarchy!"); } else { Transform foot = anim.GetBoneTransform(HumanBodyBones.LeftFoot); Transform shoulder = anim.GetBoneTransform(HumanBodyBones.LeftUpperArm); if (foot == null) { OnGUIError(avatar, "Your avatar is humanoid, but it's feet aren't specified!"); } if (shoulder == null) { OnGUIError(avatar, "Your avatar is humanoid, but it's upper arms aren't specified!"); } if (foot != null && shoulder != null) { Vector3 footPos = foot.position - avatar.transform.position; if (footPos.y < 0) { OnGUIWarning(avatar, "Avatar feet are beneath the avatar's origin (the floor). That's probably not what you want."); } Vector3 shoulderPosition = shoulder.position - avatar.transform.position; if (shoulderPosition.y < 0.2f) { OnGUIError(avatar, "This avatar is too short. The minimum is 20cm shoulder height."); } else if (shoulderPosition.y < 1.0f) { OnGUIWarning(avatar, "This avatar is short. This is probably shorter than you want."); } else if (shoulderPosition.y > 5.0f) { OnGUIWarning(avatar, "This avatar is too tall. The maximum is 5m shoulder height."); } else if (shoulderPosition.y > 2.5f) { OnGUIWarning(avatar, "This avatar is tall. This is probably taller than you want."); } } if (AnalyzeIK(avatar, avatar.gameObject, anim) == false) { OnGUILink(avatar, "See Avatar Rig Requirements for more information.", kAvatarRigRequirementsURL); } } IEnumerable <Component> componentsToRemove = VRCSDK2.AvatarValidation.FindIllegalComponents(avatar.gameObject); HashSet <string> componentsToRemoveNames = new HashSet <string>(); foreach (Component c in componentsToRemove) { if (componentsToRemoveNames.Contains(c.GetType().Name) == false) { componentsToRemoveNames.Add(c.GetType().Name); } } if (componentsToRemoveNames.Count > 0) { OnGUIError(avatar, "The following component types are found on the Avatar and will be removed by the client: " + string.Join(", ", componentsToRemoveNames.ToArray())); } if (VRCSDK2.AvatarValidation.EnforceAudioSourceLimits(avatar.gameObject).Count > 0) { OnGUIWarning(avatar, "Audio sources found on Avatar, they will be adjusted to safe limits, if necessary."); } if (VRCSDK2.AvatarValidation.EnforceAvatarStationLimits(avatar.gameObject).Count > 0) { OnGUIWarning(avatar, "Stations found on Avatar, they will be adjusted to safe limits, if necessary."); } if (avatar.gameObject.GetComponentInChildren <Camera>() != null) { OnGUIWarning(avatar, "Cameras are removed from non-local avatars at runtime."); } foreach (AvatarPerformanceCategory perfCategory in System.Enum.GetValues(typeof(AvatarPerformanceCategory))) { if (perfCategory == AvatarPerformanceCategory.Overall || perfCategory == AvatarPerformanceCategory.PolyCount || perfCategory == AvatarPerformanceCategory.AABB || perfCategory == AvatarPerformanceCategory.AvatarPerformanceCategoryCount) { continue; } OnGUIPerformanceInfo(avatar, perfStats, perfCategory); } OnGUILink(avatar, "Avatar Optimization Tips", kAvatarOptimizationTipsURL); }
void OnGUIAvatarCheck(VRCSDK2.VRC_AvatarDescriptor avatar) { int polycount; Bounds bounds; AnalyzeGeometry(avatar.gameObject, out bounds, out polycount); if (polycount < 10000) { OnGUIInformation(avatar, "Polygons: " + polycount); } else if (polycount < 15000) { OnGUIWarning(avatar, "Polygons: " + polycount + " - Please try to reduce your avatar poly count to less thatn 10k."); } else if (polycount < 20000) { OnGUIWarning(avatar, "Polygons: " + polycount + " - This avatar will not perform well on many systems."); } else { OnGUIError(avatar, "Polygons: " + polycount + " - This avatar has too many polygons. It must have less than 20k and should have less than 10k."); } if (bounds.size.x > 5f || bounds.size.y > 6.0f || bounds.size.z > 5f) { OnGUIError(avatar, "This avatar measures too large on at least one axis. It must be <5m on a side but it's bounds are " + bounds.size.ToString()); } var eventHandler = avatar.GetComponentInChildren <VRCSDK2.VRC_EventHandler>(); if (eventHandler != null) { OnGUIError(avatar, "This avatar contains an EventHandler, which is not currently supported in VRChat."); } if (avatar.lipSync == VRCSDK2.VRC_AvatarDescriptor.LipSyncStyle.VisemeBlendShape && avatar.VisemeSkinnedMesh == null) { OnGUIError(avatar, "This avatar uses Visemes but the Face Mesh is not specified."); } var anim = avatar.GetComponent <Animator>(); if (anim == null) { OnGUIWarning(avatar, "This avatar does not contain an animator, and will not animate in VRChat."); } else if (anim.isHuman == false) { OnGUIWarning(avatar, "This avatar is not imported as a humanoid rig and will not play VRChat's provided animation set."); } else if (avatar.gameObject.activeInHierarchy == false) { OnGUIError(avatar, "Your avatar is disabled in the scene heirarchy!"); } else { Transform foot = anim.GetBoneTransform(HumanBodyBones.LeftFoot); Transform shoulder = anim.GetBoneTransform(HumanBodyBones.LeftUpperArm); if (foot == null) { OnGUIError(avatar, "Your avatar is humanoid, but it's feet aren't specified!"); } if (shoulder == null) { OnGUIError(avatar, "Your avatar is humanoid, but it's upper arms aren't specified!"); } if (foot != null && shoulder != null) { Vector3 footPos = foot.position - avatar.transform.position; if (footPos.y < 0) { OnGUIWarning(avatar, "Avatar feet are beneath the avatar's origin (the floor). That's probably not what you want."); } Vector3 shoulderPosition = shoulder.position - avatar.transform.position; if (shoulderPosition.y < 0.2f) { OnGUIError(avatar, "This avatar is too short. The minimum is 20cm shoulder height."); } else if (shoulderPosition.y < 1.0f) { OnGUIWarning(avatar, "This avatar is short. This is probably shorter than you want."); } else if (shoulderPosition.y > 5.0f) { OnGUIWarning(avatar, "This avatar is too tall. The maximum is 5m shoulder height."); } else if (shoulderPosition.y > 2.5f) { OnGUIWarning(avatar, "This avatar is tall. This is probably taller than you want."); } } if (AnalyzeIK(avatar, avatar.gameObject, anim) == false) { OnGUILink(avatar, "See https://docs.vrchat.com/docs/rig-requirements for details."); } } { IEnumerable <Component> componentsToRemove = VRCSDK2.AvatarValidation.FindIllegalComponents(avatar.Name, avatar.gameObject); HashSet <string> componentsToRemoveNames = new HashSet <string>(); foreach (Component c in componentsToRemove) { if (componentsToRemoveNames.Contains(c.GetType().Name) == false) { componentsToRemoveNames.Add(c.GetType().Name); } } if (componentsToRemoveNames.Count > 0) { OnGUIError(avatar, "The following component types are found on the Avatar and will be removed by the client: " + string.Join(", ", componentsToRemoveNames.ToArray())); } } if (VRCSDK2.AvatarValidation.EnforceAudioSourceLimits(avatar.gameObject).Count > 0) { OnGUIWarning(avatar, "Audio sources found on Avatar, they will be adjusted to safe limits, if necessary."); } if (avatar.gameObject.GetComponentInChildren <Camera>() != null) { OnGUIWarning(avatar, "Cameras are removed from non-local avatars at runtime."); } }