- Home /
How to call a method when a GameObject has been duplicated?
I need to do some stuff on a Component when it's GameObject got duplicated. Did anyone find out how to? This has especially to work in the Editor - with CMD + D or Copy Paste... but is not restricted to it. I'd favor something generic - but an explicit approach would be a nice start.
Sure, --- it would be possible to hook onto the Unity command events like...
if( (e.type == EventType.ValidateCommand)
&&((e.commandName == "Duplicate") || (e.commandName == "Paste")) )
{
PostDuplicateMethod();
}
... but I don't want to do that - because only one component class would need that special treatment - and I'd really prefer not to go through all GameObjects to check if they have a Component of my Type.
If someone could show me how to hook direct onto the duplication process - that would be awesome!!!
I presume it's something in UnityEngine.Object Instantiate but - how can I find out if this was called for instances having my component...
At this point I have to go through all instances of my Component on Awake()!!! and check quite expensive if they have been copied... (they hold a wrong reference - nothing anyone else could use)
I would really appreciate any hint
Probably I should explain what I try to achieve -
The Component holds a List of Properties which derive from ScriptableObject. And these have to be cloned too when the Component gets duplicated.
Answer by AnikKhoma · Jun 15, 2015 at 05:07 AM
[ExecuteInEditMode]
public class DuplicateCatcher : MonoBehaviour {
[SerializeField] int instanceID = 0;
void Awake(){
if (instanceID != GetInstanceID()){
if (instanceID == 0){
instanceID = GetInstanceID();
}
else {
instanceID = GetInstanceID();
if (instanceID < 0){
Debug.Log("DUPLICATE/COPY");
}
}
}
}
}
Beautiful - combine this with @$$anonymous$$2_Games' answer and it works great. (However, I'd put the #if UNITY_EDITOR compile flag around the entire method)
Example:
[ExecuteInEdit$$anonymous$$ode]
public class DuplicateCatcher : $$anonymous$$onoBehaviour
{
#if UNITY_EDITOR
//catch duplication of this GameObject
[SerializeField]
int instanceID = 0;
void Awake()
{
if (Application.isPlaying)
return;
if (instanceID == 0)
{
instanceID = GetInstanceID();
return;
}
if (instanceID != GetInstanceID() && GetInstanceID() < 0)
{
Debug.LogError("Detected Duplicate!");
instanceID = GetInstanceID();
Setup(); //Do whatever you need to set up the duplicate
}
}
#endif
}
While this is the closest solution to perfection I can find. It seems to cause prefab instances to accidentally fire when loading a scene for the first time.
Could you explain the code a bit? Specifically about the behavior of GetInstanceID? Why does having an instanceID below zero constitute a duplicate? Freshly created components are very likely to also receive negative instance ID as far as my tests show. Are we certain that GetInstanceID never returns 0 itself? From Unity documentation and information I find very little guarantees or explanations about the behavior of instanceID and these examples make a lot of assumptions about the instanceID behavior.
A. the instanceid<0 check is nonsense. It should be
if(instanceID == 0 || instanceID != GetInstanceID())
secondly this won't work because you need to do EditorUtility.SetDirty(this) after you assign an ID or it won't save on scene save. otherwise it's a decent fix. personally I would then do SendMessage("OnClone") and [RequireComponent("DuplicateCatcher"]] and void OnClone() on any object that has it in, wrapped in #if UNITY_EDITOR #endif. (I wouldn't use delegates because you can't guarantee you can hook into it before it's called)
You can also use Monobehaviour.OnValidate which will call after you clone
@$$anonymous$$RoseGames it's not nonsense, for some completely undocumented reason copied objects seem to consistently get a negative instance id (just tested on 2020.3). By the way, relying on a [SerializeField] to check if the instance id changed will get you false positives whenever you reload the scene because instance ids are reassigned but the serialized field still holds the old value.
Answer by LaireonGames · May 01, 2015 at 03:45 PM
Bit of an ancient thread but I stumbled across it and thought I'd add my findings. Awake sounded strange to me since it requires processing during game time rather than in the editor so this is what I ended up doing. First give your script the ExecuteInEditMode tag then for your update function and before your other code:
void Update()
{
#if(UNITY_EDITOR)
if(!Application.isPlaying)//if in the editor
{
if(instanceID != gameObject.GetInstanceID())//if the instance ID doesnt match then this was copied!
Debug.Log("Called: " + gameObject + ", " + transform.parent);
else if(instanceID == 0)
instanceID = gameObject.GetInstanceID();//this object wasnt copied but set its ID to check for further copies
return;//prevent any actual code from running
}
#endif
}
This currently just prints when the object is copied but this is where you could place your logic to update the references.
Answer by Jamora · Jun 29, 2013 at 12:41 PM
Sounds like you could use an EditorWindow. You can duplicate your stuff, then call whatever needs to be called afterwards, even based on type or name if you feel like coding that much.
Here's a little something to get you started:
using UnityEngine;
using UnityEditor;
using System.Collections;
public class DuplicatorWindow : EditorWindow {
[MenuItem ("Custom/Duplicator")]
public static void Init(){
DuplicatorWindow window = EditorWindow.GetWindow<DuplicatorWindow >();
window.Show();
}
GameObject selectedObject = null;
void OnGUI(){
selectedObject = Selection.gameObjects[0];
if(selectedObject == null)
EditorGUILayout.LabelField("No Objects selected.");
else{
EditorGUILayout.LabelField(selectedObject.name);
if(GUILayout.Button("Duplicate")){
DuplicateGameObject();
PostDuplicateMethod();
}
}
Repaint();
}
private void DuplicateGameObject(){
/*Do all duplication here.*/
}
private void PostDuplicateMethod(){
/*Whatever needs to be done after duplication*/
}
}
N.B. This code needs to be placed inside the Assets/Editor folder. To access it, there will be a new menu item, Custom, in your menubar ( the one with File, Edit etc).
Edit: While I think the EditorWindow approach is better, because there is one button that does whatever needs be done, what you're asking can also be done in a custom inspector:
[CustomEditor(typeof(MyComponent))]
public class Duplicator : Editor {
void OnSceneGUI(){
Event e = Event.current;
if(e != null && e.type == EventType.ValidateCommand && (e.commandName =="Paste" || e.commandName =="Duplicate" )){
PostDuplicateMethod();
}
}
private void PostDuplicateMethod(){
Debug.Log("Post Duplication");
/*Whatever needs to be done after duplication*/
}
}
That requires the Scene view to have keyboard control, so extra care must be taken in that regard. (E.g. right.clicking on the scene view with an asset selected, or selecting a GameObject from the scene view.) If you need to hook into the event from where ever, you'll need to code your own Keyboard hook, much like this one http://code.google.com/p/bunchafunk/source/browse/trunk/ClipboardHistory.Net/ClipboardHistory.Net/Utils/KeyboardHook.cs?r=64 .
You'll need a scripted solution to react to any Object.Instantiate, I recommend using Awake(). Unity Editor can't help you there... unless you plan to have the game played from the Editor.
Hmm - thanks Jamora - but this is not what I meant. I need to do some custom methods after the GameObject has been duplicated - how ever that happened. When the Unity Editor User duplicates a GameObject via $$anonymous$$eyboard shortcuts or via $$anonymous$$enu/Edit/Duplicate (or Copy Paste etc) Or if some other programmer duplicates the Object via Object.Instantiate
So unfortunately to have a custom duplicate method in the menu isn't an option.
But thanx for the suggestion :)
Thank you again Jamora for the extended Editor Version - but unfortunately this isn't going to work for me. I was locking for a solution hooking onto the internal duplication process. Having this in a custom inspector would not suffice because the component could be somewhere down in the hierarchy - and not applied to the current selected gameobject(s). Is there any possibility to do so? Currently I check for changes on Awake() - but that's just a bad workaround - for as this comes for all "instantiations" of my Component - not only if it's duplicated. Here what I do to check (works - but is somewhat slow)
public void UpdateButtonlogicRefs()
{
if (m_ButtonLogics != null)
{
for (int i = 0; i < m_ButtonLogics.Count; i++)
{
if (m_ButtonLogics[i].m_button != this) // So obviously this Button has been cloned --- >
{
// so the logic needs to be cloned, too and to be reassign to this button
m_ButtonLogics[i] = this.Instantiate<RyGUIbuttonLogic>(m_ButtonLogics[i]);
m_ButtonLogics[i].SetButton(this);
Debug.LogWarning("Button " + this.name + " Reference Update for Buttonlogic: " + m_ButtonLogics[i].GetType().Name);
}
}
}
}
forgot to mention - I can tell that the Button has been duplicated because each ButtonLogic holds a ref of the Button it has been added to (created for)
The whole problem is - that when the Button gets duplicated - only the pointers of the Buttonlogics get copied.
But this is all custom - and I'm looking for a more generic - general approach. Like overriding the public static UnityEngine.Object Instantiate(UnityEngine.Object original) process - only for this - but that's a static... - and so I'm stuck
Have you considered doing a manager class that keeps track of all the references? Then in Awake(), or if they ever need to be updated, you just call ButtonReference$$anonymous$$anager.GetRefrences(this)
In the manger class, they could be stored in a dictionary, which makes for easy checking if the key is the instance that makes the queries... I was thinking something like this:
static Dictionary<$$anonymous$$onoBehaviour,ScriptableObject[]> buttonReferences = new Dictionary<$$anonymous$$onoBehaviour, ScriptableObject[]>();
public static ScriptableObject[] GetRefrences($$anonymous$$onoBehaviour caller){
ScriptableObject[] result;
if(!buttonReferences.Contains$$anonymous$$ey(caller)){
/*$$anonymous$$ake new refrences here and add them to dictionary*/
}
result = buttonReferences[caller];
return result;
}
Thanks Jamora - but if it's not possible to hook directly onto the duplication process for eg. to override somehow Unities internal cloning process
private static UnityEngine.Object INTERNAL_CALL_Internal_InstantiateSingle(UnityEngine.Object data, ref Vector3 pos, ref Quaternion rot);
the best way is it to do the check on Awake() like you suggested.
Because the problems are primarily the editor copies made by the Artists - for those it'll be enough to check only in editor mode - so the release applications will not be afflicted by doing so:
public override void Awake()
{
base.Awake();
#if UNITY_EDITOR
UpdateButtonlogicRefs();
#endif
}
With some hundred buttons in an application - that may have in average 2 Buttonlogics applied to them - this is acceptable. That another programmer could instantiate a hierarchy with a button applied remains an issue though. But programmers seem to pay more attention to a "don't do this, because of" warning than Artist do :) Probably because it's their time wasted on a bug hunt :)
So - my initial question is not really answered - probably because it can't be done - but for your efforts and good suggestions that may help others I give you a thumb up - and my personal sincere thanks.
I just then had to find out that I'm not entitled to vote...
Answer by JDAUL · Jan 10, 2021 at 06:18 AM
My simple to implement solution was to check if the filename contains " 1" and then perform some action.
[CustomEditor(typeof(Some_Data))]
public class Some_Data_Editor : Editor
{
private void Awake()
{
Some_Data data = this.target as Some_Data;
var assetPath = AssetDatabase.GetAssetPath(data.GetInstanceID());
var fileName = System.IO.Path.GetFileNameWithoutExtension(assetPath);
if (fileName.Contains(" 1"))
{
AssetDatabase.RenameAsset(assetPath, "New Data");
data.Uid = System.Guid.NewGuid().ToString();
}
}
//Rest of editor code here
Your answer
Follow this Question
Related Questions
How to duplicate a GameObject? 2 Answers
Instantiated GameObject Not Rendering 1 Answer
Instantiate from array into array? 2 Answers
Creating new Transform from existing objects Transform to Instantiate object 1 Answer
How to create a new camera and NOT have it own the view (but still Find-able)? 1 Answer