• Products
  • Solutions
  • Made with Unity
  • Learning
  • Support & Services
  • Community
  • Asset Store
  • Get Unity

UNITY ACCOUNT

You need a Unity Account to shop in the Online and Asset Stores, participate in the Unity Community and manage your license portfolio. Login Create account
  • Blog
  • Forums
  • Answers
  • Evangelists
  • User Groups
  • Beta Program
  • Advisory Panel

Navigation

  • Home
  • Products
  • Solutions
  • Made with Unity
  • Learning
  • Support & Services
  • Community
    • Blog
    • Forums
    • Answers
    • Evangelists
    • User Groups
    • Beta Program
    • Advisory Panel

Unity account

You need a Unity Account to shop in the Online and Asset Stores, participate in the Unity Community and manage your license portfolio. Login Create account

Language

  • Chinese
  • Spanish
  • Japanese
  • Korean
  • Portuguese
  • Ask a question
  • Spaces
    • Default
    • Help Room
    • META
    • Moderators
    • Topics
    • Questions
    • Users
    • Badges
  • Home /
avatar image
13
Question by vexe · Sep 04, 2013 at 05:39 AM · c#gameobjectgetcomponentcomponentaddcomponent

How to get a component from an object and add it to another? (Copy components at runtime)

EDIT: a much more solid way is to serialize/deserialize the component (clone it) - See FastSave in Vfw (Free)


Hello, I have a "FILE" and "TextFile", "ImageFile", "AudioFile" and "VideoFile" as children to (inherit from) "FILE".

"FILE" is abstract so I don't add it to my objects, I add the other scripts, text, image, etc. - What I wanna do, is once I pickup a text/image/video/audio file, I wanna 'get' the text/image/video/audio file script/component and add it to another object I have elsewhere.

I tried something like this:

 var fileScript = fileObj.GetComponent(typeof(FILE));

I think this gets it fine, right? I mean, if fileObj had a TextFile attached, this should get me that component, because a TextFile is of type FILE (inherits from it - TextFile 'is a' FILE)..... RIGHT?

But then how to add it to my other object? tried something like this:

 myOtherObj.AddComponent(fileScript.ToString());

but my guess is, is that the previous adds a new component, not the same one I picked/got from my fileObj, I wanna add the SAME one, how?

This also doesn't work:

 myOtherObj.AddComponent(fileScript);

Some comments and clarifications if you can on the return types of GetComponent and the inputs of AddComponent are very much appreciated as well.

Thanks a lot.

Comment
Add comment · Show 3
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image getyour411 · Sep 04, 2013 at 06:33 AM 0
Share

on that last example, try putting fileScript in quotes

 myOtherObj.AddComponent("fileScript");

(assu$$anonymous$$g that is actually the name of your script sans the .js)

avatar image vexe · Sep 04, 2013 at 06:39 AM 1
Share

I don't use JS. There's a C# tag. But from time to time I use the var keyword. Does this also work for C#? - I'll give it a try...

avatar image vexe · Sep 04, 2013 at 06:41 AM 1
Share

wait, do you mean that "fileScript" should be exactly what I'm adding? - that's not gonna work cuz I don't know what I'm gonna be picking up, could be a "TextFile", "AudioFile" etc.

12 Replies

· Add your reply
  • Sort: 
avatar image
58
Best Answer

Answer by vexe · Feb 16, 2014 at 10:25 AM

So I finally found a solution to copying components at runtime - via reflection. Thanks to @Jamora who pointed out that I could set values via reflection too, not just get them.

This extension method gets a copy of a component:

 public static T GetCopyOf<T>(this Component comp, T other) where T : Component
 {
     Type type = comp.GetType();
     if (type != other.GetType()) return null; // type mis-match
     BindingFlags flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Default | BindingFlags.DeclaredOnly;
     PropertyInfo[] pinfos = type.GetProperties(flags);
     foreach (var pinfo in pinfos) {
         if (pinfo.CanWrite) {
             try {
                 pinfo.SetValue(comp, pinfo.GetValue(other, null), null);
             }
             catch { } // In case of NotImplementedException being thrown. For some reason specifying that exception didn't seem to catch it, so I didn't catch anything specific.
         }
     }
     FieldInfo[] finfos = type.GetFields(flags);
     foreach (var finfo in finfos) {
         finfo.SetValue(comp, finfo.GetValue(other));
     }
     return comp as T;
 }

