I’m working on a Guid system that works in editor and play mode I want to create a new Guid each time a specific game object is added to the scene no matter if is in play oder editor mode. I’m using [ExecuteAlways] also tried [ExecuteInEditMode]
The problem I have is when duplicating parented prefabs. When doing so the initialization of the MonoBehaviour is Awake, OnEnable, OnDisable, OnEnable, Start. This only happens for Prefabs with a parent. With the second OnEnable the override I just did in Awake disappears.
Screenshot:
Code:
using System;
using UnityEngine;
[ExecuteAlways]
public class PrefabExecuteAlways : MonoBehaviour
{
[SerializeField]
private string guid = string.Empty;
public Guid Guid
{
get
{
Guid realGuid;
if (!Guid.TryParse(guid, out realGuid))
{
realGuid = Guid.NewGuid();
guid = realGuid.ToString();
}
return realGuid;
}
set
{
guid = value.ToString();
}
}
protected void Awake()
{
var newGuid = Guid.NewGuid();
Debug.Log($"ExecuteAlways Awake {this.name}, instanceID {gameObject.GetInstanceID()}, oldGuid {newGuid}, newGuid {guid}");
Guid = newGuid;
}
protected void Start()
{
Debug.Log($"ExecuteAlways Start {this.name}, instanceID {gameObject.GetInstanceID()}, newGuid {guid} ");
}
protected void OnEnable()
{
Debug.Log($"ExecuteAlways OnEnable {this.name}, instanceID {gameObject.GetInstanceID()}, newGuid {guid} ");
}
protected void OnDisable()
{
Debug.Log($"ExecuteAlways OnDisable {this.name}, instanceID {gameObject.GetInstanceID()}, newGuid {guid} ");
}
protected void OnDestroy()
{
Debug.Log($"ExecuteAlways OnDestroy {this.name}, instanceID {gameObject.GetInstanceID()}, newGuid {guid} ");
}
}
Notes:
- This is a cross post from discussions.
- I might be able to use Start instead of Awake as workaround.
- This is a striped down piece of code for reproduction
- I’m on Unity 2022.03.10f1
You duplicate manually via Ctrl+D ?
What about the OnValidate and Reset callbacks?
No calls to SetActive anywhere?
What‘s the purpose of this system?
I did some research on making assets identifiable at runtime with a unique ID and quickly decided against GUID because they are far too big, I think 36 bytes or something like that. I had an editor-only registry system and a running number index that I could easily limit to UInt16 which made it only 2 bytes to uniquely identify prefabs, and easy to switch to Int32 if I ever had more than 65k.
Tanks for your input! I already tried some options but it also made me question some decisions.
-
Duplication can happen with Ctrl+D, Ctrl+C + Ctrl+V or context menu. Something new I found, when using “drag an drop”, taking the prefab from the hierarchy it does not happen.
-
Reset does not work because its component specific. Meaning it will not trigger if you remove the GameObject. component.
I thought about OnValidate but it happens verry often not just on creation. I also would still need [ExecuteAlways] for OnDestroy and would need to prevent Awake to run in Editor Mode.
While this is an option it feels a “bit hacky” to implement like this.
-
I’m sure, there is no SetActive therefore I build the repro case script above it’s the only thing running.
-
Also thought about using the CustomEditor callbacks but that also dir not look like the right place.
The system is for VR-Builder a node based visual authoring tool for VR trainings. It is using GUID to connect the “Training Steps” with GameObjects and everything works well except prefabs. I don’t see a reason to optimize this as there is no bottleneck. There will be usually less the 100 GUIDs in one application.
That beding said we are also planning to build a improved tagging system (not to be confused with GameObejct Tags). This will not rely on GUID for referencing but will be a lot more work to implement properly and probably will be more resource hungry. Also until now basic users preferred to use unique referencing.
Ok this now derailed form the original issue. Why Awake, OnEnable, OnDisable, OnEnable, Start. I will try to escalate this with a bug report.
I created a repro case without System.Guid for the bug report IN-60512.
using UnityEngine;
[ExecuteAlways]
public class PrefabExecuteAlways : MonoBehaviour
{
private static int counter = 0;
[SerializeField]
private int count = 0;
protected void Awake()
{
counter++;
count = counter;
Debug.Log($"ExecuteAlways Awake {this.name}, counter {counter}, count {count}");
}
protected void Start()
{
Debug.Log($"ExecuteAlways Start {this.name}, counter {counter}, count {count}");
}
protected void OnEnable()
{
Debug.Log($"ExecuteAlways OnEnable {this.name}, counter {counter}, count {count}");
}
protected void OnDisable()
{
Debug.Log($"ExecuteAlways OnDisable {this.name}, counter {counter}, count {count}");
}
protected void OnDestroy()
{
Debug.Log($"ExecuteAlways OnDestroy {this.name}, counter {counter}, count {count}");
}
}
1 Like
Could you share that repro project? I’m curious. 
I think it would be easier to just copy paste the script into any Unity project you have and create a empty scene.
Anyway here is the repo GitHub - mrwellmann/RefTest 
I will update this when I get feedback through the bug report IN-60512.
1 Like
Thanks!
I gave it a go, added logging of GetInstanceID() and ISerializationCallbackReceiver. Couldn’t make much of it other than a hunch that ExecuteAlways affects how the prefab comes into play. But strange that behaviour changes only once a parent game object comes into play.
I see that after the initial Awake => OnEnable => OnDisable (with count being > 0) the prefab runs OnBeforeSerialize, directly followed by the new instance running OnAfterSerialize and then OnEnable (the second one) on the new instance. The latter both have the expected count value.
The docs for ExecuteInEditMode say that one should use ExecuteAlways because that works (better?) with prefab edit mode.
This is the updated script:
using System;
using UnityEditor;
using UnityEngine;
[ExecuteAlways]
public class PrefabExecuteAlways : MonoBehaviour, ISerializationCallbackReceiver
{
private static Int32 counter;
[SerializeField] private Int32 count;
public void OnBeforeSerialize() =>
Debug.Log($"{GetInstanceID()}: OnBeforeSerialize: counter {counter}, count {count}");
public void OnAfterDeserialize() =>
Debug.Log($"{GetInstanceID()}: OnAfterSerialize: counter {counter}, count {count}");
protected void Awake()
{
name = GetInstanceID() + " Instance";
counter++;
count = counter;
Debug.Log(
$"{GetInstanceID()}: Awake {name}, parent: {transform.parent.name}, counter {counter}, count {count}");
#if UNITY_EDITOR
EditorApplication.playModeStateChanged += OnPlayModeStateChanged;
#endif
}
protected void Start() => Debug.Log(
$"{GetInstanceID()}: Start {name}, parent: {transform.parent.name}, counter {counter}, count {count}");
protected void OnEnable() => Debug.Log(
$"{GetInstanceID()}: OnEnable {name}, parent: {transform.parent.name}, counter {counter}, count {count}");
protected void OnDisable() => Debug.Log(
$"{GetInstanceID()}: OnDisable {name}, parent: {transform.parent.name}, counter {counter}, count {count}");
protected void OnDestroy() => Debug.Log(
$"{GetInstanceID()}: OnDestroy {name}, parent: {transform.parent.name}, counter {counter}, count {count}");
#if UNITY_EDITOR
private void OnPlayModeStateChanged(PlayModeStateChange obj) => Debug.Log($"Playmode state: {obj}");
#endif
}
You will have to close the Inspector window to stop the serialization callbacks from spamming the console.
1 Like
Unity bug ticket is confirmed and in review now IN-60512