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.
Prefab after using script.
External ids appeared
3.Here my missing component that was added after removing by script. If I delete it will fix the problem.
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();
}
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.
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 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.
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
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:
From C# , Declare a new class called TestScriptableObject that extends from ScriptableObject
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)
From Unity, Create a new ScriptabObject (B) of instance TestScriptabeObject and set it as a subasset of (A).
In the C# environment, Rename the class TestScriptableObject or delete the definition of the class.
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.
@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?