How do I Remove null components ( i.e. "Missing(Mono Script)" ) via editor script?

I have made a script somewhat similar to this one: http://answers.unity3d.com/questions/5020/searching-a-project-for-missing-mono-script

But rather than just finding the null entries, I simply want to sweep through and delete them all. By that I mean remove the null references from the GameObjects's list of Components.

The documentation for GameObject.AddComponent notes that there is no such thing as RemoveComponent, requiring you to call Object.Destroy() to remove that entry. This is impossible because there is no object to Destroy.

Creating a new GameObject and adding the components of the old one to it would invalidate any references to it.

Is there a way to do this without laboriously clicking through the UI?

NEW EDIT: (in light of comments below)

Are these references to some funny objects representing a missing script that the overloaded == operator is treating as equal to null? No, because calling DestroyImmediate() does nothing, and trying to actually de-reference the pointer causes a null reference exception. It must be that there are null pointer entries in the list, and the list is not modifiable by editor script.

Here is the demonstration code:

[MenuItem("Custom/Remove Missing Script Entries")]
public static void Do() {
    foreach (Transform t in Selection.transforms) {
        Debug.Log(t.GetComponents(typeof(Component)).Length);
        foreach (Component c in t.GetComponents(typeof(Component))) {
            if (c == null){
                Debug.Log("NULL");
                // throw caution to the wind and destroy anyway!!! AHAHHAHAHAH!!!
                GameObject.DestroyImmediate(c);
                // awwww nothing happened.  still there.
            }
            else
                Debug.Log(c.GetType());
        }
    }
}

Ran into this myself and ended up with a working solution. Only works in the editor of course.

[MenuItem("Edit/Cleanup Missing Scripts")]
static void CleanupMissingScripts ()
{
	for(int i = 0; i < Selection.gameObjects.Length; i++)
	{
		var gameObject = Selection.gameObjects*;*
  •  // We must use the GetComponents array to actually detect missing components*
    
  •  var components = gameObject.GetComponents<Component>();*
    
  •  // Create a serialized object so that we can edit the component list*
    
  •  var serializedObject = new SerializedObject(gameObject);*
    
  •  // Find the component list property*
    
  •  var prop = serializedObject.FindProperty("m_Component");*
    
  •  // Track how many components we've removed*
    
  •  int r = 0;*
    
  •  // Iterate over all components*
    
  •  for(int j = 0; j < components.Length; j++)*
    
  •  {*
    
  •  	// Check if the ref is null*
    
  •  	if(components[j] == null)*
    
  •  	{*
    
  •  		// If so, remove from the serialized component array*
    
  •  		prop.DeleteArrayElementAtIndex(j-r);*
    
  •  		// Increment removed count*
    
  •  		r++;*
    
  •  	}*
    
  •  }*
    
  •  // Apply our changes to the game object*
    
  •  serializedObject.ApplyModifiedProperties();*
    
  • }*
    }

They have added a fix for this in 2019.1

Edit: Code example

[MenuItem("Auto/Remove Missing Scripts Recursively")]
private static void FindAndRemoveMissingInSelected()
{
    var deepSelection = EditorUtility.CollectDeepHierarchy(Selection.gameObjects);
    int compCount = 0;
    int goCount = 0;
    foreach (var o in deepSelection)
    {
        if (o is GameObject go)
        {
            int count = GameObjectUtility.GetMonoBehavioursWithMissingScriptCount(go);
            if (count > 0)
            {
                // Edit: use undo record object, since undo destroy wont work with missing
                Undo.RegisterCompleteObjectUndo(go, "Remove missing scripts");
                GameObjectUtility.RemoveMonoBehavioursWithMissingScript(go);
                compCount += count;
                goCount++;
            }
        }
    }
    Debug.Log($"Found and removed {compCount} missing scripts from {goCount} GameObjects");
}

Edit2: Sorry didn’t see @AlanMattano posted this in a comment a while ago.

Edit3: For this to work on inactive Objects and prefabs, the code is a bit more complicated:

using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEditor;
public static class FindMissingScriptsRecursively
{
    [MenuItem("Auto/Remove Missing Scripts Recursively Visit Prefabs")]
    private static void FindAndRemoveMissingInSelected()
    {
        // EditorUtility.CollectDeepHierarchy does not include inactive children
        var deeperSelection = Selection.gameObjects.SelectMany(go => go.GetComponentsInChildren<Transform>(true))
            .Select(t => t.gameObject);
        var prefabs = new HashSet<Object>();
        int compCount = 0;
        int goCount = 0;
        foreach (var go in deeperSelection)
        {
            int count = GameObjectUtility.GetMonoBehavioursWithMissingScriptCount(go);
            if (count > 0)
            {
                if (PrefabUtility.IsPartOfAnyPrefab(go))
                {
                    RecursivePrefabSource(go, prefabs, ref compCount, ref goCount);
                    count = GameObjectUtility.GetMonoBehavioursWithMissingScriptCount(go);
                    // if count == 0 the missing scripts has been removed from prefabs
                    if (count == 0)
                        continue;
                    // if not the missing scripts must be prefab overrides on this instance
                }

                Undo.RegisterCompleteObjectUndo(go, "Remove missing scripts");
                GameObjectUtility.RemoveMonoBehavioursWithMissingScript(go);
                compCount += count;
                goCount++;
            }
        }

        Debug.Log($"Found and removed {compCount} missing scripts from {goCount} GameObjects");
    }

