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 OnGUISceneCheck(VRC_SceneDescriptor scene) { CheckUploadChanges(scene); #if !VRC_SDK_VRCSDK2 && !VRC_SDK_VRCSDK3 bool isSdk3Scene = false; #elif VRC_SDK_VRCSDK2 && !VRC_SDK_VRCSDK3 bool isSdk3Scene = false; #elif !VRC_SDK_VRCSDK2 && VRC_SDK_VRCSDK3 bool isSdk3Scene = true; //Do we actually use this variable anywhere? #else bool isSdk3Scene = scene as VRCSceneDescriptor != null; #endif List <VRC_EventHandler> sdkBaseEventHandlers = new List <VRC_EventHandler>(Object.FindObjectsOfType <VRC_EventHandler>()); #if VRC_SDK_VRCSDK2 if (isSdk3Scene == false) { for (int i = sdkBaseEventHandlers.Count - 1; i >= 0; --i) { if (sdkBaseEventHandlers[i] as VRCSDK2.VRC_EventHandler) { sdkBaseEventHandlers.RemoveAt(i); } } } #endif if (sdkBaseEventHandlers.Count > 0) { _builder.OnGUIError(scene, "You have Event Handlers in your scene that are not allowed in this build configuration.", delegate { List <Object> gos = sdkBaseEventHandlers.ConvertAll(item => (Object)item.gameObject); Selection.objects = gos.ToArray(); }, delegate { foreach (VRC_EventHandler eh in sdkBaseEventHandlers) { #if VRC_SDK_VRCSDK2 GameObject go = eh.gameObject; if (isSdk3Scene == false) { if (VRC_SceneDescriptor.Instance as VRCSDK2.VRC_SceneDescriptor != null) { go.AddComponent <VRCSDK2.VRC_EventHandler>(); } } #endif Object.DestroyImmediate(eh); } }); } Vector3 g = Physics.gravity; if (Math.Abs(g.x) > float.Epsilon || Math.Abs(g.z) > float.Epsilon) { _builder.OnGUIWarning(scene, "Gravity vector is not straight down. Though we support different gravity, player orientation is always 'upwards' so things don't always behave as you intend.", delegate { SettingsService.OpenProjectSettings("Project/Physics"); }, null); } if (g.y > 0) { _builder.OnGUIWarning(scene, "Gravity vector is not straight down, inverted or zero gravity will make walking extremely difficult.", delegate { SettingsService.OpenProjectSettings("Project/Physics"); }, null); } if (Math.Abs(g.y) < float.Epsilon) { _builder.OnGUIWarning(scene, "Zero gravity will make walking extremely difficult, though we support different gravity, player orientation is always 'upwards' so this may not have the effect you're looking for.", delegate { SettingsService.OpenProjectSettings("Project/Physics"); }, null); } if (CheckFogSettings()) { _builder.OnGUIWarning( scene, "Fog shader stripping is set to Custom, this may lead to incorrect or unnecessary shader variants being included in the build. You should use Automatic unless you change the fog mode at runtime.", delegate { SettingsService.OpenProjectSettings("Project/Graphics"); }, delegate { EnvConfig.SetFogSettings( new EnvConfig.FogSettings(EnvConfig.FogSettings.FogStrippingMode.Automatic)); }); } if (scene.autoSpatializeAudioSources) { _builder.OnGUIWarning(scene, "Your scene previously used the 'Auto Spatialize Audio Sources' feature. This has been deprecated, press 'Fix' to disable. Also, please add VRC_SpatialAudioSource to all your audio sources. Make sure Spatial Blend is set to 3D for the sources you want to spatialize.", null, delegate { scene.autoSpatializeAudioSources = false; } ); } AudioSource[] audioSources = Object.FindObjectsOfType <AudioSource>(); foreach (AudioSource a in audioSources) { if (a.GetComponent <ONSPAudioSource>() != null) { _builder.OnGUIWarning(scene, "Found audio source(s) using ONSP, this is deprecated. Press 'fix' to convert to VRC_SpatialAudioSource.", delegate { Selection.activeObject = a.gameObject; }, delegate { Selection.activeObject = a.gameObject; AutoAddSpatialAudioComponents.ConvertONSPAudioSource(a); } ); break; } else if (a.GetComponent <VRC_SpatialAudioSource>() == null) { string msg = "Found 3D audio source with no VRC Spatial Audio component, this is deprecated. Press 'fix' to add a VRC_SpatialAudioSource."; if (IsAudioSource2D(a)) { msg = "Found 2D audio source with no VRC Spatial Audio component, this is deprecated. Press 'fix' to add a (disabled) VRC_SpatialAudioSource."; } _builder.OnGUIWarning(scene, msg, delegate { Selection.activeObject = a.gameObject; }, delegate { Selection.activeObject = a.gameObject; AutoAddSpatialAudioComponents.AddVRCSpatialToBareAudioSource(a); } ); break; } } if (VRCSdkControlPanel.HasSubstances()) { _builder.OnGUIWarning(scene, "One or more scene objects have Substance materials. This is not supported and may break in game. Please bake your Substances to regular materials.", () => { Selection.objects = VRCSdkControlPanel.GetSubstanceObjects(); }, null); } #if VRC_SDK_VRCSDK2 foreach (VRC_DataStorage ds in Object.FindObjectsOfType <VRC_DataStorage>()) { VRCSDK2.VRC_ObjectSync os = ds.GetComponent <VRCSDK2.VRC_ObjectSync>(); if (os != null && os.SynchronizePhysics) { _builder.OnGUIWarning(scene, ds.name + " has a VRC_DataStorage and VRC_ObjectSync, with SynchronizePhysics enabled.", delegate { Selection.activeObject = os.gameObject; }, null); } } #endif string vrcFilePath = UnityWebRequest.UnEscapeURL(EditorPrefs.GetString("lastVRCPath")); if (!string.IsNullOrEmpty(vrcFilePath) && ValidationHelpers.CheckIfAssetBundleFileTooLarge(ContentType.World, vrcFilePath, out int fileSize)) { _builder.OnGUIWarning(scene, ValidationHelpers.GetAssetBundleOverSizeLimitMessageSDKWarning(ContentType.World, fileSize), null, null); } foreach (GameObject go in Object.FindObjectsOfType <GameObject>()) { if (go.transform.parent == null) { // check root game objects #if UNITY_ANDROID IEnumerable <Shader> illegalShaders = VRCSDK2.Validation.WorldValidation.FindIllegalShaders(go); foreach (Shader s in illegalShaders) { _builder.OnGUIWarning(scene, "World uses unsupported shader '" + s.name + "'. This could cause low performance or future compatibility issues.", null, null); } #endif } else { // Check sibling game objects for (int idx = 0; idx < go.transform.parent.childCount; ++idx) { Transform t = go.transform.parent.GetChild(idx); if (t == go.transform) { continue; } #if VRC_SDK_VRCSDK2 bool allowedType = (t.GetComponent <VRCSDK2.VRC_ObjectSync>() || t.GetComponent <VRCSDK2.VRC_SyncAnimation>() || t.GetComponent <VRC_SyncVideoPlayer>() || t.GetComponent <VRC_SyncVideoStream>()); if (t.name != go.transform.name || allowedType) { continue; } #else if (t.name != go.transform.name) { continue; } #endif string path = t.name; Transform p = t.parent; while (p != null) { path = p.name + "/" + path; p = p.parent; } _builder.OnGUIWarning(scene, "Sibling objects share the same path, which may break network events: " + path, delegate { List <Object> gos = new List <Object>(); for (int c = 0; c < go.transform.parent.childCount; ++c) { if (go.transform.parent.GetChild(c).name == go.name) { gos.Add(go.transform.parent.GetChild(c).gameObject); } } Selection.objects = gos.ToArray(); }, delegate { List <Object> gos = new List <Object>(); for (int c = 0; c < go.transform.parent.childCount; ++c) { if (go.transform.parent.GetChild(c).name == go.name) { gos.Add(go.transform.parent.GetChild(c).gameObject); } } Selection.objects = gos.ToArray(); for (int i = 0; i < gos.Count; ++i) { gos[i].name = gos[i].name + "-" + i.ToString("00"); } }); break; } } } // detect dynamic materials and prefabs with identical names (since these could break triggers) VRC_Trigger[] triggers = Tools.FindSceneObjectsOfTypeAll <VRC_Trigger>(); List <GameObject> prefabs = new List <GameObject>(); List <Material> materials = new List <Material>(); #if VRC_SDK_VRCSDK2 AssetExporter.FindDynamicContent(ref prefabs, ref materials); #elif VRC_SDK_VRCSDK3 AssetExporter.FindDynamicContent(ref prefabs, ref materials); #endif foreach (VRC_Trigger t in triggers) { foreach (VRC_Trigger.TriggerEvent triggerEvent in t.Triggers) { foreach (VRC_EventHandler.VrcEvent e in triggerEvent.Events.Where(evt => evt.EventType == VRC_EventHandler.VrcEventType.SpawnObject)) { GameObject go = AssetDatabase.LoadAssetAtPath(e.ParameterString, typeof(GameObject)) as GameObject; if (go == null) { continue; } foreach (GameObject existing in prefabs) { if (go == existing || go.name != existing.name) { continue; } _builder.OnGUIWarning(scene, "Trigger prefab '" + AssetDatabase.GetAssetPath(go).Replace(".prefab", "") + "' has same name as a prefab in another folder. This may break the trigger.", delegate { Selection.objects = new Object[] { go }; }, delegate { AssetDatabase.RenameAsset(AssetDatabase.GetAssetPath(go), go.name + "-" + go.GetInstanceID()); AssetDatabase.Refresh(); e.ParameterString = AssetDatabase.GetAssetPath(go); }); } } foreach (VRC_EventHandler.VrcEvent e in triggerEvent.Events.Where(evt => evt.EventType == VRC_EventHandler.VrcEventType.SetMaterial)) { Material mat = AssetDatabase.LoadAssetAtPath <Material>(e.ParameterString); if (mat == null || mat.name.Contains("(Instance)")) { continue; } foreach (Material existing in materials) { if (mat == existing || mat.name != existing.name) { continue; } _builder.OnGUIWarning(scene, "Trigger material '" + AssetDatabase.GetAssetPath(mat).Replace(".mat", "") + "' has same name as a material in another folder. This may break the trigger.", delegate { Selection.objects = new Object[] { mat }; }, delegate { AssetDatabase.RenameAsset(AssetDatabase.GetAssetPath(mat), mat.name + "-" + mat.GetInstanceID()); AssetDatabase.Refresh(); e.ParameterString = AssetDatabase.GetAssetPath(mat); }); } } } } }
public override void ValidateFeatures(VRC.SDKBase.VRC_AvatarDescriptor avatar, Animator anim, AvatarPerformanceStats perfStats) { 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); }