This is a hopefully useful code snippet, which turns into a wish list. The solution to my problem ended up being far more of a PITA then I expected or wanted it to be. Please consider making it “just work” in a future version of Unity.
The scenario:
We take an animated object from Maya, and import it into Unity. We instantiate an instance of this object, and add a bunch of additional components to this object to drive the game’s logic, and then create a prefab.
Sometime later, we have to modify the underlying model used by the prefab. Since importing the model a second time results in a separate object without the prefabs, we replace the old .mb file with the new one from the finder (dangerous! we know). Sometimes things are good, sometimes something that changed in the model breaks the skinned mesh renderer, and we have to drag a new instance of the .mb in anyway.
The problem:
In either case, we have two objects, one which no longer works visually, but contains a bunch of configuration data that we want to preserve. The other works visually, but contains none of the additional components that make the prefab useful (re-linking the object might take a very long time, and is error prone).
We want to be able to clone the components from one GameObject to another (Or have the underlying visual not break in the first place, but I’m going to assume that’s not easy either).
The solution:
Here’s a code snippet which creates a “Cloning Wizard”. However, it’s pretty fragile code. It works for some situations, and you may be able to adapt it for your uses.
It takes two game objects that have identical hierarchies, and attempts to copy the structure of one onto the other.
Problems with the solution:
-
In order to ensure everything works out right, the two hierarchies must be identical with respect to name and structure. Hopefully your old model and new model have the exact same names for bones and whatnot, so this will transfer over easily.
-
You can clone a component with EditorUtil, but you can’t add that component to a GameObject. You instead have to add a component of the same type, but this will initialize to the default values in the script.
-
Using a dest.SendMessage( “DeepCopy”, src, SendMessageOptions.DontRequireReceiver) to solve problem #2 kind of works, but it’s ugly.
For any components you wrote, you can add a copy constructor of sorts, which will do a member-wise copy of relevant data, but you have to be careful not to omit anything. For any components that belong to Unity, they’re going to get set to default values. You could work around that by writing a custom script which will DeepCopy the Unity component for you, but that’s another layer of junk which can mess things up.
Finally, the source.
using UnityEditor;
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class DeepCopyComponents : ScriptableWizard {
public GameObject source = null;
public GameObject destination = null;
static bool clearFields = false;
[MenuItem( "GameObject/Deep Copy Structure")]
static void CreateWizard()
{
clearFields = true;
ScriptableWizard.DisplayWizard( "Deep Copy Structure", typeof( DeepCopyComponents ), "Copy" );
}
void OnWizardCreate()
{
foreach( CopyStructure copy in copyList )
{
Debug.Log( "Copying from : " + copy.from.gameObject.name + " to " + copy.to.gameObject.name );
CopyComponents( copy );
}
}
void CopyComponents( CopyStructure copy )
{
foreach( Component c in copy.from.GetComponents( typeof(Component) ) )
{
Component clone = copy.to.GetComponent( c.GetType() );
if( clone == null )
{
Debug.Log( " Cloning component : " + c.ToString() );
clone = copy.to.gameObject.AddComponent( c.GetType() );
}
clone.SendMessage( "DeepCopy", c, SendMessageOptions.DontRequireReceiver );
}
}
void OnWizardUpdate()
{
if( clearFields )
{
source = null;
destination = null;
clearFields = false;
}
if( source == null )
helpString = "Select the object to copy from";
else if( destination == null )
helpString = "Select the object to copy to";
isValid = false;
errorString = "";
if( source != null destination != null )
{
copyList = new List<CopyStructure>();
if( IsSameHierarchy( source.transform, destination.transform ) )
{
isValid = true;
} else
{
errorString = "The two structures do not match";
}
}
}
private class CopyStructure
{
public CopyStructure( Transform f, Transform t )
{
from = f;
to = t;
}
public Transform from;
public Transform to;
}
private List<CopyStructure> copyList = new List<CopyStructure>();
bool IsSameHierarchy( Transform firstTransform, Transform secondTransform )
{
if( firstTransform.gameObject.name != secondTransform.gameObject.name ) return false;
if( firstTransform.childCount != secondTransform.childCount ) return false;
Transform[] firstArray = new Transform[firstTransform.childCount];
Transform[] secondArray = new Transform[firstTransform.childCount];
int j = 0;
foreach( Transform child in firstTransform )
{
firstArray[j] = child;
++j;
}
j = 0;
foreach( Transform child in secondTransform )
{
secondArray[j] = child;
++j;
}
for( int i = 0; i < firstTransform.childCount; ++i )
{
bool foundMatch = false;
for( int k = 0; k < firstTransform.childCount; ++k )
{
if ( IsSameHierarchy( firstArray[i], secondArray[k] ) )
{
copyList.Add( new CopyStructure( firstArray[i], secondArray[k] ) );
foundMatch = true;
}
}
if( foundMatch == false )
{
Debug.Log( "Unable to match : " + firstArray[i].gameObject.name );
return false;
}
}
return true;
}
}