Custom GUIDs in Unity

Introduction

If you’ve ever tried to save data for many entities in a video game, you’ve probably come across an approach that looks like this:

  1. Associate each entity with a persistent, unique ID.

  2. Create a key-value store (like a Python dictionary), using these IDs as keys and records with the relevant data for each entity as values.

  3. Write the key-value store to disk / read the key-value store from disk as necessary.

It’s possible to go into significantly more detail for each of these steps, but this article will focus primarily on the first one: assigning persistent, unique IDs.

The Problem

Specifically, this article will explain how to create a MonoBehaviour script which, when attached to a GameObject, will assign it a unique ID that persists between different play sessions.

With a script like this, you would want these IDs to be unique even in the following situations:

  1. A developer duplicates a GameObject (in the editor).

  2. A developer converts a GameObject into a prefab (in the editor).

  3. A developer instantiates a prefab (in the editor).

If a normal MonoBehaviour script were attached to the GameObject in any of these cases, then the fields on that script would be duplicated along with the GameObject.

You would have GameObjects with the same IDs as their prefabs, or duplicate GameObjects with identical IDs. This would make it impossible to save/load data for individual GameObjects.

GUIDs

First of all, let’s settle what a GUID is. In short, a GUID (also known as a UUID) is a 128-bit data structure that represents a globally unique ID.

As the name of this article implies, we’ll be using GUIDs as our IDs in this article.

Specifically, we’ll be (automatically) creating these GUIDs in the editor, so that the specific GUID value for each GameObject is part of its scene. This will keep these GUIDs the same every time the player plays the game.

Unity already uses GUIDs to refer to different assets in the editor (and you can actually see them in use if you look into the .meta or .asset files in a Unity project).

It’s worth nothing that Unity already creates unique IDs to refer to each GameObject. However, these IDs will change between player runtime and Editor sessions, making them useless for the purposes of saving/loading data.

The ExecuteAlways Attribute

In Unity, our C# scripts typically run during a player’s runtime session. That is, they execute code either when we play the game in the editor or when the player is running an actual build.

Since we’re creating GUIDs in the editor, we’ll need to execute some code in the editor itself. That is, we’ll want the script to execute before we even start the game, as we create prefabs or instantiate GameObjects while in the editor. To achieve this, we’ll use the ExecuteAlways attribute.

It’s worth noting that there is an older and similar attribute called ExecuteInEditMode, which has been deprecated. Annoyingly, this deprecation warning only shows up in the middle of its documentation (at least for Unity version 2021.3).

Example: ExecuteAlways

The syntax for the ExecuteAlways attribute is fairly straightforward. This example is taken from Unity’s documentation for the attribute:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
using UnityEngine;

[ExecuteAlways]
public class ExampleClass : MonoBehaviour
{
    void Start()
    {
        if (Application.IsPlaying(gameObject))
        {
            // Play logic
        }
        else
        {
            // Editor logic
        }
    }
}

As seen on line 3, this class has been marked with ExecuteAlways. As a result, it now has the ability to execute code at both runtime and in the editor itself.

To execute different code between runtime and the editor, we can use the Application.IsPlaying method, passing the script’s own GameObject as an argument. This method will return true if we’re in a play session, and it will return false if we’re only in the editor.

Start, Awake, and Update

With a normal script, the Start, Awake, Update methods execute when the scene is loaded and every frame, respectively. Both of these methods only run during a play session. With the ExecuteAlways attribute, these methods will execute in the editor as well as during a play session. Specifically:

  • In the editor, Start will execute when a script is first attached to a GameObject and when the scene is loaded.

  • In the editor, Awake behaves similarly to Start, except that it is executed first and it will execute even if the script component is disabled.

  • In the editor, Update will be called whenever something in the scene is changed.

We’ll be using the Awake and Update methods in our script. Though we could conceptually do everything in Update, we’ll use Awake to perform particularly expensive operations as a performance optimization.

Script Usage: ExecuteAlways

Let’s start our actual script, using these features:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
// Using statements...

[ExecuteAlways]
public class CustomGUID : MonoBehavior
{
    private void Awake()
    {
        if (!Application.IsPlaying(gameObject))
        {
            // Only in editor
        }
    }

    private void Update()
    {
        if (!Application.IsPlaying(gameObject))
        {
            // Only in editor
        }
    }
}