Usage:

 var copy = myComp.GetCopyOf(someOtherComponent);

To make things more convenient, I added this GameObject extension method to add a component directly by just passing it so a copy of it will get added:

 public static T AddComponent<T>(this GameObject go, T toAdd) where T : Component
 {
     return go.AddComponent<T>().GetCopyOf(toAdd) as T;
 }

Usage:

 Health myHealth = gameObject.AddComponent<Health>(enemy.health);

Have fun :D

Comment
Add comment · Show 12 · Share
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image Wolfram · Jul 01, 2015 at 11:27 AM 0
Share

great answer!

avatar image RimFra · Sep 01, 2015 at 12:16 PM 0
Share

Would it be possible to have it recursive?Nested classes are not copied.

avatar image M0rph3v5 · Dec 02, 2015 at 10:22 PM 1
Share

Just stumbled into a problem with this code, things like an EdgeCollider2D which extends a Collider2D properties like offset won't get copied. If I remove the flags it works, but I'm not sure what impact that has. Not sure either under what flag it would be.

avatar image Llockham-Industries · May 21, 2017 at 11:13 PM 1
Share

Just checking out the documentation. It appears BindingFlags.DeclaredOnly restricts the members gathered to the members of that type only, and not members inherited. Not sure why it's included, removing that should allow you to copy members of inherited types as well.

avatar image TommyZakh · Oct 29, 2017 at 01:06 AM 0
Share

This does not work when copying AudioSource components. If I try to do this, it gives the error message: "$$anonymous$$Volume is not supported anymore. Use $$anonymous$$-, maxDistance and rolloff$$anonymous$$ode ins$$anonymous$$d. System.Reflection.$$anonymous$$onoProperty:GetValue(Object, Object[])" and several more similar ones. I assume this is because it tries to copy deprecated properties, but how would I go about ignoring them?

avatar image Cardinal90 TommyZakh · Aug 23, 2018 at 06:07 AM 1
Share

I had the same problem. The solution I found is to add this code before try-catch block

 bool obsolete = false;
 IEnumerable attrData = pinfo.CustomAttributes;
 foreach (CustomAttributeData data in attrData)
 {
     if (data.AttributeType == typeof(System.ObsoleteAttribute))
     {
         obsolete = true;
         break;
     }
 }
 if (obsolete)
 {
     continue;
 }

avatar image CaseyHofland Cardinal90 · Apr 12, 2020 at 04:35 PM 1
Share

Try this ins$$anonymous$$d:

 using System.Linq;
 
 // Replace pinfos with this:
 var pinfos = from property in type.GetProperties(flags)
          where !property.CustomAttributes.Any(attribute => attribute.AttributeType == typeof(ObsoleteAttribute))
          select property;

It queries the properties before iterating over them.

Show more comments
avatar image
12

Answer by vexe · Dec 27, 2013 at 02:40 PM

EDIT:

DON'T USE EDITOR CODE IN YOUR SHIPPABLE CODE - IT WON'T BUILD.


Thanks to @Statement for his comment here, It is possible to copy/paste components values, at runtime - using the same functionality used by the editor.

The functionality is hidden, and undocumented sitting inside UnityEditorInternal.ComponentUtility

Here's a peek inside:

     [WrapperlessIcall]
     public static bool CopyComponent(Component component);
     [WrapperlessIcall]
     public static bool MoveComponentDown(Component component);
     [WrapperlessIcall]
     public static bool MoveComponentUp(Component component);
     [WrapperlessIcall]
     public static bool PasteComponentAsNew(GameObject go);
     [WrapperlessIcall]
     public static bool PasteComponentValues(Component component);


