Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

How to change a component to a child of the same component

Discussion in 'Editor & General Support' started by LordFluffy1, May 16, 2021.

  1. LordFluffy1

    LordFluffy1

    Joined:
    Mar 11, 2015
    Posts:
    11
    This is a situation I keep running into while working in the Unity editor:

    -I decide to create a child class of a component in C# in order to differentiate it from other GameObjects using the same component class.

    -I remove the old component from the GameObject in question, add the new child component, and have to reenter all the individual values that this new component shared with the old one within the editor.

    I've tried copying the old component and then pasting in the values to the new one, but that option is greyed out.

    Is there any way to quickly perform this operation, to retain the values from the old parent component and use them in the new child? Manually reentering values over and over again is becoming a bit of a pain.
     
    Last edited: May 16, 2021
  2. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,762
  3. LordFluffy1

    LordFluffy1

    Joined:
    Mar 11, 2015
    Posts:
    11
    Yikes, thanks for the response, but that's rather hacky isn't it? There's seriously no built-in editor functionality for switching to a child class of a component? This seems to me like it would be an extremely common task.
     
  4. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,762
    It's just data my dear Lord Fluffy One, just ones and zeroes.

    F'rinstance, here's the two .meta files I just made for BaseScript.cs and DerivedScript.cs:

    Screen Shot 2021-05-16 at 5.09.08 PM.png

    I made a prefab with the script on the root and a Target child object dragged into the "Bat" field:

    Screen Shot 2021-05-16 at 5.14.39 PM.png

    Screen Shot 2021-05-16 at 5.14.43 PM.png

    Using the Unity Editor the way you are and REMOVING the old script and ADDING the new script, then populating all fields, this was the total diff generated by source control:

    Screen Shot 2021-05-16 at 5.15.36 PM.png

    The actual diff is only the final of the three. The first two huge numerics are internal-to-the-asset linkages, which got recreated but do not actually impact what GUID is connected. They only correlate to each other, nothing else, so it is not necessary to change them when you change the guid.

    So then I reverted that work and did my single-point GUID-change "hack" with
    vi


    And for the final piece de resistance in all of its derived resplendent glory,

    Screen Shot 2021-05-16 at 5.21.57 PM.png

    SHIP IT!
     
    nbaris likes this.
  5. LordFluffy1

    LordFluffy1

    Joined:
    Mar 11, 2015
    Posts:
    11
    Sure, but in most cases I suspect this is going to be more trouble than it's worth, so I'll just keep punching the values in manually over and over again to avoid it. Plus, good luck passing this on to level designers and such.

    So I take it that's a no on there being an integrated editor function for reassigning a component to a child of the same class?

    Your method is very cool and all, and I appreciate the detailed response, but I was hoping there might be some editor functionality that would help with this task.
     
    Last edited: May 17, 2021
  6. slippyfrog

    slippyfrog

    Joined:
    Jan 18, 2016
    Posts:
    42
    Hi @LordFluffy1

    Code (CSharp):
    1.  
    2.   public class CopyComponentValues : MonoBehaviour
    3.   {
    4.     [SerializeField]
    5.     private Component m_Src;
    6.  
    7.  
    8.     [SerializeField]
    9.     private Component m_Dst;
    10.  
    11.     [InspectorButton("Copy Values From Src To Dst", true)]
    12.     public void CopyValues( )
    13.     {
    14.       Type srcType = m_Src.GetType( );
    15.       Type dstType = m_Dst.GetType( );
    16.  
    17.       Assert.IsTrue ( dstType.IsAssignableFrom ( srcType ), $"DstType ({dstType}) must be assignable from SrcType ({srcType})" );
    18.  
    19.       FieldInfo [] sourceFields = dstType.GetFields ( BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance );
    20.  
    21.  
    22.       foreach ( FieldInfo field in sourceFields )
    23.       {
    24.         field.SetValue ( m_Dst, field.GetValue ( m_Src ) );
    25.       }
    26.     }
    27.   }
    This is quick and dirty but you could probably do something like the above code. You can evolve the implementation as I implemented this without much effort. The functionality is probably better served as a EditorWindow (see below) It works well assuming you just have fields and not properties that need to be copied over, deep copy vs shallow copy considerations, etc.

    In any case, the solution uses reflection. There are two parameters, Src and Dst. In the implementation Dst must be a base class of Src. All common fields are copied from src to dst.

    Fidelity could be added to this solution by allow the user to select the coponent type they want to add, and deleting the old component via the script. Pseudo code:

    Code (csharp):
    1.  
    2. PSEUDO CODE:
    3. ReplaceComponentEditorWindow : EditorWindow
    4. {
    5. OnGUI(){
    6.   m_ComponentToReplace =  ObjectField(m_ComponentToReplace);
    7.   if ( Button("Select Component Type"))
    8.   {
    9. //-- select the new type we want to use from a list of component types (types shown are base types to ComponentToReplace )
    10.      m_SelectedComponentTypeToCreate = ShowComponentTypeMenu(m_ComponentToReplace.GetType());
    11.   }
    12.   if (Button("Do Component Replace"))
    13.   {
    14.       ReplaceComponentWithNewComponentOfType (  m_SelectedComponentTypeToCreate,   m_ComponentToReplace  );
    15.   }
    16. }
    17.  
    18. ReplaceComponentWithNewComponentOfType(Type p_NewComponentType, Component p_ComponentToReplace)
    19. {
    20.   GameObject theParent = p_CompoentToReplace.parent;
    21.   Component newComponentInstance = theParent.AddComponent(p_NewComponentType)
    22.   CopyValues(p_ComponentToReplace, newComponentInstance)
    23.   Destroy(p_ComponentToReplace)
    24. }
    25. }
    26.  
    27.  
    Hope it helps!
     
  7. MaxedHuk

    MaxedHuk

    Joined:
    Apr 25, 2018
    Posts:
    2
    I had the same problem today and discovered by accident that switching the properties to Debug mode makes the Script property of the Component editable and you can drag and drop the new script into it. This saved me so much time, hope it helps someone as well.
     
  8. daniel_lochner

    daniel_lochner

    Joined:
    Jun 9, 2016
    Posts:
    168
    Thanks! This was SUPER helpful!!! :D
     
  9. crochelmeyer

    crochelmeyer

    Joined:
    Mar 27, 2016
    Posts:
    4
    We used to do this as well, but it looks like Unity has removed this workaround (at least as of 2022.2) as the script field is readonly in the debug view now.

    Added a little script as a new workaround. Unfortunately it's not as speedy as swapping values in debug mode, but certainly beats updating an asset and possible variants manually :/

    Code (CSharp):
    1. using System.IO;
    2. using UnityEngine;
    3.  
    4. #if UNITY_EDITOR
    5.  
    6. using UnityEditor;
    7.  
    8. #endif
    9.  
    10. public static class MonoBehaviourExtensions
    11. {
    12. #if UNITY_EDITOR
    13.  
    14.     [UnityEditor.MenuItem("CONTEXT/MonoBehaviour/Change Script")]
    15.     public static void ChangeScript(MenuCommand command)
    16.     {
    17.         if (command.context == null) return;
    18.  
    19.         var monoBehaviour = command.context as MonoBehaviour;
    20.         var monoScript = MonoScript.FromMonoBehaviour(monoBehaviour);
    21.  
    22.         var scriptPath = AssetDatabase.GetAssetPath(monoScript);
    23.         var directoryPath = new FileInfo(scriptPath).Directory?.FullName;
    24.  
    25.         // Allow the user to select which script to replace with
    26.         var newScriptPath = EditorUtility.OpenFilePanel("Select replacement script", directoryPath, "cs");
    27.  
    28.         // Don't log anything if they cancelled the window
    29.         if (string.IsNullOrEmpty(newScriptPath)) return;
    30.  
    31.         // Load the selected asset
    32.         var relativePath = "Assets\\" + Path.GetRelativePath(Application.dataPath, newScriptPath);
    33.         var chosenTextAsset = AssetDatabase.LoadAssetAtPath<TextAsset>(relativePath);
    34.  
    35.         if (chosenTextAsset == null)
    36.         {
    37.             Debug.LogWarning($"Selected script couldn't be loaded ({relativePath})");
    38.             return;
    39.         }
    40.  
    41.         Undo.RegisterCompleteObjectUndo(command.context, "Changing component script");
    42.  
    43.         var so = new SerializedObject(monoBehaviour);
    44.         var scriptProperty = so.FindProperty("m_Script");
    45.         so.Update();
    46.         scriptProperty.objectReferenceValue = chosenTextAsset;
    47.         so.ApplyModifiedProperties();
    48.     }
    49.  
    50. #endif
    51. }
     
    cmdexecutor likes this.
  10. cyliax

    cyliax

    Joined:
    Feb 25, 2014
    Posts:
    18
    This is exactly what I needed, cheers bro!
     
  11. wechat_os_Qy0xDfW7_FXo5lVaICGD12FD8

    wechat_os_Qy0xDfW7_FXo5lVaICGD12FD8

    Joined:
    Jul 9, 2023
    Posts:
    4
    Exactly what I need, thanks a lot!!!