In this script, both our Awake and our Update methods will only execute code in the editor. They won’t execute any code when the player is actually playing the game.

Editor APIs: SerializedProperty and SerializedObject

We need the Editor APIs to properly modify our GUID field from the editor (especially if the script is attached to a prefab asset).

But before we look at the Editor APIs, let’s first look at our guid field.

Script Usage: String Field For GUID

We’ll store our GUIDs as strings, via a field:

1
2
3
4
5
6
7
8
9
// Using statements...

[ExecuteAlways]
public class CustomGUID : MonoBehaviour
{
    public string guid;

    // Awake and Update methods...
}

Though Guid already exists as a part of C#'s standard library (and we’ll be using this type to generate our GUIDs), we’ll be storing them as strings for a few reasons:

  1. Unity’s built-in serialization doesn’t directly support Guid fields, but it does support strings.

  2. By directly serializing strings, we can easily view the GUIDs in the editor itself, seeing the typical hexadecimal representation for a GUID.

From here on, if I refer to the GUID of a GameObject, I mean the guid field of a CustomGUID script that has been attached to that GameObject.

A Naive (and Somewhat Buggy) Approach

Under normal circumstances, you would update this guid field by simply assigning to it:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
// Using statements...

[ExecuteAlways]
public class CustomGUID : MonoBehaviour
{
    public string guid;

    private void Awake()
    {
        if (!Application.IsPlaying(gameObject)) {
            // Assign a new GUID as necessary
	    guid = Guid.NewGuid().ToString();
        }
    }

    // Update method not implemented...
}

When simply assigning to GameObjects or duplicating them, this code will assign GUIDs properly.

However, when this CustomGUID script is attached to a prefab, an annoying bug will appear: the script will be unable to properly read from and write to the guid field.

That is, when attached to a prefab asset, the script will always consider the guid to initially be null, even if the prefab is actually storing a valid GUID in the field. This makes it difficult to detect when we should generate a new GUID (we’ll discuss the exact algorithm for this later).

Additionally, this approach will fail to write to a script’s field in a prefab asset. Even though it would seem like values have been assigned to these fields, those values would not persist in the prefab asset itself.

To solve these problems, we will use parts of the Unity API that are normally seen in custom editors: the SerializedObject and SerializedProperty classes.

Why Use SerializedObject and SerializedProperty?

In short, SerializedObject and SerializedProperty are how you’re supposed to modify the fields of a MonoBehavior script when executing code in the Unity editor itself. When used, they will automatically support key editor functionality like undo and prefab overrides.

For our use case, we’re more interested in the fact that these classes avoid the prefab-related bugs mentioned earlier.

Editor API: SerializedObject

As its name might imply, SerializedObject represents an object and will indirectly allow us to modify the fields in this object. Specifically, it represents an object that descends from the Object class provided by Unity.

It’s worth noting that C# also provides an Object class, and that all classes in C# inherit from this class. Somewhat confusingly, this Object class is not the Object class provided by Unity.

Both GameObjects and MonoBehavior scripts descend from Unity’s Object class, so we’ll be able to use SerializedObject to represent the script that we want to modify.

Concretely speaking, we’ll only directly use a SerializedObject to gain access to a SerializedProperty.

Editor API: SerializedProperty

The SerializedProperty class allows us to actually modify an object’s field. However, in order to obtain it, we first need to create a SerializedObject (which is why I described SerializedObject first).

While using this part of the Editor API, we need to follow these 4 broad steps to modify an object’s field:

  1. Obtain a SerializedObject that represents our MonoBehavior script.

  2. Obtain a SerializedProperty from the SerializedObject that represents a specific field.

  3. Tell the SerializedProperty that you want to change the relevant field.

  4. Finalize your changes via the SerializedObject.

The following example code will show how to set our guid field via these parts of the Editor API:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// Other using statements...
using UnityEditor;

[ExecuteAlways]
public class CustomGUID : MonoBehaviour
{
    public string guid;

    private void Awake()
    {
        if (!Application.IsPlaying(gameObject)) {
            // For now, always assign a new GUID

            // Step 1
            SerializedObject serializedObject = new SerializedObject(this);

            // Step 2
            SerializedProperty guidProperty =
                serializedObject.FindProperty("guid");

            // Step 3
            guidProperty.stringValue = Guid.NewGuid().ToString();

            // Step 4
            serializedObject.ApplyModifiedProperties();
        }
    }

