Is there a command I can run in C# that will change the type associated with a component?

Everything I’m finding for changing the type associated with a component is done via yaml editing directly. But Unity obviously has an internal way to handle this, as we can do it from the IDE when a script goes missing.

Does anyone know if any of those API’s for changing it are exposed? It would be so much easier to write replacement tools if this were exposed and we didn’t have to work directly with YAML.

Did you figure out how TMPro is doing it with their guid-mapper?

I think ProBuilder also has an equivalent remap script tool, possibly even written by another guy than TMPro.

Whatever they’re doing is probably all that is possible under Unity.

tmppro is effectively doing a find replace on the yaml.

Sigh. :slight_smile:

I think we need a teeshirt for them,

“Do you even sed, bro?”

1 Like

Well you can just use the usual SerializedObject / SerializedProperty for that. The inspector does nothing different. When you drag a different MonoScript asset onto the m_Script field, Unity will destroy the object and recreate it with the new class while still keeping the serialized data.

I quickly created this test editor window and it works as it should:

using UnityEngine;
using UnityEditor;

public class ChangeScriptTypeWindow : EditorWindow
{
    [MenuItem("Tools/B83/ChangeScriptType")]
    public static void Init()
    {
        GetWindow<ChangeScriptTypeWindow>();
    }
    MonoScript newScript;
    MonoBehaviour target;
   
    void OnGUI()
    {
        target = (MonoBehaviour)EditorGUILayout.ObjectField("target: ", target, typeof(MonoBehaviour), true);
        newScript = (MonoScript)EditorGUILayout.ObjectField("replacement script: ", newScript, typeof(MonoScript), true);
        if (target && newScript)
        {
            if (GUILayout.Button("Replace"))
            {
                var go = target.gameObject;
                
                var so = new SerializedObject(target);
                var sProperty = so.FindProperty("m_Script");
                sProperty.objectReferenceValue = newScript;
                so.ApplyModifiedProperties();

                target = (MonoBehaviour)go.GetComponent(newScript.GetClass());
            }
        }
    }
}

You can drag any MonoBehaviour instance onto the target field of this editor window. Drag a replacement MonoScript onto the replacement script field. When you press “Replace” we will exchange the actual class associated with that MonoBehaviour component. In effect Unity will serialize the old instance, destroy the old instance, adds a new instance with the new class and deserialize it.

Note the first and last line in the button body are just to restore the reference to the target script after it get recreated. Otherwise our target variable will point to the destroyed instance.

If you have trouble getting the MonoScript instance for a certain MonoBehaviour class, you can use MonoScript.FromMonoBehaviour to get that instance from a given MonoBehaviour instance. Unfortunately there’s no method to directly get a MonoScript instance from a System.Type reference. However this could be done by a simple helper like that:

    static MonoScript GetMonoScriptFromType(System.Type aType)
    {
        if (typeof(MonoBehaviour).IsAssignableFrom(aType))
        {
            var tmpGO = EditorUtility.CreateGameObjectWithHideFlags("tmp", HideFlags.HideAndDontSave, aType);
            var monoScript = MonoScript.FromMonoBehaviour((MonoBehaviour)tmpGO.GetComponent(aType));
            DestroyImmediate(tmpGO);
            return monoScript;
        }
        if (typeof(ScriptableObject).IsAssignableFrom(aType))
        {
            var tmpInst = ScriptableObject.CreateInstance(aType);
            var monoScript = MonoScript.FromScriptableObject(tmpInst);
            DestroyImmediate(tmpInst);
            return monoScript;
        }
        return null;
    }

Another possible solution is using “EditorUtility.CopySerializedManagedFieldsOnly”. This method allows you to copy the managed serializable fields from one instance to another. So this should work as well:

            if (GUILayout.Button("Replace serialized"))
            {
                var newInst = (MonoBehaviour)target.gameObject.AddComponent(newScript.GetClass());
                EditorUtility.CopySerializedManagedFieldsOnly(target, newInst);
                DestroyImmediate(target);
                target = newInst;
            }

Here we manually create a new instance of the target type, copy all fields over and destroy the old instance.

3 Likes