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.