    // Update method not implemented...
}

The 4 statements in our Awake method shown above correspond to the 4 steps necessary for us to modify a field.

There are two potentially problematic details here:

  1. On lines 18/19, we obtain a SerializedProperty by searching for a field with the name "guid" (via a string argument to a method). This may lead to bugs if we ever change the name of this field (which we will address later in this post).

  2. As seen on line 2, we have to write a using UnityEditor statement, otherwise we won’t have access to SerializedObject and SerializedProperty from the Editor API. This will lead to problems when making builds of our game (as we will shortly discuss).

Problem: We Can’t Build!

If you tried to build a project that uses SerializedObject and SerializedProperty in a MonoBehavior script (as we demonstrated above), you would run into an annoying problem: the project would fail to build entirely.

Our problem most directly originates with our use of the UnityEditor namespace. We need this namespace to access SerializedObject and SerializedProperty, but, as stated in its documentation, we can’t reference this namespace in scripts that are compiled for a final build.

APIs in UnityEditor are typically used in when writing custom Editors in Unity. In our case, we’re not doing this. Instead, we’re using these APIs in a MonoBehavior script that has been annotated with the ExecuteAlways attribute. As with normal MonoBehavior scripts, our script will be compiled when making a build.

Here’s a key insight: even though our CustomGUID script will be compiled for a user-facing build, only part of its functionality needs to be available when the game is running.

  1. In the game itself: we need access to the GUID for each relevant GameObject. This does not require any of the Editor APIs. This essentially just requires the guid field.

  2. In the editor: we need to ensure that the GUIDs attached to GameObjects are unique, even when a GameObject is duplicated or instantiated from a prefab. This will require the Editor APIs, so that we can properly assign to the guid field from the editor itself.

With this in mind, we can make a key conclusion: we only need the Editor APIs while in the Unity editor itself.

C# Preprocessor Directives

In order to make our project build properly, we’ll be using something called C# preprocessor directives.

With this feature of C# (and its integration with Unity), we can avoid compiling the editor-specific part of our script in player-facing builds.

Example: C# Preprocessor Directives

Let’s look at a brief of example of the relevant syntax:

1
2
3
4
5
#if UNITY_EDITOR

    Debug.Log("Hello, editor!");

#endif

In the example above, the #if UNITY_EDITOR and #endif lines are preprocessor directives[1]. With these directives, the Debug.Log("Hello, editor!") line will only compile within the editor itself. When we make a full build for the game, this line (and everything between these two directives) won’t be compiled at all. From the compiler’s point of view, it’ll be as if these lines simply aren’t in our source code.

This technique is called conditional compilation, as it only compiles parts of the code when certain conditions are true (like being in the Unity editor or not).

Script Usage: C# Preprocessor Directives

Now that we have an understanding of the syntax, let’s use C# preprocessor directives in our script itself:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// Other using statements...

#if UNITY_EDITOR
using UnityEditor;
#endif

[ExecuteAlways]
public class CustomGUID : MonoBehaviour
{
    public string guid;

#if UNITY_EDITOR
    private void Awake()
    {
        if (!Application.IsPlaying(gameObject)) {
            // For now, always assign a new GUID

            SerializedObject serializedObject = new SerializedObject(this);

            SerializedProperty guidProperty =
                serializedObject.FindProperty("guid");

            guidProperty.stringValue = Guid.NewGuid().ToString();

            serializedObject.ApplyModifiedProperties();
        }
    }
#endif

    // Update method not implemented...
}

There are currently 2 blocks of code that we conditionally compile, since both of them involve the Editor APIs:

  1. The using UnityEditor statement.

  2. The Awake method.

When we’re in our editor, both of these sections of code will compile, ensuring that our script can assign different GUID values as necessary.

When making a final build, however, both sections of code will be ignored, leaving our script with no references to UnityEditor or anything in that namespace. This will allow us to successfully create a final build. This final build will still have access to the guid field, as it is not surrouded by preprocessor directives.

The nameof Expression

