Remove all missing components in prefabs

Hi!
I am trying to figuring out how to automatically remove all missing components in prefabs.
I found out script here: https://answers.unity.com/questions/15225/how-do-i-remove-null-components-ie-missingmono-scr.html?childToView=1614734#answer-1614734

Here is the problem 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.

Few screenshots to see the problem.

  1. Prefab after using script.
    5895896--628856--upload_2020-5-25_14-47-18.png

  2. External ids appeared
    5895896--628859--upload_2020-5-25_14-47-40.png

3.Here my missing component that was added after removing by script. If I delete it will fix the problem.

  1. Part of diff after using script.

So tried to use this script for clean null subassets afterwards: Deleting null sub-assets
I modified to work with prefabs. It can delete null subassets but it cause all guids and ids inside prefab to regenerate because it use magic workaround. So I am not usre how stable it will work with nested and variants. I also see that some other objects have lost links to my prefabs.

        private static void FixMissingScript(Object toDelete)
        {
            //Create a new instance of the object to delete
            var type = toDelete.GetType();
            Object newInstance;
            if(type == typeof(ScriptableObject))
                newInstance = CreateInstance(toDelete.GetType());
            else if (type == typeof(GameObject) && PrefabUtility.IsPartOfPrefabAsset(toDelete))
            {
                newInstance = Instantiate(toDelete);
            }
            else
            {
                return;
            }

            //Copy the original content to the new instance
            EditorUtility.CopySerialized(toDelete, newInstance);
            newInstance.name = toDelete.name;

            string toDeletePath = AssetDatabase.GetAssetPath(toDelete);
            string clonePath = "";
         
            if(type == typeof(ScriptableObject))
                clonePath = toDeletePath.Replace(".asset", "CLONE.asset");
            else if (type == typeof(GameObject) && toDeletePath.Contains(".prefab") && PrefabUtility.IsPartOfPrefabAsset(toDelete))
            {
                clonePath = toDeletePath.Replace(".prefab", "CLONE.prefab");
            }
            else
                return;

            //Create the new asset on the project files
            if (type == typeof(ScriptableObject))
            {
                AssetDatabase.CreateAsset(newInstance, clonePath);
            }
            else if (type == typeof(GameObject) &&PrefabUtility.IsPartOfPrefabAsset(toDelete))
            {
                PrefabUtility.SaveAsPrefabAsset(newInstance as GameObject, clonePath, out var success);
                if(!success)
                    Debug.LogError($"cannot fix {newInstance.name}");
            }

            AssetDatabase.ImportAsset(clonePath);

            //Unhide sub-assets
            var subAssets = AssetDatabase.LoadAllAssetsAtPath(toDeletePath);
            HideFlags[] flags = new HideFlags[subAssets.Length];
            for (int i = 0; i < subAssets.Length; i++)
            {
                //Ignore the "corrupt" one
                if (subAssets[i] == null)
                    continue;

                //Store the previous hide flag
                flags[i] = subAssets[i].hideFlags;
                subAssets[i].hideFlags = HideFlags.None;
                EditorUtility.SetDirty(subAssets[i]);
            }

            EditorUtility.SetDirty(toDelete);
            AssetDatabase.SaveAssets();

            //Reparent the subAssets to the new instance
            foreach (var subAsset in AssetDatabase.LoadAllAssetRepresentationsAtPath(toDeletePath))
            {
                //Ignore the "corrupt" one
                if (subAsset == null)
                    continue;

                //We need to remove the parent before setting a new one
                AssetDatabase.RemoveObjectFromAsset(subAsset);
                AssetDatabase.AddObjectToAsset(subAsset, newInstance);
            }

            //Import both assets back to unity
            AssetDatabase.ImportAsset(toDeletePath);
            AssetDatabase.ImportAsset(clonePath);

            //Reset sub-asset flags
            for (int i = 0; i < subAssets.Length; i++)
            {
                //Ignore the "corrupt" one
                if (subAssets[i] == null)
                    continue;

                subAssets[i].hideFlags = flags[i];
                EditorUtility.SetDirty(subAssets[i]);
            }

            EditorUtility.SetDirty(newInstance);
            AssetDatabase.SaveAssets();

            //Here's the magic. First, we need the system path of the assets
            string globalToDeletePath = System.IO.Path.Combine(System.IO.Path.GetDirectoryName(Application.dataPath), toDeletePath);
            string globalClonePath = System.IO.Path.Combine(System.IO.Path.GetDirectoryName(Application.dataPath), clonePath);

            //We need to delete the original file (the one with the missing script asset)
            //Rename the clone to the original file and finally
            //Delete the meta file from the clone since it no longer exists

            System.IO.File.Delete(globalToDeletePath);
            System.IO.File.Delete(globalClonePath + ".meta");
            System.IO.File.Move(globalClonePath, globalToDeletePath);

            AssetDatabase.Refresh();
        }