    // Prefabs can both be nested or variants, so best way to clean all is to go through them all
    // rather than jumping straight to the original prefab source.
    private static void RecursivePrefabSource(GameObject instance, HashSet<Object> prefabs, ref int compCount,
        ref int goCount)
    {
        var source = PrefabUtility.GetCorrespondingObjectFromSource(instance);
        // Only visit if source is valid, and hasn't been visited before
        if (source == null || !prefabs.Add(source))
            return;

        // go deep before removing, to differantiate local overrides from missing in source
        RecursivePrefabSource(source, prefabs, ref compCount, ref goCount);

        int count = GameObjectUtility.GetMonoBehavioursWithMissingScriptCount(source);
        if (count > 0)
        {
            Undo.RegisterCompleteObjectUndo(source, "Remove missing scripts");
            GameObjectUtility.RemoveMonoBehavioursWithMissingScript(source);
            compCount += count;
            goCount++;
        }
    }
}

By using this editor script, you can select all objects that are missing scripts, and then just delete them using the component menu.

Note that this will only work for missing scripts of the same type.

[MenuItem("MyMenu/SelectMissing")]
static void SelectMissing(MenuCommand command)
{
	Transform[] ts = FindObjectsOfType<Transform>();

	List<GameObject> selection = new List<GameObject>();

	foreach(Transform t in ts)
	{
		Component[] cs = t.gameObject.GetComponents<Component>();

		foreach(Component c in cs)
		{
			if (c == null)
			{
				selection.Add(t.gameObject);
			}
		}
	}

	Selection.objects = selection.ToArray();
}

39642-screen-shot-2015-01-27-at-125708-pm.png

Wrote a solution

I believe there is currently no easy solution to do this in scripting. The internal implementation when you pick "Remove Component" from the popup menu is in C++, where all MonoBehaviour components are just belonging to a MonoBehaviour class. Once these are passed to C#, they get the class types of their scripts, or, if the script is empty, then null, which cannot be used to destroy the object.

You might be able to get a pointer reference to the actual MonoBehaviour component which you could call delete on, by iterating through all the GameObjects properties using the SerializedObject class, but that probably takes same time to get to work, if it is possible at all.

Have you actually tried calling "Destroy()" on the value which appears to be null? It may work, due to the slightly unusual way that objects which inherit from Unity's base Object deal with null references.

If calling Destroy isn't working on them, and they are actually a missing reference that is reporting null, could you just try setting the variable to null explicitly to get rid of the missing references? They should be cleaned up by mono if they are storing anything if you're worried about memory leaks.

When you create the object, just be sure to use Object.hideFlags = HideFlags.DontSave;

assuming that is you're creating it program-maticaly.. otherwise, there isn't much you can do if the object is null. it will be cached for later referencing.

Mercilessly short answer:
You can do this in general in an editor script by Instantiating the object (prefab/asset/whatever you wish to call it) temporarily into the currently open scene, making the desired changes to the object, then using the Editor API to save it back out by replacing the prefab (asset). After that, delete the temporary object from the scene.

I’m currently facing a similar issue. I can grab and modify the prefab and all it’s associated behaviors just fine - including adding and removing MonoBehaviors - but any MonoBehaviors with broken refrences come up as null, and trying to remove or destroy them doesn’t work. As far as I can tell, it’s impossible to access these MonoBehaviors from the code. I’ve tried every suggestion so far in this thread, but none of them have worked - Am I stuck going through and removing these by hand?

Thanks a bunch in advance.

I just like to add that if you have Unity pro you can use the yaml text format for scene files. This allows you to edit the scene in a text editor. However it’s still difficult to identify MonoBehaviours with missing components since the component just has a guid field which identifies which script asset was used by this MonoBehaviour.

I think it should be possible to make an editor script that searches for all MonoBehaviours in the scene file and try to match them up with the actual components in Unity. That way you should be able to find the component in the text file and delete it from the scene file manually.

The key is to restore the script to your project so that the script references are valid, then use the code shown in the question to scan all transforms and see if it has the component you wanted removed, and then remove it. Once all your objects are fixed, delete the script from the project.

