- Home /
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.
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)
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...
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.
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
Would it be possible to have it recursive?Nested classes are not copied.
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.
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.
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?
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;
}
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.
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);
}
}
Enjoy! :D
This is the true answer (given, you are in the editor that is).
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#
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.
$$anonymous$$ay be because of the circular dependencies of an object but that's just a guess.
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!
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 );
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
Great answer but there are a few corrections that need to be made. I am posting my own answer with the edits