As we mentioned earlier, we have to obtain a SerializedProperty by searching for a field with the name "guid". If we were to change the name of our field (e.g. from "guid" to "id"), then we would need to change the argument passed to the SerializedObject.FindProperty method as well.

Fortunately, we can automatically obtain the name of our guid field using a feature of C# called the nameof expression.

As its name would imply, this expression allows us to obtain the name of a variable, type, or field as a string constant.

Script Usage: nameof

Since this feature is rather simple, let’s use just use it in our script:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// Other using statements...

#if UNITY_EDITOR
using UnityEditor;
#endif

[ExecuteAlways]
public class CustomGUID : MonoBehaviour
{
    public string guid;

#if UNITY_EDITOR
    private void Awake()
    {
        if (!Application.IsPlaying(gameObject)) {
            // For now, always assign a new GUID

            SerializedObject serializedObject = new SerializedObject(this);

            SerializedProperty guidProperty =
                serializedObject.FindProperty(nameof(guid));

            guidProperty.stringValue = Guid.NewGuid().ToString();

            serializedObject.ApplyModifiedProperties();
        }
    }
#endif

    // Update method not implemented...
}

Compared to our previous example, we’ve only changed one line: line 21.

Specifically, we changed our argument from the strong literal "guid" to the expression nameof(guid). This will have the same functionality while being more robust.

If we were to rename the guid field (perhaps to id) in an IDE like Rider, then our IDE would automatically use the new name on line 20 for us. By using this feature, we no longer need to worry about changing the argument to SerializedObject.FindProperty.

Stop Constantly Changing GUIDs

Up to this point, you may have noticed a possible quirk in our implementation: we assign a new GUID to each GameObject every time we load a scene (or fully open up a prefab).

There are a few annoying issues with this approach:

  • Whenever leaving a scene, the Unity editor will ask if we want to save, even if it doesn’t seem like we’ve changed anything (as the GUIDs of our GameObjects will have changed).

  • Our commits in git will constantly show scenes and prefabs being changed, even if we didn’t apparently modify them (as the GUIDs of their GameObjects will have changed upon opening them).

To solve these issues, we’ll use a different approach entirely: we’ll only modify a GameObject’s GUID as necessary. We will begin by showing how this approach handles the duplication of GameObjects.

Handling Duplication

As we mentioned near the beginning of this post, we need to ensure that the GUID is different when a GameObject is duplicated.

Normally, when a GameObject is duplicated, all of the fields in its attached scripts are duplicated as well.

However, we want the guid field in the CustomGUID script of a GameObject to not be duplicated (as this stores our GameObject’s GUID). How can we achieve this?

Ideally, we would want to run code to generate a new GUID whenever the GameObject is duplicated. Unfortunately, Unity doesn’t provide any direct ways to achieve this.

This forum post seems to describe an indirect way to do this, but the approach shown there will also run code when a scene is first entered, so it’s not viable for our use case. (If we generated a new GUID whenever we entered a scene, then our GameObjects would change GUIDs constantly, leaving us with the annoying issues from earlier.)

Script Usage: Handling Duplication

Since we can’t run code specifically when a GameObject is being duplicated, we’ll simply have each CustomGUID script check the other GUIDs to see if our current GUID is a duplicate:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// Just the Awake method is shown here

#if UNITY_EDITOR
    private void Awake()
    {
        if (!Application.IsPlaying(gameObject))
        {
            SerializedObject serializedObject = new SerializedObject(this);

            SerializedProperty guidProperty =
                serializedObject.FindProperty(nameof(guid));

            // Potential performance problem, but fine for dozens of GameObjects
            CustomGUID[] customGuids = FindObjectsOfType<CustomGUID>();
            foreach (CustomGUID customGuid in customGuids)
            {
                if (customGuid.guid == guid && customGuid != this)
                {
                    guidProperty.stringValue = NewGuid();
                }
            }

            serializedObject.ApplyModifiedProperties();
        }
    }
#endif

Though this approach will correctly prevent duplicate GameObjects from having the same GUIDs, it has a potential performance problem. Since each CustomGUID will have to check all of our CustomGUID scripts, this section of code will have a run time of O(n2), which will be abysmal with large numbers of CustomGUID scripts (i.e. large numbers of GameObjects with CustomGUID scripts attached).

