'Unique unchanged identifier for gameobject in unity

I have to store information about every object (gameobject/mesh) in an excel file. Like I have a sphere object I am getting its instanceId and saving it in excel. Within the excel file along with object instance id i am saving additional things like object display name, type.

  objInstanceId = transform.GetInstanceID();

My question is that is the safest way? I checked that instanceID is unique but I am not sure if it is gets changed ever? Like if I replace my model will it get changed? I need a unique and unchanged identifier for every single mesh so that I can save it in an excel file and associate some data with the identifier!



Solution 1:[1]

Let's take a look at the docs:

The instance ID of an object is always unique. The ID changes between player runtime and Editor sessions. As such, the ID is not reliable for performing actions between the Editor and runtime sessions, for example, loading an object state from a save file.

https://docs.unity3d.com/2021.1/Documentation/ScriptReference/Object.GetInstanceID.html

So within a runtime session the id is guaranteed to be unique. Between sessions the ids may change.

In other words, exporting all instance ids, adding/changing values and then importing them again will work within a runtime session (when the app is running).

It won't work when you close the app and start another runtime session, in which case any created objects will have different ids.

Solution 2:[2]

If you want a unique persistent ID I would use a serialized field and make it hidden like e.g.

    #if UNITY_EDITOR
using UnityEditor;
#endif

using UnityEngine;
using System.Collections.Generic;
using System.Linq;

// See https://docs.unity3d.com/ScriptReference/ExecuteInEditMode.html
[ExecuteAlways]
public class UniqueID : MonoBehaviour
{
    // Serialize/saves the unique ID
    // but hide it in the Inspector -> we don't want to edit it manually
    // uint allows around 4 Million IDs that should be enough for most cases ;)
    [HideInInspector] private uint _id;
    private static List<uint> usedIds;

    // Public read-only accessor
    public uint ID => _id;

#if UNITY_EDITOR
    // Due to ExecuteAllways this is called once the component is created
    private void Awake()
    {
        if (!Application.isPlaying && _id == 0)
        {
            _id = GetFreeID();
            usedIDs.Add(_id);

            EditorUtility.SetDirty(this);
        }
    }
#endif

    // For runtime e.g. when spawning prefabs
    // in that case Start is delayed so you can right after instantiating also assign a specific ID
    private void Start()
    {
        if (Application.isPlaying && _id == 0) _id = GetFreeID();
    }

    // Allows to set a specific ID e.g. when instantiating on runtime
    public void SetSpecificID(uint id)
    {
        if (Application.isPlaying) _id = id;
        else Debug.LogWarning("Only use in play mode!", this);
    }

    // Stores all already used IDs
    private readonly static HashSet<uint> usedIDs = new HashSet<uint>();

    private static readonly System.Random random = new System.Random();

    // Get a random uint
    private static uint RandomUInt()
    {
        uint thirtyBits = (uint)random.Next(1 << 30);
        uint twoBits = (uint)random.Next(1 << 2);
        return (thirtyBits << 2) | twoBits;
    }

    // This is called ONCE when the project is opened in the editor and ONCE when the app is started
    // See https://docs.unity3d.com/ScriptReference/InitializeOnLoadMethodAttribute.html
    [InitializeOnLoadMethod]
    [RuntimeInitializeOnLoadMethod]
    private static void InitUsedIDs()
    {
        // Find all instances of this class in the scene
        var instances = FindObjectsOfType<UniqueID>(true);
        foreach (var instance in instances.Where(i => i._id != 0))
        {
            usedIds.Add(instance._id);
        }

        foreach (var instance in instances.Where(i => i._id == 0))
        {
            instance._id = GetFreeID();

#if UNITY_EDITOR
            // See https://docs.unity3d.com/ScriptReference/EditorUtility.SetDirty.html
            EditorUtility.SetDirty(instance);
#endif

            usedIDs.Add(instance._id);
        }
    }

    private static uint GetFreeID()
    {
        uint id = 0;

        do
        {
            id = RandomUInt();
        }
        while (id == 0 || usedIDs.Contains(id));

        return id;
    }
}

Note: Typed on smartphone but I hope the idea gets clear.

There might be some edge cases but I hope that gets you started.


As an alternative you could probably also simply use two SerializedDictionary and directly reference forth and back between any type (like GameObject or Mesh) and a unique key (like e.g. the uint).

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 sommmen
Solution 2