if(c==MissingReferenceException){c=null;}

Hi, @VildNinja !
I am trying to figuring out how to automatically remove all missing components in prefabs and I found your script here.

I met a strange problem with it when I use it It is actually delete missing components but it produce external object ids in prefabs. So when scriptable build pipeline gather all reference for building by using AssetDatabase.
LoadAllAssetRepresentationsAtPath it return this components as null subasset.
I cannot find a way to delete it except manually parse yaml prefab asset and remove components. As I understand it is happening beacuse of using GameObjectUtility.RemoveMonoBehavioursWithMissingScript.

Link to forum with screenshots.

https://forum.unity.com/threads/remove-all-missing-components-in-prefabs.897761/

Since I am not much expert in editor scripting so I am using a free tool available at Asset store named “Button Inspector”. Using that tool I wrote the below script which go through all the Childs(Active, Inactive) of a parent object and reports them. After reporting if you want to delete missing scripts, they can be done too. But my script does not have Undo/Redo support so keep that in mind. For me, it is working perfectly.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using BasicTools.ButtonInspector;
using UnityEditor;

public class MissingScriptFinder : MonoBehaviour
{
    public GameObject parentElement;
    public Transform[] transformsObtained;
    private bool missingScriptsHasBeenObtained = false;
    private int missingScriptCounter;
    private int missingScriptIntHolder;

    [Button("Report Missing Scripts", "ReportMissingScripts", true)]
    public bool reportMissingScripts;

    [Button("Remove Missing Scripts", "RemoveMissingScripts", true)]
    public bool removeMissingScripts;

    [Button("Clear Array", "ClearArray", true)]
    public bool clearArray;

    public void ReportMissingScripts()
    {
        missingScriptsHasBeenObtained = true;
        transformsObtained = new Transform[0];
        transformsObtained = parentElement.GetComponentsInChildren<Transform>(true);
        foreach(Transform t in transformsObtained)
        {
            missingScriptIntHolder = GameObjectUtility.GetMonoBehavioursWithMissingScriptCount(t.gameObject);
            missingScriptCounter += missingScriptIntHolder;
            if(missingScriptIntHolder == 1)
            {
                Debug.Log(t.name + " contains a missing script.");
            }
            
        }
        Debug.Log("There are total " + missingScriptCounter + " missing/null components.");
    }

    public void RemoveMissingScripts()
    {
        if(!missingScriptsHasBeenObtained)
        {
            Debug.LogWarning("Kindly first Report Missing Scripts by clicking on Report Missing Scripts button.");
        }
        else
        {
            if (missingScriptCounter == 0)
            {
                Debug.Log("There are no missing/null scripts.");
            }
            else
            {
                foreach (Transform t in transformsObtained)
                {

                    GameObjectUtility.RemoveMonoBehavioursWithMissingScript(t.gameObject);
                }
                Debug.Log("All missing/null scripts has been destroyed.");
            }
        }

        missingScriptCounter = 0;
        missingScriptsHasBeenObtained = false;
    }

    public void ClearArray()
    {
        transformsObtained = new Transform[0];
        missingScriptCounter = 0;
        missingScriptsHasBeenObtained = false;
    }
}

Wrote a fix that works Unity 2020.1
NOTE : Works for Prefabs found at a given Path, but can be easily adapted for gameobjects in a scene.
Heavilly inspired from Clean up all missing scripts in the prefab under the project directory - Programmer Sought (thanks!)

using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
public static class Tool_RemoveMissingComponent
{
    /// <summary>
    /// DOES : 
    /// Remove missing scripts in prefabs found at PATH, then save prefab.
    /// Saved prefab will have no missing scripts left.
    /// Will not mod prefabs that dont have missing scripts.
    /// 
    /// NOTE : 
    /// If prefab has another prefab#2 that is not in PATH, that prefab#2 will still have missing scripts.
    /// The instance of the prefab#2 in prefab will not have missing scripts (thus counted has override of prefab#2)
    /// 
    /// HOW TO USE :
    /// Copy code in script anywhere in project.
    /// Set the PATH var in method <see cref="RemoveMissingScripstsInPrefabsAtPath"/>.
    /// Clik the button.
    /// </summary>