Here's a simple script, that swaps the transforms of two objects, using CopyComponent and PasteComponentValues :)

 using UnityEngine;
 using System.Collections.Generic;

 public class TestMB : MonoBehaviour
 {
     public GameObject go1;
     public GameObject go2;

     void OnGUI()
     {
         if (GUI.Button(new Rect(Screen.width / 2, 100, 200, 100), "Swap transforms")) {
             var t1 = go1.GetComponent<Transform>();
             var t2 = go2.GetComponent<Transform>();
             var temp = new GameObject("_TEMP").GetComponent<Transform>();

             // temp = t1;
             // t1 = t2;
             // t2 = temp;

             if (UnityEditorInternal.ComponentUtility.CopyComponent(t1)) {
                 if (UnityEditorInternal.ComponentUtility.PasteComponentValues(temp)) {
                     if (UnityEditorInternal.ComponentUtility.CopyComponent(t2)) {
                         if (UnityEditorInternal.ComponentUtility.PasteComponentValues(t1)) {
                             if (UnityEditorInternal.ComponentUtility.CopyComponent(temp)) {
                                 if (UnityEditorInternal.ComponentUtility.PasteComponentValues(t2)) {
                                     print("DONE");
                                 }
                             }
                         }
                     }
                 }
             }
             Destroy(temp.gameObject);
         }
     }
 

alt text

Enjoy! :D



swap.png (293.1 kB)
Comment
Add comment · Show 1 · Share
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image domiii · Feb 27, 2016 at 12:34 PM 0
Share

This is the true answer (given, you are in the editor that is).

avatar image
3

Answer by robhuhn · Sep 04, 2013 at 06:45 AM

You can't transfer a component from one object to another. What you can do is

  • add a new component to the new object and transfer all properties from one component to the other

  • or clone the whole object and remove what is not needed:

    FileComponent fileClone = (FileComponent)Instantiate(fileObj); //c#

Comment
Add comment · Show 3 · Share
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image vexe · Sep 04, 2013 at 06:48 AM 1
Share

Yes, unfortunately that's the conclusion I came to as well... :/ I don't know why Unity doesn't allow this, it should be simple. Thanks for the answer. I'll wait a bit and then mark it as the correct answer.

avatar image robhuhn · Sep 04, 2013 at 07:00 AM 0
Share

$$anonymous$$ay be because of the circular dependencies of an object but that's just a guess.

avatar image vexe · Sep 04, 2013 at 08:10 AM 1
Share

but hey wait a second, in the editor, you can copy a component and paste it somewhere else, can't we do that in code??? that would be cool!

avatar image
3

Answer by arutyunef · Jan 07, 2019 at 09:59 PM

First of all, setting fields/properties that aren't public/readonly is pointless. A much cleaner version of the code would be this. You really don't need flags & the above codes are messed up with the type usage:

 public static T AddComponent<T> ( this GameObject game, T duplicate ) where T : Component
 {
     T target = game.AddComponent<T> ();

     foreach (PropertyInfo x in typeof ( T ).GetProperties ())
         if (x.CanWrite)
             x.SetValue ( target, x.GetValue ( duplicate ) );

     return target;
 }

Then you just use it like this:

 your_gameobject.AddComponent ( component_you_want_to_duplicate );
Comment
Add comment · Share
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image
2

Answer by CaseyHofland · Apr 28, 2020 at 10:50 AM

I command you on your implementation vexe, but allow me to one-up it. My biggest complaint is that it doesn't allow for abstract types like Colliders to be copied. And when you copy their runtime types, the derived values are all omitted (e.g. isTrigger, sharedMaterial, etc.)

Introducing my own implementation, which takes care of these complaints and copies Derived fields flawlessly:

 // Based on an implementation by vexe.
 
 using System;
 using System.Collections.Generic;
 using System.Linq;
 using System.Reflection;
 using UnityEngine;
 
 public static class RuntimeComponentCopier
 {
     private const BindingFlags bindingFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Default | BindingFlags.DeclaredOnly;
 
     public static T GetCopyOf<T>(this Component comp, T other) where T : Component
     {
         Type type = comp.GetType();
         if(type != other.GetType()) return null; // type mis-match
 
         Type derivedType = typeof(T);
         bool hasDerivedType = derivedType != type;
 
         IEnumerable<PropertyInfo> pinfos = type.GetProperties(bindingFlags);
         if(hasDerivedType)
         {
             pinfos = pinfos.Concat(derivedType.GetProperties(bindingFlags));
         }
         pinfos = from property in pinfos
                  where !(type == typeof(Rigidbody) && property.Name == "inertiaTensor") // Special case for Rigidbodies inertiaTensor which isn't catched for some reason.
                  where !property.CustomAttributes.Any(attribute => attribute.AttributeType == typeof(ObsoleteAttribute))
                  select property;
         foreach(var pinfo in pinfos)
         {
             if(pinfo.CanWrite)
             {
                  if(pinfos.Any(e => e.Name == $"shared{char.ToUpper(pinfo.Name[0])}{pinfo.Name.Substring(1)}"))
                 {
                     continue;
                 }

                 try
                 {
                     pinfo.SetValue(comp, pinfo.GetValue(other, null), null);
                 }
                 catch { } // In case of NotImplementedException being thrown. For some reason specifying that exception didn't seem to catch it, so I didn't catch anything specific.
             }
         }
 
         IEnumerable<FieldInfo> finfos = type.GetFields(bindingFlags);
         if(hasDerivedType)
         {
             if(finfos.Any(e => e.Name == $"shared{char.ToUpper(finfo.Name[0])}{finfo.Name.Substring(1)}"))
             {
                 continue;
             }

             finfos = finfos.Concat(derivedType.GetFields(bindingFlags));
         }
         finfos = from field in finfos
                  where field.CustomAttributes.Any(attribute => attribute.AttributeType == typeof(ObsoleteAttribute))
                  select field;
         foreach(var finfo in finfos)
         {
             finfo.SetValue(comp, finfo.GetValue(other));
         }
 
         return comp as T;
     }
 
     public static T AddComponent<T>(this GameObject go, T toAdd) where T : Component
     {
         return go.AddComponent(toAdd.GetType()).GetCopyOf(toAdd) as T;
     }
 }


If you already have vexe's implementation, you can simply swap it out and it will still work the same, now with the addition of abstract types.

Usage

 var col = GetComponent<Collider>(); // this is a Box Collider 
 var copy = myComp.GetCopyOf(col); 
 Debug.Log(copy.GetType()) // = BoxCollider


Or Simply

 someGameObject.AddComponent(col); // is a Box Collider


Also doesn't copy Obsolete values and checks if there is a shared version of a value before copying :D

Comment
Add comment · Show 1 · Share
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image njwilson · Jun 05, 2020 at 02:44 PM 1
Share

Great answer but there are a few corrections that need to be made. I am posting my own answer with the edits

  • 1
  • 2
  • 3
  • ›

Your answer

Hint: You can notify a user about this post by typing @username

Up to 2 attachments (including images) can be used with a maximum of 524.3 kB each and 1.0 MB total.

Follow this Question

Answers Answers and Comments

31 People are following this question.

avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image

Related Questions

AddComponent for RawImage not functioning as expected 0 Answers

Disabling A Script on a GameObject From a Different Script 2 Answers

Can I use AddComponent to add (this.script) to an object? 1 Answer

Find a Component/GameObject Using an Interface Reference 2 Answers

GetComponent vs AddComponent 3 Answers


Enterprise
Social Q&A

Social
Subscribe on YouTube social-youtube Follow on LinkedIn social-linkedin Follow on Twitter social-twitter Follow on Facebook social-facebook Follow on Instagram social-instagram

Footer

  • Purchase
    • Products
    • Subscription
    • Asset Store
    • Unity Gear
    • Resellers
  • Education
    • Students
    • Educators
    • Certification
    • Learn
    • Center of Excellence
  • Download
    • Unity
    • Beta Program
  • Unity Labs
    • Labs
    • Publications
  • Resources
    • Learn platform
    • Community
    • Documentation
    • Unity QA
    • FAQ
    • Services Status
    • Connect
  • About Unity
    • About Us
    • Blog
    • Events
    • Careers
    • Contact
    • Press
    • Partners
    • Affiliates
    • Security
Copyright © 2020 Unity Technologies
  • Legal
  • Privacy Policy
  • Cookies
  • Do Not Sell My Personal Information
  • Cookies Settings
"Unity", Unity logos, and other Unity trademarks are trademarks or registered trademarks of Unity Technologies or its affiliates in the U.S. and elsewhere (more info here). Other names or brands are trademarks of their respective owners.
  • Anonymous
  • Sign in
  • Create
  • Ask a question
  • Spaces
  • Default
  • Help Room
  • META
  • Moderators
  • Explore
  • Topics
  • Questions
  • Users
  • Badges