- Home /
How to reset a component on duplication?
I have a component, that I want to automatically reset when the game object it is attached to is duplicated / cloned in the editor.
I have tried doing this with a stored ID, and every time you inspect the component it looks through the whole scene for other objects with the same ID and if there is one, resets itself. But this is a very clumsy way of doing it, and it's very specific (I have to individually reset every variable).
Is there a way to reset a component?
Is there a way to catch duplication?
Is it possible to do this from a custom editor instead of the component itself (lets say I don't have access to the component's script)?
Is it supposed to be an editor duplication event, or a run-time duplication event? Have you tried calling Reset() during OnEnable() ?
It indeed is supposed to be editor duplication, which does not call OnEnable().
Answer by CaseyHofland · Aug 11, 2020 at 09:58 PM
Late but for new readers: Here is the definitive way to do it!
public class Foo : MonoBehaviour
#if UNITY_EDITOR
, ISerializationCallbackReceiver
#endif
{
private GameObject childObject;
#if UNITY_EDITOR
[HideInInspector] [SerializeField] private GameObject _childObject;
private bool isDuplicate = true;
public void OnBeforeSerialize()
{
// Security check in case of Reset.
if(childObject)
{
// Serialize the childObject for the next time we duplicate.
_childObject = childObject;
}
}
public void OnAfterDeserialize()
{
if(isDuplicate)
{
// Delay these calls since gameObjects can't be accessed during Deserialization.
UnityEditor.EditorApplication.delayCall += OnDuplicate;
UnityEditor.EditorApplication.delayCall += Reset;
}
// Security check in case of domain reload.
if(_childObject)
{
// Reassign the serialized childObject.
chilldObject = _childObject;
}
}
private int siblingIndex = -1;
private void OnDuplicate()
{
// Destroy the duplicated child.
siblingIndex = childObject.transform.GetSiblingIndex();
DestroyImmediate(transform.GetChild(siblingIndex).gameObject);
}
private void Reset()
{
// Create a new child and set it to our siblingIndex (in case of duplication).
isDuplicate = false;
childObject = new GameObject();
childObject.transform.SetParent(transform);
if(siblingIndex > -1)
{
childObject.transform.SetSiblingIndex(siblingIndex);
}
}
#endif
}
This way we can do whatever junk we need to do in OnDuplicate (if we need it) and still call Reset afterwards. Since this is all done with Serialization, we can keep our Awake method clean and wrap this all in an #if UNITY_EDITOR, no [ExecuteAlways] required!
If you do experience trouble with this please let me know.
But won't this trigger OnDuplicate after every recompile/reloading since isDuplicate is not getting serialized?
Answer by Max Kaufmann · Oct 28, 2010 at 02:17 AM
You could save the "restored" version as a prefab, and instance off of that?
Hm. Using a prefab would be tricky, as I then need very specific code to get instantiate the prefab, get the right component out of it, read its data and remove the instance again. Plus I have to create prefabs for pretty much everything.
But the most important issue for me is to catch when the duplication actually happens.
Answer by IzzySoft · Dec 29, 2014 at 11:24 PM
Hmm... Have you tried:
[ExecuteInEditMode]
public class myObject : MonoBehaviour
{
[NonSerialized] public bool isDuplicate = true;
void Awake()
{
myObject mo = this as myObject;
Debug.Log( "In Awake. isDuplicate: " + myObject.isDuplicate );
if( myObject.isDuplicate )
Reset();
}
void Reset()
{
myObject mo = this as myObject;
// re-set/intialize NonSerialized members, shared meshes, etc...
myObject.isDuplicate = false;
}
}
One drawback is, Reset() will get called 2 times when you Duplicate your object in the Unity editor. Sorry. :/
Answer by bjennings76 · Jun 17, 2015 at 06:26 AM
I wasn't able to find a good way to directly call the 'Reset' function that the Inspector menu has, but I was able to cheat by deleting/recreating the component when it's instance ID doesn't match the saved one:
[ExecuteInEditMode]
public class ResetTest : MonoBehaviour
{
public int TestNumber;
[HideInInspector] public int MyID;
private void OnEnable()
{
if (MyID == GetInstanceID()) return;
if (MyID == 0) MyID = GetInstanceID();
else
{
gameObject.AddComponent(GetType());
DestroyImmediate(this);
}
}
}
Down side is this will 'move' the component to the end of the component list on the duplicated object. If your component order is important, that would be a problem.
If you don't want to use the ever-finicky [ExecuteInEditMode], you can do the same trick from a custom editor:
[CustomEditor(typeof (ResetTest))]
public class ResetTestEditor : Editor
{
private void OnEnable()
{
var t = (ResetTest) target;
if (t.MyID == t.GetInstanceID()) return;
if (t.MyID == 0) t.MyID = t.GetInstanceID();
else
{
t.gameObject.AddComponent(t.GetType());
DestroyImmediate(t);
}
}
}
Which also cleans up the the class:
public class ResetTest : MonoBehaviour
{
public int TestNumber;
[HideInInspector] public int MyID;
}
That doens't work because GetInstanceID() would return a different value each for the same objects time you load the scene.