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
}
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.
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:
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;
}
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.