For now I cannot find a way to do it except manually delete missing components in prefab yaml after using missing script cleaner.

1 Like

I think it may be a bug so I changed title to it.

1 Like

Poping up a question

Looking at the file you posted above, this is not about a GameObject component, this is a ScriptableObject embedded in the prefab asset.
I am not sure how to get rid of those if the script is missing. I will have to do some investigation.

1 Like

Ok, but why it was there? I did not have any embedded ScriptableObject in this prefab. It appeared when I used this method.

Few screenshots of diff after script usage:


Ohhh I see now, so you used RemoveMonoBehaviour… which did remove the MB from the GameObject but it didn’t actually destroy the object and it became detached.

I will have a look at this.

2 Likes

Yes, thank you!

@SteenLund Is this bug fixed in the latest version?

@huang9012

No, a fix is coming.

3 Likes

I hit this too. As a work around until RemoveMonoBehavioursWithMissingScript gets fixed, if you just instantiate a prefab in the scene (PrefabUtility.InstantiatePrefab) then remove the missing scripts from the instance, then save over the previous prefab (PrefabUtility.SaveAsPrefabAsset) it seems to remove the missing components from the prefab file without the extra null sub-assets that appear when calling RemoveMonoBehavioursWithMissingScript directly on the prefab.

4 Likes

Any news on this ?

@amynox

The fix landed in the beginning of August and thus is included in 2020.2 beta

1 Like

what kind of fix it was? I still cant delete null sub assets from scriptable object

1 Like

My guess is they fixed the fact that RemoveMonoBehavioursWithMissingScript produces “dangling” objects inside prefabs that can’t be deleted with code. But if you already have them, there’s no way to do it. Here’s the best way I found:

  • Manually identify missing script guid by opening one of the .prefab files
  • Create a new empty ScriptableObject and modify it’s .meta file to have the same guid
  • Write a script that searches all assets and removes sub-objects of this new type
1 Like

I’m encountering the same issue in unity 2021.2 , sans prefabs, with assets derived from scriptable objects containing subassets that are instances derived from ScriptableObjects.

Steps:

  1. From C# , Declare a new class called TestScriptableObject that extends from ScriptableObject
  2. From Unity or via c#, Create a new ScriptableObject instance (A) and save it to the AssetDatabase (it doesnt matter the Type of A is)
  3. From Unity, Create a new ScriptabObject (B) of instance TestScriptabeObject and set it as a subasset of (A).
  4. In the C# environment, Rename the class TestScriptableObject or delete the definition of the class.
  5. From Unity, select the asset (A) and view its subassets.

Observed Notice (A) now contains null subassets (Script Is Missing).

These null subassets cannot be removed via API calls to the AssetDatabase via DestroyImmediatey, or RemoveObjectFromAsset. Furthermore, GameObjectUil::RemoveMonoBehavioursWithMissingScript cannot be used as the asset (A) is not a prefab in this case.

The AssetDatabase does seem like it is missing proper functionality to handle this. The use case does comes up quite frequently. Consider refactoring/deleting code that could result in missing scripts.

@SteenLund Do you know if the fix accounted for this use case (and not just the prefab asset use case mentioned initially)?

Please pardon the message if it is not relevant – I do feel like this is the relevant place to to post this as the issue presented is a generalization of the prefab asset issue mentioned.

Thanks in advance and have a nice day:)

@slippyfrog I am fairly certain that RemoveMonoBehaviourWithMissingScript does not work for this case. I’ll check with the Asset Database team what the proper way is to handle this.

I run into this same issue refrequently, I’ve build a few system which have ScriptableObjects with component based behavior, setup as an array pointing to sub-assets which the ScriptableObject host adds to itself.
This situation naturally needs a custom inspector, and in these cases I’ve had to add code to handle when some Sub-Asset has become “corrupted” and render a “Missing Component” Editor in those cases.

Unfortunately I see no way to fix these Assets nor remove them using built in APIs, the only idea I’ve had is to build code to directly modify the asset file text. Obviously this wouldn’t be an idea solution and so its one I’ve not pursued.

The ability to repair broken assets by trying to re-assign scripts to them would be a great feature to have along side the ability to remove broken sub-assets.

I had a tool that use to work using RemoveMonoBehaviourWithMissingScript but now in 2021.2 doesn’t work.
I have a scene with some gameObjects that has missing scripts, RemoveMonoBehaviourWithMissingScript return always 0.
Is this a known bug?

1 Like