Perhaps due to the speed of modern computers (and the fact that this code is in Awake rather than Update), this performance is fine when you have dozens and dozens of GameObjects in your scene. However, as you approach hundreds of GameObjects, the performance problems become significantly worse, at least on the gaming laptop I used for testing.

To solve this performance problem, you can cache the GUIDs that have been used so far. Using a Dictionary or a HashSet in a static variable for this cache will allow you to achieve rapid look-ups and avoid redundant insertions.

I won’t go into too much detail regarding this optimization, as this post is primarily focused on the core techniques and features I used to set up persistent GUIDs in Unity. The underlying approach is the same regardless of whether or not you cache currently-used GUIDs, and the code shown here is optimized enough for use as an explanatory example.

Handling New GameObjects

When first attaching a CustomGUID script to a GameObject, we (perhaps obviously) need to give it a GUID.

To avoid having a new GUID every time we load the scene, we’ll use null as the default value for our guid field (null is already the default value for a string field) and then initialize guid if it has not been initialized before:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
// Inside the CustomGUID class

#if UNITY_EDITOR
    private void Update()
    {
        if (!Application.IsPlaying(gameObject))
        {
            SerializedObject serializedObject = new SerializedObject(this);

            SerializedProperty guidProperty =
                serializedObject.FindProperty(nameof(guid));

            if (String.IsNullOrEmpty(guidProperty.stringValue))
            {
                guidProperty.stringValue = NewGuid();
            }

            serializedObject.ApplyModifiedProperties();
        }
    }
#endif

Additionally, since we’re now generating GUIDs in more than one place, let’s refactor that GUID-generation code into its own little method:

1
2
3
4
5
6
    // Inside the CustomGUID class

    private string NewGuid()
    {
        return Guid.NewGuid().ToString();
    }

It’s fine for us to place this code in Update, since checking the GUID every time something in the scene changes is cheap—​cheap enough to be negligible.

In the Update method, we reuse many of the same features we saw before: Editor APIs (SerializedObject and SerializedProperty), the nameof expression, and functionality related to ExecuteAttribute (Application.IsPlaying). Since we’re familiar with these features already, our functionality is fairly straightforward.

The only particularly new detail is the use of String.IsNullOrEmpty. This is just a little utility function from C#'s standard library which checks if a string is null or empty.

Checking for an empty string is useful for debugging purposes, as it allows us to force new GUIDs to be generated from the editor. Simply by clearing the guid field from the Inspector window, we can force this script to generate a new GUID. This benefit is also only possible in the Update method. If we were to check for null or empty in the Awake method, then we wouldn’t see an instant response, as we would have to wait until the Awake method is next called (probably the next time we leave and re-open the scene).

Handling Prefab Instances

We want to ensure that the GUID of our prefab is different from the GUID of any of its instances.

Fortunately, the Editor APIs provide a fairly straightforward way to accomplish this:

  • The SerializedProperty.isInstantiatedPrefab property tells us if our property is part of a script which is attached to a prefab instance.

  • The SerializedProperty.prefabOverride property tells us if the property has been modified from the property’s value in its parent prefab (assuming that the property is attached to a prefab instance).

Script Usage: isInstantiatedPrefab and prefabOverride

Let’s use these 2 properties to check if the GUID of a prefab instance is the same as the GUID of its prefab:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
// Inside the CustomGUID class

#if UNITY_EDITOR
    private void Update()
    {
        if (!Application.IsPlaying(gameObject))
        {
            SerializedObject serializedObject = new SerializedObject(this);

            SerializedProperty guidProperty =
                serializedObject.FindProperty(nameof(guid));

            if (String.IsNullOrEmpty(guidProperty.stringValue) ||
                (guidProperty.isInstantiatedPrefab && !guidProperty.prefabOverride))
            {
                guidProperty.stringValue = NewGuid();
            }

            serializedObject.ApplyModifiedProperties();
        }
    }
#endif

Our changes can be seen on line 14, with the addition of new conditions to our if statement.

By checking the negation of guidProperty.prefabOverride specifically, we can check if the guid field in our prefab instance is the same as in our original prefab asset.

Using this fact, this code directly checks if our script is part of a prefab instance and if our guid field is the same as the original prefab asset. If so, it generates and assigns a new GUID.

Handling Prefabs (EditorAPIs)