    [MenuItem("Tools/FixingStuff/Remove MissingComponents in Prefabs at Path")]
    public static void RemoveMissingScripstsInPrefabsAtPath()
    {
        string PATH = "Assets/Prefabs";

        EditorUtility.DisplayProgressBar("Modify Prefab", "Please wait...", 0);
        string[] ids = AssetDatabase.FindAssets("t:Prefab", new string[] { PATH});
        for (int i = 0; i < ids.Length; i++)
        {
            string path = AssetDatabase.GUIDToAssetPath(ids*);*

GameObject prefab = AssetDatabase.LoadAssetAtPath(path, typeof(GameObject)) as GameObject;
GameObject instance = PrefabUtility.InstantiatePrefab(prefab) as GameObject;

int delCount = 0;
RecursivelyModifyPrefabChilds(instance, ref delCount);

if (delCount > 0)
{
Debug.Log($“Removed({delCount}) on {path}”, prefab);
PrefabUtility.SaveAsPrefabAssetAndConnect(instance, path, InteractionMode.AutomatedAction);
}

UnityEngine.Object.DestroyImmediate(instance);
EditorUtility.DisplayProgressBar(“Modify Prefab”, “Please wait…”, i / (float)ids.Length);
}
AssetDatabase.SaveAssets();
EditorUtility.ClearProgressBar();
}

private static void RecursivelyModifyPrefabChilds(GameObject obj, ref int delCount)
{
if (obj.transform.childCount > 0)
{
for (int i = 0; i < obj.transform.childCount; i++)
{
var _childObj = obj.transform.GetChild(i).gameObject;
RecursivelyModifyPrefabChilds(_childObj, ref delCount);
}
}

int innerDelCount = GameObjectUtility.RemoveMonoBehavioursWithMissingScript(obj);
delCount += innerDelCount;
}

}

#endif
,Wrote a fix that works Unity 2020.1
NOTE : Works for Prefabs found at a given Path, but can be easily adapted for gameobjects in a scene.
Heavilly inspired from Clean up all missing scripts in the prefab under the project directory - Programmer Sought (thanks!)
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
public static class Tool_RemoveMissingComponent
{
///


/// DOES :
/// Remove missing scripts in prefabs found at PATH, then save prefab.
/// Saved prefab will have no missing scripts left.
/// Will not mod prefabs that dont have missing scripts.
///
/// NOTE :
/// If prefab has another prefab#2 that is not in PATH, that prefab#2 will still have missing scripts.
/// The instance of the prefab#2 in prefab will not have missing scripts (thus counted has override of prefab#2)
///
/// HOW TO USE :
/// Copy code in script anywhere in project.
/// Set the PATH var in method .
/// Clik the button.
///

[MenuItem(“Tools/FixingStuff/Remove MissingComponents in Prefabs at Path”)]
public static void RemoveMissingScripstsInPrefabsAtPath()
{
string PATH = “Assets/Prefabs”;

EditorUtility.DisplayProgressBar(“Modify Prefab”, “Please wait…”, 0);
string[] ids = AssetDatabase.FindAssets(“t:Prefab”, new string[] { PATH});
for (int i = 0; i < ids.Length; i++)
{
string path = AssetDatabase.GUIDToAssetPath(ids*);*
GameObject prefab = AssetDatabase.LoadAssetAtPath(path, typeof(GameObject)) as GameObject;
GameObject instance = PrefabUtility.InstantiatePrefab(prefab) as GameObject;

int delCount = 0;
RecursivelyModifyPrefabChilds(instance, ref delCount);

if (delCount > 0)
{
Debug.Log($“Removed({delCount}) on {path}”, prefab);
PrefabUtility.SaveAsPrefabAssetAndConnect(instance, path, InteractionMode.AutomatedAction);
}

UnityEngine.Object.DestroyImmediate(instance);
EditorUtility.DisplayProgressBar(“Modify Prefab”, “Please wait…”, i / (float)ids.Length);
}
AssetDatabase.SaveAssets();
EditorUtility.ClearProgressBar();
}

private static void RecursivelyModifyPrefabChilds(GameObject obj, ref int delCount)
{
if (obj.transform.childCount > 0)
{
for (int i = 0; i < obj.transform.childCount; i++)
{
var _childObj = obj.transform.GetChild(i).gameObject;
RecursivelyModifyPrefabChilds(_childObj, ref delCount);
}
}

int innerDelCount = GameObjectUtility.RemoveMonoBehavioursWithMissingScript(obj);
delCount += innerDelCount;
}

}

#endif

I am getting this error

It is not allowed to modify the data property
UnityEditor.SerializedProperty:DeleteArrayElementAtIndex (int)
FLGCoreEditor.Utilities.FindMissingScriptsRecursivelyAndRemove:FindInGo (UnityEngine.GameObject) (at Assets/Plugins/missingscriptt.cs:77)
FLGCoreEditor.Utilities.FindMissingScriptsRecursivelyAndRemove:FindInSelected () (at Assets/Plugins/missingscriptt.cs:42)
FLGCoreEditor.Utilities.FindMissingScriptsRecursivelyAndRemove:OnGUI () (at Assets/Plugins/missingscriptt.cs:24)
UnityEngine.GUIUtility:ProcessEvent (int,intptr,bool&)