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. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice
  3. Join us on November 16th, 2023, between 1 pm and 9 pm CET for Ask the Experts Online on Discord and on Unity Discussions.
    Dismiss Notice

Copy component fields but maintain local references

Discussion in 'Scripting' started by alexisrabadan, Sep 28, 2016.

  1. alexisrabadan

    alexisrabadan

    Joined:
    Aug 26, 2014
    Posts:
    81
    I have two gameobject's(GameObjectA and GameObjectB), they each have a component of the same type (ComponentA, ComponentB). The components have a field that references their own gameobject, and I want to copy all of the fields from ComponentA to ComponentB.

    When the gameobject field is copied from ComponentA, it copies the reference of GameObjectA to ComponentB. What I need however is the field to copy BUT copy in a way that it references the local gameobject (ie. ComponentB references GameObjectB).

    This is just an example, it needs to work for all object references in the local hierarchy.

    I'm fairly sure this is possible since it seems Unity prefabs can do it.
     
  2. CloudKid

    CloudKid

    Joined:
    Dec 13, 2015
    Posts:
    207
    I don't think there Is a magic function that does that. What you can do is to test if a field in an object is a reference to itself. You test that by doing:
    Code (CSharp):
    1.     private bool TestIfSelf(UnityEngine.Object  a, UnityEngine.Object b)
    2.     {
    3.         return a.GetInstanceID() == b.GetInstanceID();
    4.     }
    Instead of UnityEngine.Object you can use any Unity Component or Object(for example GameObject, Transform, Renderer, etc)

    So, if you find a "GameObject obj" field you test if TestIfSelf(obj, gameObject) is true, and if it is, you will not copy this value.

    I am sure you could generalise this function even more, but if you know exactly what components you need to test, it should be fine
     
  3. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,199
    How are you copying components? Through the "paste component as new" menu?

    The scripting side isn't that hard; use UnityEditorInternal.ComponentUtility to copy/paste the script. After that, create a SerializedObject wrapper for the copy, and iterate through all it's SerializedProperties. If one is an object/transform reference, and that reference points to the object the original is on, replace it with a reference to the new one.

    The harder part is the menu. I don't know if it's possible to place something next to the "paste component as new" menu - you can place things there with the ContextMenu attribute, but that's on a script-by-script basis, not sure if you can do if for object. You could make a keyboard shortcut, that's my best suggestion.
     
    Gizmoi likes this.
  4. alexisrabadan

    alexisrabadan

    Joined:
    Aug 26, 2014
    Posts:
    81
    Thing is that I need it to work for objects/components in the same hierarchy as well (ie. a component that references a component on the parent game object).
     
  5. alexisrabadan

    alexisrabadan

    Joined:
    Aug 26, 2014
    Posts:
    81
    Sorry, forgot about that. I am currently using reflection, copying the fields one by one. My first guess was to test and see if the value is a component/gameobject reference and if so then find a reference that matches in the local hierarchy.
     
  6. i_cassell

    i_cassell

    Joined:
    Apr 6, 2019
    Posts:
    21
    ComponentUtility Can't copy refrence!
     
  7. rubcc95

    rubcc95

    Joined:
    Dec 27, 2019
    Posts:
    222
    If it's your custom component, it would be easy creating your custom ICopyable interface:

    Code (CSharp):
    1. public interface ICopyable<T>
    2. {
    3.     void CopyValueFrom(T origin);
    4. }
    For a cleaner code, you can create some extension methods:
    Code (CSharp):
    1. public static class GameObjectExtensionMethods
    2. {
    3.     public static T CopyComponent<T, S>(this GameObject gameObject, S component) where T : Component, ICopyable<S>
    4.     {
    5.         var result = gameObject.AddComponent<T>();
    6.         result.CopyValueFrom(component);
    7.         return result;
    8.     }
    9.  
    10.     public static T CopyComponent<T>(this GameObject gameObject, T component) where T : Component, ICopyable<T> => gameObject.CopyComponent<T, T>(component);
    11.  
    12.     public static T CopyComponent<T, S>(this GameObject to, GameObject from) where T : Component, ICopyable<S>
    13.     {
    14.         if (from.TryGetComponent<S>(out var component))
    15.         {
    16.             return to.CopyComponent<T, S>(component);
    17.         }
    18.         throw new ArgumentException(from.name + " has not attached any component " + typeof(S).Name);
    19.     }
    20.  
    21.     public static T CopyComponent<T>(this GameObject to, GameObject from) where T : Component, ICopyable<T> => to.CopyComponent<T, T>(from);
    22. }
    23.  
    Now we implement ICopyable to our component:
    Code (CSharp):
    1. public class MyComponent : MonoBehaviour, ICopyable<MyComponent>
    2. {
    3.     public int value = 0;
    4.     public string data = "default string";
    5.  
    6.     public void CopyValueFrom(MyComponent origin)
    7.     {
    8.         value = origin.value;
    9.         data = origin.data;
    10.     }
    11. }
    And here it is, now we can do:
    Code (CSharp):
    1. public class ExampleButton : MonoBehaviour
    2. {
    3.     GameObject a;
    4.     GameObject b;
    5.  
    6.     public void OnClick()
    7.     {
    8.          b.CopyComponent<MyComponent>(a);
    9.     }
    10. }
    And b gets a new instance of MyComponent and value is pasted as we defined at CopyValueFrom().
     
  8. i_cassell

    i_cassell

    Joined:
    Apr 6, 2019
    Posts:
    21
    I need to copy from thirdparty scripts,not from my script .Maybe FileID is possible,i have no idea.
     
  9. rubcc95

    rubcc95

    Joined:
    Dec 27, 2019
    Posts:
    222
    Well you could add another extension method like this:

    Code (CSharp):
    1.         public static T CopyComponent<T>(this GameObject gameObject, T component, Action<T,T> action) where T : Component
    2.         {
    3.             var result = gameObject.AddComponent<T>();
    4.             action(component, result);
    5.             return result;
    6.         }
    With it, now you can copy, for example a Unity sealed class like Rigidbody or a Collider:
    Code (CSharp):
    1.     public class MyMonoBehaviour : MonoBehaviour
    2.     {
    3.         CircleCollider2D col;
    4.  
    5.         void CopyCircleCollider2D()
    6.         {
    7.             gameObject.CopyComponent(col, (original, copy) =>
    8.             {
    9.                 copy.radius = original.radius;
    10.                 copy.density = original.density;
    11.                 copy.usedByComposite = original.usedByComposite;
    12.                 copy.usedByEffector = original.usedByEffector;
    13.                 copy.isTrigger = original.isTrigger;
    14.                 copy.sharedMaterial = original.sharedMaterial;
    15.                 copy.offset = original.offset;
    16.  
    17.             });
    18.         }
    19.     }