Skip to content

Instantly share code, notes, and snippets.

@murgo
Created November 19, 2019 02:17
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save murgo/59a2cf2063445a89592ca4edfbb794f1 to your computer and use it in GitHub Desktop.
Save murgo/59a2cf2063445a89592ca4edfbb794f1 to your computer and use it in GitHub Desktop.
Unity tool script for replacing a Component with another in prefabs and scene objects
using System;
using System.Linq;
using UnityEditor;
using UnityEngine;
using Object = UnityEngine.Object;
namespace Assets.Scripts.Tools.Editor
{
public class ComponentReplaceTool
{
public static void AddComponentAndCopyData<TToReplace, TReplaceWith>(Action<TToReplace, TReplaceWith> copyAction, Func<TToReplace, TReplaceWith, bool> componentEquals, Func<TToReplace, string> toString1, Func<TReplaceWith, string> toString2) where TToReplace : Component where TReplaceWith : Component
{
var toReplace = DisabledObjectFinder.FindObjectsOfTypeAll<TToReplace>();
foreach (var componentToReplace in toReplace)
{
var go = componentToReplace.gameObject;
var isPartOfAnyPrefab = PrefabUtility.IsPartOfAnyPrefab(go) &&
!PrefabUtility.IsDisconnectedFromPrefabAsset(go);
TReplaceWith replaceWithInPrefab = null;
// set data in prefab
if (isPartOfAnyPrefab)
{
var prefab = PrefabUtility.GetCorrespondingObjectFromOriginalSource(go);
replaceWithInPrefab = prefab.GetComponent<TReplaceWith>();
var toReplaceInPrefab = prefab.GetComponent<TToReplace>();
if (toReplaceInPrefab == null)
{
continue;
}
if (replaceWithInPrefab == null)
{
var addedComponentInPrefab = go.AddComponent<TReplaceWith>();
copyAction?.Invoke(toReplaceInPrefab, addedComponentInPrefab);
PrefabUtility.ApplyAddedComponent(addedComponentInPrefab, PrefabUtility.GetPrefabAssetPathOfNearestInstanceRoot(go), InteractionMode.UserAction);
Debug.Log($"Created {typeof(TReplaceWith)} in prefab {prefab.name} with ({toString2?.Invoke(addedComponentInPrefab)})", prefab);
replaceWithInPrefab = prefab.GetComponent<TReplaceWith>();
Debug.Assert(replaceWithInPrefab != null);
}
}
// set data in object instance
var replaceWith = go.GetComponent<TReplaceWith>();
if (replaceWith != null)
{
if (isPartOfAnyPrefab)
{
if (!componentEquals(componentToReplace, replaceWithInPrefab))
{
Debug.Log($"-- SCENE NEEDS SAVING -- Setting {typeof(TReplaceWith)} value in prefab instance object {go.name} to ({toString1?.Invoke(componentToReplace)}) (old {toString2?.Invoke(replaceWith)})", go);
copyAction?.Invoke(componentToReplace, replaceWith);
}
}
else
{
if (!componentEquals(componentToReplace, replaceWith))
{
Debug.Log($"-- SCENE NEEDS SAVING -- Setting { typeof(TReplaceWith)} value in non-prefab object {go.name} to ({toString1?.Invoke(componentToReplace)}) (old {toString2?.Invoke(replaceWith)})", go);
copyAction?.Invoke(componentToReplace, replaceWith);
}
}
}
else
{
Debug.Assert(!isPartOfAnyPrefab, $"{typeof(TToReplace)} for {go} found in prefab but it doesn't already have {typeof(TReplaceWith)} set!", componentToReplace);
replaceWith = go.AddComponent<TReplaceWith>();
copyAction?.Invoke(componentToReplace, replaceWith);
Debug.Log($"-- SCENE NEEDS SAVING -- Created {typeof(TReplaceWith)} in non-prefab object {go.name} with ({toString2?.Invoke(replaceWith)})", replaceWith);
}
}
}
public static void RemoveComponents<TToRemove, TReplacedWith>(Func<TToRemove, string> toString1, Func<TReplacedWith, string> toString2) where TToRemove : Component where TReplacedWith : Component
{
var uniqueBarkActors = DisabledObjectFinder.FindObjectsOfTypeAll<TReplacedWith>();
foreach (var uniqueBarkActor in uniqueBarkActors)
{
var go = uniqueBarkActor.gameObject;
var isPartOfAnyPrefab = PrefabUtility.IsPartOfAnyPrefab(go) &&
!PrefabUtility.IsDisconnectedFromPrefabAsset(go);
if (isPartOfAnyPrefab)
{
// in case of prefab, add the TReplaceWith component to the prefab
var prefab = PrefabUtility.GetCorrespondingObjectFromOriginalSource(go);
var uniqueBarkActorInPrefab = prefab.GetComponent<TReplacedWith>();
if (uniqueBarkActorInPrefab == null)
{
Debug.LogError($"-- HANDLE MANUALLY -- Trying to delete {typeof(TToRemove)} from prefab {prefab.name} but it doesn't have {typeof(TReplacedWith)}!", prefab);
}
else
{
var toRemove = go.GetComponent<TToRemove>();
if (toRemove != null)
{
Debug.Log($"Removing {typeof(TToRemove)} from prefab {prefab.name} with ({toString1?.Invoke(toRemove)}) ({typeof(TReplacedWith)} {toString2?.Invoke(uniqueBarkActorInPrefab)})", prefab);
Object.DestroyImmediate(toRemove);
PrefabUtility.ApplyRemovedComponent(go, prefab.GetComponent<TToRemove>(), InteractionMode.UserAction);
}
}
}
else
{
var toRemove = go.GetComponent<TToRemove>();
if (toRemove != null)
{
Debug.Log($"-- SCENE NEEDS SAVING -- Removed {typeof(TToRemove)} from non-prefab object {go.name} with ({toString1?.Invoke(toRemove)})", go);
Object.DestroyImmediate(toRemove);
}
}
}
var toRemovesAfterDeletion = DisabledObjectFinder.FindObjectsOfTypeAll<TToRemove>();
Debug.Assert(toRemovesAfterDeletion.Count == 0, $"{typeof(TToRemove)}s remaining after delete: {toRemovesAfterDeletion.Count}, list: {string.Join(", ", toRemovesAfterDeletion.Select(o => o.ToString()))}");
}
}
}
using UnityEditor;
namespace Assets.Scripts.Tools.Editor
{
public class ExampleScriptReplaceTool
{
[MenuItem("Tools/ComponentReplaceTool/Add ExampleScript to Objects with OldExampleScript")]
public static void AddExampleScriptToObjects()
{
ComponentReplaceTool.AddComponentAndCopyData<OldExampleScript, ExampleScript>(
(old, nuevo) => nuevo.SetName(old.Name),
(old, nuevo) => old?.Name == nuevo?.Name,
(old) => $"name: {old?.Name}",
(nuevo) => $"name: {nuevo?.Name}"
);
}
[MenuItem("Tools/ComponentReplaceTool/Remove OldExampleScript from Objects with ExampleScript")]
public static void RemoveOldExampleScriptFromObjects()
{
ComponentReplaceTool.RemoveComponents<OldExampleScript, ExampleScript>(
(old) => $"name: {old?.Name}",
(nuevo) => $"name: {nuevo?.Name}"
);
}
}
}
@leohilbert
Copy link

leohilbert commented Aug 12, 2023

Found a way to do this without writing a manual migration script action:

public static void SwitchOutComponent<TReplaceWith>(MonoBehaviour from)
            where TReplaceWith : MonoBehaviour
{
    GameObject go = from.gameObject;
    string fromJson = JsonUtility.ToJson(from);

    Undo.DestroyObjectImmediate(from);
    
    // todo somehow preserve guid from original component?
    TReplaceWith to = Undo.AddComponent<TReplaceWith>(go);

    JsonUtility.FromJsonOverwrite(fromJson, to);
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment