So, let me clarify my exact issue here:
I am making a system in which certain GameObjects will get a unique id, that is not only unique, but persists over Unity Editor updates (I’ll explain the reason I mention this specifically), game quits/etc.
I already know how to:
Save data to file to persist over game quits/plays.
Create unique IDs with Guids.
For now I have been manually setting new ID’s with a custom context menu and method that simply creates a new Guid.
I have tried/combined these two ideas (I did try many others but these two were the closest to being successful):
(I event tried copying the code line for line in case I typed something incorrectly and it still would not work…)
This semi worked. It created new IDs for every new gameobject but since unity’s build in InstanceID changes everytime the unity editor reloads/scene loads/etc this broke and regenerated the IDs, making persisting IDs impossible.
Is there any simple solution or code that would either make IDs different as soon as this “UniqueID” component is added to a GameObject or a way to keep track of these objects (without a decrease in Unity Editor performance) and recreate IDs if any match/gameobject duplicated?
There are a few proposed solutions for this online, but none of them worked for me. The position-based GUID is nice, but breaks if I move one of my game objects.
I came up with another solution, which seems to work for me. It copes with moving your game objects, duplicating them, having prefabs as well as additive scene loading (i.e. the id here is guaranteed to be unique across scenes). It doesn’t require any editor scripts and has no run-time overhead when you make a non-editor build.
I’ve added some comments explaining how it works which might make it look a bit verbose.
// Script for generating a unique but persistent string identifier belonging to this
// component
//
// We construct the identifier from two parts, the scene name and a guid.
//
// The guid is guaranteed to be unique across all components loaded at
// any given time. In practice this means the ID is unique within this scene. We
// then append the name of the scene to it. This ensures that the identifier will be
// unique accross all scenes. (as long as your scene names are unique)
//
// The identifier is serialised ensuring it will remaing the same when the level is
// reloaded
//
// This code copes with copying the game object we are part of, using prefabs and
// additive level loading
//
// Final point - After adding this Component to a prefab, you need to open all the
// scenes that contain instances of this prefab and resave them (to save the newly
// generated identifier). I recommend manually saving it rather than just closing it
// and waiting for Unity to prompt you to save it, as this automatic mechanism
// doesn't always seem to know exactly what needs saving and you end up being re-asked
// incessantly
//
// Written by Diarmid Campbell 2017 - feel free to use and ammend as you like
//
using UnityEngine;
using System.Collections.Generic;
using System;
#if UNITY_EDITOR
using UnityEditor;
using UnityEditor.SceneManagement;
#endif
[ExecuteInEditMode]
public class UniqueId : MonoBehaviour {
// global lookup of IDs to Components - we can esnure at edit time that no two
// components which are loaded at the same time have the same ID.
static Dictionary<string, UniqueId> allGuids = new Dictionary<string, UniqueId> ();
public string uniqueId;
// Only compile the code in an editor build
#if UNITY_EDITOR
// Whenever something changes in the editor (note the [ExecuteInEditMode])
void Update(){
// Don't do anything when running the game
if (Application.isPlaying)
return;
// Construct the name of the scene with an underscore to prefix to the Guid
string sceneName = gameObject.scene.name + "_";
// if we are not part of a scene then we are a prefab so do not attempt to set
// the id
if (sceneName == null) return;
// Test if we need to make a new id
bool hasSceneNameAtBeginning = (uniqueId != null &&
uniqueId.Length > sceneName.Length &&
uniqueId.Substring (0, sceneName.Length) == sceneName);
bool anotherComponentAlreadyHasThisID = (uniqueId != null &&
allGuids.ContainsKey (uniqueId) &&
allGuids [uniqueId] != this);
if (!hasSceneNameAtBeginning || anotherComponentAlreadyHasThisID){
uniqueId = sceneName + Guid.NewGuid ();
EditorUtility.SetDirty (this);
EditorSceneManager.MarkSceneDirty (gameObject.scene);
}
// We can be sure that the key is unique - now make sure we have
// it in our list
if (!allGuids.ContainsKey (uniqueId)) {
allGuids.Add(uniqueId, this);
}
}
// When we get destroyed (which happens when unloading a level)
// we must remove ourselves from the global list otherwise the
// entry still hangs around when we reload the same level again
// but now the THIS pointer has changed and end up changing
// our ID
void OnDestroy(){
allGuids.Remove(uniqueId);
}
#endif
}
You can use System.Guid
When generating an ID it’s guaranteed to be unique. The only thing you need to do is to generate and store it in the gameObject.
To do this you can create in a component a public string field (Since unity doesn’t serialize properly the GUIDs) like this:
public string mGuid = System.Guid.NewGuid().ToString(); //Unique persistent ID
And that’s all you have to do to have a unique id that is persistent. Yet, remember that when you clone an object the GUID will remain so it will be great to create an editor component that takes care of updating the value.
I actually wrote an open-source Unity package to solve this specific problem called Fluid Unique ID. It checks across scenes for duplicate and null IDs with a GUI. It also supports handling prefabs and no coding required to use it.
I stumbled on this thread trying to grapple with the same problem the OP has outlined, and after a lot of trial and error with the proposed solutions in this thread, I’ve finally got it to work - for anyone looking for a full guide on how to accomplish this, I’ve put one together here:
I flew by this exact problem when I was creating my save/load system for objects.
What I ended up using was actually stupidly simple: Float which is object’s position.SqrMagnitude
Since all saved in-game objects I use are sort of “pick ups” or interactables, they all have their collision boxes and physics. No two objects will occupy exact same space. This is all but guaranteed to be unique for any 3D application.
This is only used for “placable” types, i.e. pickups placed from within the editor. They use IDs (generated at Start, before any physics takes place) to know if they’ve been picked up, from a list of picked up IDs.
Spawnable or droppable types do not need IDs, since they are spawned from a list of saved objects.
public float GetID ( Transform obj )
{
float a = 0;
System.Text.StringBuilder s = new System.Text.StringBuilder();
s.Append(obj.name);
Transform parent = obj.parent;
a += obj.GetSiblingIndex();
while (parent != null)
{
s.Append(parent.name);
a += parent.GetSiblingIndex();
parent = parent.parent;
}
string name = s.ToString();
char[] c = name.ToCharArray();
foreach (var item in c)
{
a += (int)item;
}
Vector3 pos = obj.transform.position;
Vector3 rot = obj.transform.eulerAngles;
Vector3 scale = obj.transform.localScale;
a += pos.x;
a += pos.y;
a += pos.z;
a += rot.x;
a += rot.y;
a += rot.z;
a += scale.x;
a += scale.y;
a += scale.z;
a += obj.childCount;
return a;
}
Sorry for dredging up this old thread, but wanted to add my two cents after searching for a solution. The accepted solution was very flawed
So the best thing I could come up with was creating an IUnique interface, which implements a “UniqueId” object. In my case, the UniqueId object contains a stringified Guid (for serialization purposes I’m using string instead of Guid).
Now, to make sure the Id contained inside the UniqueId object is always unique, I added a custom property drawer. It just renders the Id to the inspector, but more importantly, looks for any other GameObject that implements the IUnique interface and has the same Guid. If there is one, it generates a new one. If not, it stays as is.
It still feels like a hacky, clunky mess of code, but at least it can be generalized. Just implement the IUnique interface and its members, and you can be sure it has a unique identiefier that is persistent.
If I have made a mistake, I hope someone is willing to point it out to me and explain it.
[CustomPropertyDrawer(typeof(UniqueId))]
public class UniqueIdDrawer : PropertyDrawer
{
public override VisualElement CreatePropertyGUI(SerializedProperty property)
{
// Create property container element.
var container = new VisualElement();
var target = property.serializedObject.targetObject as IUnique;
var copyFound = GameObject.FindObjectsByType<MonoBehaviour>(FindObjectsSortMode.None).OfType<IUnique>().Any(go => go != target && go.UniqueId.Id == target.UniqueId.Id);
if (copyFound)
target.UniqueId.Id = Guid.NewGuid().ToString();
var idField = new PropertyField(property.FindPropertyRelative("Id"));
container.Add(idField);
return container;
}
}
[Serializable]
public class UniqueId
{
[SerializeField]
public string Id = Guid.NewGuid().ToString();;
}
public interface IUnique
{
UniqueId UniqueId { get; set; }
}