public static bool FixGameObjectIssue(GameObjectIssueRecord issue, GameObject go, Component component, RecordType type) { bool result = false; if (type == RecordType.MissingComponent) { bool hasIssue = GameObjectHasMissingComponent(go); if (hasIssue) { FixMissingComponents(issue, go); result = !GameObjectHasMissingComponent(go); } else { Debug.LogWarning(Maintainer.LOG_PREFIX + "Looks like this issue was already fixed:\n" + issue); } } else if (type == RecordType.MissingReference) { result = FixMissingReference(issue, component); } return(result); }
public static bool FixObjectIssue(GameObjectIssueRecord issue, Object obj, Component component, IssueKind type) { var result = false; if (type == IssueKind.MissingComponent) { var hasIssue = GameObjectHasMissingComponent(obj as GameObject); if (hasIssue) { FixMissingComponents(issue, obj as GameObject, false); result = !GameObjectHasMissingComponent(obj as GameObject); } else { result = true; } } else if (type == IssueKind.MissingReference) { if (component != null) { result = FixMissingReference(component, issue.propertyPath, issue.Location); } else { result = FixMissingReference(obj, issue.propertyPath, issue.Location); } } return(result); }
public static FixResult FixObjectIssue(GameObjectIssueRecord issue, Object obj, Component component, IssueKind type) { FixResult result; if (type == IssueKind.MissingComponent) { var go = (GameObject)obj; var hasIssue = GameObjectHasMissingComponent(go); if (hasIssue) { if (PrefabUtility.IsPartOfPrefabAsset(go)) { var allTransforms = go.transform.root.GetComponentsInChildren <Transform>(true); foreach (var child in allTransforms) { FixMissingComponents(issue, child.gameObject, false); } } else { FixMissingComponents(issue, go, false); } if (!GameObjectHasMissingComponent(go)) { result = new FixResult(true); } else { result = FixResult.CreateError("Fix attempt failed!"); } } else { result = new FixResult(true); } } else if (type == IssueKind.MissingReference) { result = FixMissingReference(component != null ? component : obj, issue.propertyPath, issue.Location); } else { result = FixResult.CreateError("IssueKind is not supported!"); } return(result); }
public static FixResult FixObjectIssue(GameObjectIssueRecord issue, Object obj, Component component, IssueKind type) { FixResult result; if (type == IssueKind.MissingComponent) { var hasIssue = GameObjectHasMissingComponent(obj as GameObject); if (hasIssue) { FixMissingComponents(issue, obj as GameObject, false); if (!GameObjectHasMissingComponent(obj as GameObject)) { result = new FixResult(true); } else { result = FixResult.CreateError("Fix attempt failed!"); } } else { result = new FixResult(true); } } else if (type == IssueKind.MissingReference) { if (component != null) { result = FixMissingReference(component, issue.propertyPath, issue.Location); } else { result = FixMissingReference(obj, issue.propertyPath, issue.Location); } } else { result = FixResult.CreateError("IssueKind is not supported!"); } return(result); }
// ---------------------------------------------------------------------------- // fix missing component // ---------------------------------------------------------------------------- private static void FixMissingComponents(GameObjectIssueRecord issue, GameObject go) { CSObjectTools.SelectGameObject(go, issue.location); ActiveEditorTracker tracker = CSEditorTools.GetActiveEditorTrackerForSelectedObject(); tracker.RebuildIfNecessary(); bool touched = false; Editor[] activeEditors = tracker.activeEditors; for (int i = activeEditors.Length - 1; i >= 0; i--) { Editor editor = activeEditors[i]; if (CSObjectTools.GetLocalIdentifierInFileForObject(editor.serializedObject.targetObject) == issue.componentId) { Object.DestroyImmediate(editor.target, true); touched = true; } } if (touched) { #if UNITY_5_0_PLUS if (issue.location == RecordLocation.Scene) { CSSceneTools.MarkSceneDirty(); } else { EditorUtility.SetDirty(go); } #else EditorUtility.SetDirty(go); #endif } //CSObjectTools.SelectGameObject(null, issue.location); }
// ---------------------------------------------------------------------------- // fix missing reference // ---------------------------------------------------------------------------- private static bool FixMissingReference(GameObjectIssueRecord issue, Component component) { SerializedObject so = new SerializedObject(component); SerializedProperty sp = so.FindProperty(issue.propertyPath); if (sp.propertyType == SerializedPropertyType.ObjectReference) { if (sp.objectReferenceValue == null && sp.objectReferenceInstanceIDValue != 0) { sp.objectReferenceInstanceIDValue = 0; #if UNITY_5_2_PLUS // fixes dirty scene flag after batch issues fix // due to the additional undo action so.ApplyModifiedPropertiesWithoutUndo(); #else so.ApplyModifiedProperties(); #endif #if UNITY_5_0_PLUS if (issue.location == RecordLocation.Scene) { CSSceneTools.MarkSceneDirty(); } else { EditorUtility.SetDirty(component); } #else EditorUtility.SetDirty(component); #endif } } return(true); }
// ---------------------------------------------------------------------------- // fix missing component // ---------------------------------------------------------------------------- private static bool FixMissingComponents(GameObjectIssueRecord issue, GameObject go, bool alternative) { var touched = false; #if UNITY_2019_1_OR_NEWER var removedCount = GameObjectUtility.RemoveMonoBehavioursWithMissingScript(go); if (removedCount > 0) { touched = true; } #else if (!alternative) { CSObjectTools.SelectGameObject(go, issue.Location == RecordLocation.Scene); } var tracker = CSEditorTools.GetActiveEditorTrackerForSelectedObject(); if (tracker == null) { Debug.LogError(Maintainer.ConstructError("Can't get active tracker.")); return(false); } tracker.RebuildIfNecessary(); var activeEditors = tracker.activeEditors; for (var i = activeEditors.Length - 1; i >= 0; i--) { var editor = activeEditors[i]; if (editor.serializedObject.targetObject == null) { Object.DestroyImmediate(editor.target, true); touched = true; } } if (alternative) { return(touched); } if (!touched) { // missing script could be hidden with hide flags, so let's try select it directly and repeat var serializedObject = new SerializedObject(go); var componentsArray = serializedObject.FindProperty("m_Component"); if (componentsArray != null) { for (var i = componentsArray.arraySize - 1; i >= 0; i--) { var componentPair = componentsArray.GetArrayElementAtIndex(i); var nestedComponent = componentPair.FindPropertyRelative("component"); if (nestedComponent != null) { if (MissingReferenceDetector.IsPropertyHasMissingReference(nestedComponent)) { var instanceId = nestedComponent.objectReferenceInstanceIDValue; if (instanceId == 0) { var fileId = nestedComponent.FindPropertyRelative("m_FileID"); if (fileId != null) { instanceId = fileId.intValue; } } Selection.instanceIDs = new [] { instanceId }; touched |= FixMissingComponents(issue, go, true); } } else { Debug.LogError(Maintainer.LogPrefix + "Couldn't find component in component pair!"); break; } } if (touched) { CSObjectTools.SelectGameObject(go, issue.Location == RecordLocation.Scene); } } else { Debug.LogError(Maintainer.LogPrefix + "Couldn't find components array!"); } } #endif if (touched) { if (issue.Location == RecordLocation.Scene) { CSSceneTools.MarkSceneDirty(); } else { EditorUtility.SetDirty(go); } } return(touched); }
// ----------------------------------------------------------------------------- // fix missing component // ----------------------------------------------------------------------------- private static bool FixMissingComponents(GameObjectIssueRecord issue, GameObject go, bool alternative) { var touched = false; // TODO: re-check in Unity 2021 // unfortunately RemoveMonoBehavioursWithMissingScript does not works correctly: // https://forum.unity.com/threads/remove-all-missing-components-in-prefabs.897761/ // so it will be enabled back in later Unity versions /*var removedCount = GameObjectUtility.RemoveMonoBehavioursWithMissingScript(go); * if (removedCount > 0) * { * touched = true; * }*/ if (!alternative) { CSObjectTools.SelectGameObject(go, issue.Location == RecordLocation.Scene); } var tracker = CSEditorTools.GetActiveEditorTrackerForSelectedObject(); if (tracker == null) { Debug.LogError(Maintainer.ConstructError("Can't get active tracker.")); return(false); } tracker.RebuildIfNecessary(); var activeEditors = tracker.activeEditors; for (var i = activeEditors.Length - 1; i >= 0; i--) { var editor = activeEditors[i]; if (editor.serializedObject.targetObject == null) { Object.DestroyImmediate(editor.target, true); touched = true; } } if (alternative) { return(touched); } if (!touched) { // missing script could be hidden with hide flags, so let's try select it directly and repeat var serializedObject = new SerializedObject(go); var componentsArray = serializedObject.FindProperty("m_Component"); if (componentsArray != null) { for (var i = componentsArray.arraySize - 1; i >= 0; i--) { var componentPair = componentsArray.GetArrayElementAtIndex(i); var nestedComponent = componentPair.FindPropertyRelative("component"); if (nestedComponent != null) { if (MissingReferenceDetector.IsPropertyHasMissingReference(nestedComponent)) { var instanceId = nestedComponent.objectReferenceInstanceIDValue; if (instanceId == 0) { var fileId = nestedComponent.FindPropertyRelative("m_FileID"); if (fileId != null) { instanceId = fileId.intValue; } } Selection.instanceIDs = new [] { instanceId }; touched |= FixMissingComponents(issue, go, true); } } else { Debug.LogError(Maintainer.LogPrefix + "Couldn't find component in component pair!"); break; } } if (touched) { CSObjectTools.SelectGameObject(go, issue.Location == RecordLocation.Scene); } } else { Debug.LogError(Maintainer.LogPrefix + "Couldn't find components array!"); } } if (touched) { if (issue.Location == RecordLocation.Scene) { CSSceneTools.MarkSceneDirty(); } else { EditorUtility.SetDirty(go); } } return(touched); }
private static void CheckObjectForIssues(List <IssueRecord> issues, string path, GameObject go, bool checkingScene) { RecordLocation location = checkingScene ? RecordLocation.Scene : RecordLocation.Prefab; // ---------------------------------------------------------------------------- // looking for object-level issues // ---------------------------------------------------------------------------- if (!MaintainerSettings.Issues.touchInactiveGameObjects) { if (checkingScene) { if (!go.activeInHierarchy) { return; } } else { if (!go.activeSelf) { return; } } } // ---------------------------------------------------------------------------- // checking all components for ignores // ---------------------------------------------------------------------------- bool checkForIgnores = (MaintainerSettings.Issues.componentIgnores != null && MaintainerSettings.Issues.componentIgnores.Length > 0); bool skipEmptyMeshFilter = false; bool skipEmptyAudioSource = false; Component[] allComponents = go.GetComponents <Component>(); int allComponentsCount = allComponents.Length; List <Component> components = new List <Component>(allComponentsCount); List <Type> componentsTypes = new List <Type>(allComponentsCount); List <string> componentsNames = new List <string>(allComponentsCount); List <string> componentsFullNames = new List <string>(allComponentsCount); List <string> componentsNamespaces = new List <string>(allComponentsCount); int componentsCount = 0; for (int i = 0; i < allComponentsCount; i++) { Component component = allComponents[i]; if (component == null) { if (MaintainerSettings.Issues.missingComponents) { issues.Add(GameObjectIssueRecord.Create(RecordType.MissingComponent, location, path, go)); } continue; } Type componentType = component.GetType(); string componentName = componentType.Name; string componentFullName = componentType.FullName; string componentNamespace = componentType.Namespace; /* * checking object for the components which may affect * other components and produce false positives */ // allowing empty mesh filters for the objects with attached TextMeshPro and 2D Toolkit components. if (!skipEmptyMeshFilter) { skipEmptyMeshFilter = (componentFullName == "TMPro.TextMeshPro") || componentName.StartsWith("tk2d"); } // allowing empty AudioSources for the objects with attached standard FirstPersonController. if (!skipEmptyAudioSource) { skipEmptyAudioSource = componentFullName == "UnityStandardAssets.Characters.FirstPerson.FirstPersonController"; } // skipping disabled components if (!MaintainerSettings.Issues.touchDisabledComponents) { if (EditorUtility.GetObjectEnabled(component) == 0) { continue; } } // skipping ignored components if (checkForIgnores) { if (Array.IndexOf(MaintainerSettings.Issues.componentIgnores, componentName) != -1) { continue; } } components.Add(component); componentsTypes.Add(componentType); componentsNames.Add(componentName); componentsFullNames.Add(componentFullName); componentsNamespaces.Add(componentNamespace); componentsCount++; } // ---------------------------------------------------------------------------- // checking stuff related to the prefabs in scenes // ---------------------------------------------------------------------------- if (checkingScene) { PrefabType prefabType = PrefabUtility.GetPrefabType(go); if (prefabType != PrefabType.None) { /* checking if we're inside of nested prefab with same type as root, * allows to skip detections of missed and disconnected prefabs children */ GameObject rootPrefab = PrefabUtility.FindRootGameObjectWithSameParentPrefab(go); bool rootPrefabHasSameType = false; if (rootPrefab != go) { PrefabType rootPrefabType = PrefabUtility.GetPrefabType(rootPrefab); if (rootPrefabType == prefabType) { rootPrefabHasSameType = true; } } if (prefabType == PrefabType.MissingPrefabInstance) { if (MaintainerSettings.Issues.missingPrefabs && !rootPrefabHasSameType) { issues.Add(GameObjectIssueRecord.Create(RecordType.MissingPrefab, location, path, go)); } } else if (prefabType == PrefabType.DisconnectedPrefabInstance || prefabType == PrefabType.DisconnectedModelPrefabInstance) { if (MaintainerSettings.Issues.disconnectedPrefabs && !rootPrefabHasSameType) { issues.Add(GameObjectIssueRecord.Create(RecordType.DisconnectedPrefab, location, path, go)); } } /* checking if this game object is actually prefab instance * without any changes, so we can skip it if we have assets search enabled */ if (prefabType != PrefabType.DisconnectedPrefabInstance && prefabType != PrefabType.DisconnectedModelPrefabInstance && prefabType != PrefabType.MissingPrefabInstance && MaintainerSettings.Issues.lookInAssets) { bool skipThisPrefabInstance = true; // we shouldn't skip object if it's nested deeper 2nd level if (CSEditorTools.GetDepthInHierarchy(go.transform, rootPrefab.transform) >= 2) { skipThisPrefabInstance = false; } else { PropertyModification[] modifications = PrefabUtility.GetPropertyModifications(go); foreach (PropertyModification modification in modifications) { Object target = modification.target; if (target is Transform) { continue; } skipThisPrefabInstance = false; break; } } if (skipThisPrefabInstance) { return; } } } } if (MaintainerSettings.Issues.undefinedTags) { bool undefinedTag = false; try { if (string.IsNullOrEmpty(go.tag)) { undefinedTag = true; } } catch (UnityException e) { if (e.Message.Contains("undefined tag")) { undefinedTag = true; } else { Debug.LogError(Maintainer.LOG_PREFIX + "Unknown error while checking tag of the " + go.name + "\n" + e); } } if (undefinedTag) { issues.Add(GameObjectIssueRecord.Create(RecordType.UndefinedTag, location, path, go)); } } if (MaintainerSettings.Issues.unnamedLayers) { int layerIndex = go.layer; if (string.IsNullOrEmpty(LayerMask.LayerToName(layerIndex))) { GameObjectIssueRecord issue = GameObjectIssueRecord.Create(RecordType.UnnamedLayer, location, path, go); issue.headerExtra = "(index: " + layerIndex + ")"; issues.Add(issue); } } Dictionary <Type, int> uniqueTypes = null; List <int> similarComponentsIndexes = null; TerrainData terrainTerrainData = null; TerrainData terrainColliderTerrainData = null; bool terrainChecked = false; bool terrainColliderChecked = false; // ---------------------------------------------------------------------------- // looking for component-level issues // ---------------------------------------------------------------------------- for (int i = 0; i < componentsCount; i++) { Component component = components[i]; Type componentType = componentsTypes[i]; string componentName = componentsNames[i]; //string componentFullName = componentsFullNames[i]; string componentNamespace = componentsNamespaces[i]; if (component is Transform) { if (MaintainerSettings.Issues.hugePositions) { Vector3 position = (component as Transform).position; if (Math.Abs(position.x) > 100000f || Math.Abs(position.y) > 100000f || Math.Abs(position.z) > 100000f) { issues.Add(GameObjectIssueRecord.Create(RecordType.HugePosition, location, path, go, component, componentType, componentName, "Position")); } } continue; } if (MaintainerSettings.Issues.duplicateComponents && (componentNamespace != "Fabric")) { // initializing dictionary and list on first usage if (uniqueTypes == null) { uniqueTypes = new Dictionary <Type, int>(componentsCount); } if (similarComponentsIndexes == null) { similarComponentsIndexes = new List <int>(componentsCount); } // checking if current component type already met before if (uniqueTypes.ContainsKey(componentType)) { int uniqueTypeIndex = uniqueTypes[componentType]; // checking if initially met component index already in indexes list // since we need to compare all duplicate candidates against initial component if (!similarComponentsIndexes.Contains(uniqueTypeIndex)) { similarComponentsIndexes.Add(uniqueTypeIndex); } // adding current component index to the indexes list similarComponentsIndexes.Add(i); } else { uniqueTypes.Add(componentType, i); } } if (component is MeshCollider) { if (MaintainerSettings.Issues.emptyMeshColliders) { if ((component as MeshCollider).sharedMesh == null) { issues.Add(GameObjectIssueRecord.Create(RecordType.EmptyMeshCollider, location, path, go, component, componentType, componentName)); } } } else if (component is MeshFilter) { if (MaintainerSettings.Issues.emptyMeshFilters && !skipEmptyMeshFilter) { if ((component as MeshFilter).sharedMesh == null) { issues.Add(GameObjectIssueRecord.Create(RecordType.EmptyMeshFilter, location, path, go, component, componentType, componentName)); } } } else if (component is Renderer) { if (MaintainerSettings.Issues.emptyRenderers) { if ((component as Renderer).sharedMaterial == null) { issues.Add(GameObjectIssueRecord.Create(RecordType.EmptyRenderer, location, path, go, component, componentType, componentName)); } } if (component is SpriteRenderer) { if (MaintainerSettings.Issues.emptySpriteRenderers) { if ((component as SpriteRenderer).sprite == null) { issues.Add(GameObjectIssueRecord.Create(RecordType.EmptySpriteRenderer, location, path, go, component, componentType, componentName)); } } } } else if (component is Animation) { if (MaintainerSettings.Issues.emptyAnimations) { if ((component as Animation).GetClipCount() <= 0 && (component as Animation).clip == null) { issues.Add(GameObjectIssueRecord.Create(RecordType.EmptyAnimation, location, path, go, component, componentType, componentName)); } } } else if (component is Terrain) { if (MaintainerSettings.Issues.inconsistentTerrainData) { terrainTerrainData = (component as Terrain).terrainData; terrainChecked = true; } } else if (component is TerrainCollider) { if (MaintainerSettings.Issues.inconsistentTerrainData) { terrainColliderTerrainData = (component as TerrainCollider).terrainData; terrainColliderChecked = true; } if (MaintainerSettings.Issues.emptyTerrainCollider) { if ((component as TerrainCollider).terrainData == null) { issues.Add(GameObjectIssueRecord.Create(RecordType.EmptyTerrainCollider, location, path, go, component, componentType, componentName)); } } } else if (component is AudioSource) { if (MaintainerSettings.Issues.emptyAudioSource && !skipEmptyAudioSource) { if ((component as AudioSource).clip == null) { issues.Add(GameObjectIssueRecord.Create(RecordType.EmptyAudioSource, location, path, go, component, componentType, componentName)); } } } // ---------------------------------------------------------------------------- // looping through the component's SerializedProperties via SerializedObject // ---------------------------------------------------------------------------- Dictionary <string, int> emptyArrayItems = new Dictionary <string, int>(); SerializedObject so = new SerializedObject(component); SerializedProperty sp = so.GetIterator(); while (sp.NextVisible(true)) { string fullPropertyPath = sp.propertyPath; bool isArrayItem = fullPropertyPath.EndsWith("]", StringComparison.Ordinal); if (MaintainerSettings.Issues.missingReferences) { if (sp.propertyType == SerializedPropertyType.ObjectReference) { if (sp.objectReferenceValue == null && sp.objectReferenceInstanceIDValue != 0) { string propertyName = isArrayItem ? GetArrayItemNameAndIndex(fullPropertyPath) : sp.name; issues.Add(GameObjectIssueRecord.Create(RecordType.MissingReference, location, path, go, component, componentType, componentName, propertyName)); } } } if (checkingScene || !MaintainerSettings.Issues.skipEmptyArrayItemsOnPrefabs) { // skipping SpriteRenderer as it has hidden array with materials of size 1 if (MaintainerSettings.Issues.emptyArrayItems && isArrayItem) { // ignoring components where empty array items is a normal behavior if (!(component is SpriteRenderer) && !componentName.StartsWith("TextMeshPro")) { if (sp.propertyType == SerializedPropertyType.ObjectReference && sp.objectReferenceValue == null && sp.objectReferenceInstanceIDValue == 0) { string arrayName = GetArrayItemName(fullPropertyPath); // ignoring TextMeshPro's FontAssetArrays with 16 empty items inside if (!emptyArrayItems.ContainsKey(arrayName)) { emptyArrayItems.Add(arrayName, 0); } emptyArrayItems[arrayName]++; } } } } /*else * { * continue; * }*/ } if (MaintainerSettings.Issues.emptyArrayItems) { foreach (var item in emptyArrayItems.Keys) { GameObjectIssueRecord issueRecord = GameObjectIssueRecord.Create(RecordType.EmptyArrayItem, location, path, go, component, componentType, componentName, item); issueRecord.headerFormatArgument = emptyArrayItems[item].ToString(); issues.Add(issueRecord); } } } if (MaintainerSettings.Issues.inconsistentTerrainData && terrainColliderTerrainData != terrainTerrainData && terrainChecked && terrainColliderChecked) { issues.Add(GameObjectIssueRecord.Create(RecordType.InconsistentTerrainData, location, path, go)); } if (MaintainerSettings.Issues.duplicateComponents) { if (similarComponentsIndexes != null && similarComponentsIndexes.Count > 0) { int similarComponentsCount = similarComponentsIndexes.Count; List <long> similarComponentsHashes = new List <long>(similarComponentsCount); for (int i = 0; i < similarComponentsCount; i++) { int componentIndex = similarComponentsIndexes[i]; Component component = components[componentIndex]; long componentHash = 0; if (MaintainerSettings.Issues.duplicateComponentsPrecise) { SerializedObject so = new SerializedObject(component); SerializedProperty sp = so.GetIterator(); while (sp.NextVisible(true)) { componentHash += CSEditorTools.GetPropertyHash(sp); } } similarComponentsHashes.Add(componentHash); } List <long> distinctItems = new List <long>(similarComponentsCount); for (int i = 0; i < similarComponentsCount; i++) { int componentIndex = similarComponentsIndexes[i]; if (distinctItems.Contains(similarComponentsHashes[i])) { issues.Add(GameObjectIssueRecord.Create(RecordType.DuplicateComponent, location, path, go, components[componentIndex], componentsTypes[componentIndex], componentsNames[componentIndex])); } else { distinctItems.Add(similarComponentsHashes[i]); } } } } }