Ideally, we want the GUID of a prefab itself to be null, to prevent us from saving/loading information for a prefab when we mean to be saving/loading information for its instances.

That is, if we ever accidentally access the GUID of a prefab (while intending to access the GUID of one of its instances), we want a glaring null value to show us that we’re doing something wrong.

Ideally, we would want to execute some code whenever we create a new prefab. Unfortunately, as far as I can tell, Unity does not offer any way for us to do this.

As a result, I’ve adopted this approach: set the guid field for a CustomGUID script to null as soon as we’ve detected that it’s attached to a prefab asset.

There are two places in the Unity editor where we might modify a prefab:

  1. While we have a scene open, and we are only previewing the prefab (viewing the prefab solely in the inspector).

  2. When we have the prefab opened fully, in its own special scene which exists just to edit that prefab.

To detect whether or not a CustomGUID script (or any other script) is attached to a prefab asset, we have to detect both of these cases.

PrefabStage and PrefabStageUtility

Despite my best efforts, I was only able to detect the second case, when we have the prefab fully opened in its own special scene.

To accomplish this, I used the PrefabStage and PrefabStageUtility classes, which are part of the editor API.

  • A PrefabStage represents a special, prefab-specific scene that allows us to edit all of the scenes of a prefab. In the editor, we normally reach this scene by double-clicking on a prefab.

  • PrefabStageUtility contains multiple utility methods, one of which allows us to check if we’re currently in a special prefab scene.

Script Usage: PrefabStage and PrefabStageUtility

Let’s create a method which will check if our script is attached to a Prefab asset (while in a special prefab scene):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// Inside the CustomGUID class

#if UNITY_EDITOR
    private bool IsPrefab()
    {
        PrefabStage currPrefabStage = PrefabStageUtility.GetCurrentPrefabStage();

        return currPrefabStage != null &&
               currPrefabStage.IsPartOfPrefabContents(gameObject);
    }
#endif

These new Editor APIs are located in the UnityEditor.SceneManagement namespace, so we’ll need to add a new conditionally-compiled using statement:

1
2
3
4
#if UNITY_EDITOR
using UnityEditor;
using UnityEditor.SceneManagement;
#endif UNITY_EDITOR

Now, let’s use this IsPrefab method in our Update method:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// Inside the CustomGUID class

#if UNITY_EDITOR
    private void Update()
    {
        if (!Application.IsPlaying(gameObject))
        {
            SerializedObject serializedObject = new SerializedObject(this);

            SerializedProperty guidProperty =
                serializedObject.FindProperty(nameof(guid));

            if (IsPrefab())
            {
                guidProperty.stringValue = null;
            }
            else if (String.IsNullOrEmpty(guidProperty.stringValue) ||
                     (guidProperty.isInstantiatedPrefab &&!guidProperty.prefabOverride))
            {
                guidProperty.stringValue = NewGuid();
            }

            serializedObject.ApplyModifiedProperties();
        }
    }
#endif

The major changes here come on lines 13-16, where we check if we’re in a full prefab scene and set the GUID to null accordingly.

It’s important that line 17 be an else if rather than another if statement, otherwise we’ll end up generating a new GUID immediately after setting our field to null.

Now, whenever we fully open a prefab in the Unity editor, its GUID will be automatically set to null.

What About Prefab Previews?

As I alluded to earlier, I never managed to detect the first prefab case (which is when we have a normal scene open in the editor and a prefab previewed in the Inspector window).

Though this is not ideal, it’s still acceptable, and the script is still usable. Even if we never open a prefab fully and the prefab continues to have a non-null GUID, our changes from the Handling Prefab Instances section will prevent us from facing any bugs outright.

In the future, as I continue to use this script, I may discover how to handle this case.

Full Script

Download here for the full script.

Hopefully, this post helps you understand how this script operates, in case you ever want to modify it.


1. These are called preprocessor directives because they are heavily inspired by a similar mechanism associated with the C programming language. When programming with C, the preprocessor performs "dumb" textual manipulation of the source code before the compiler actually parses and compiles anything into machine code. Traditionally, the preprocessor was a separate program entirely, being invoked by the compiler before anything else was done.

In C#, the compiler doesn’t use a separate preprocessor, but it does process each of these directives as if there were a separate program running before the rest of the compiler.