I’m trying to make a system that automatically remembers data between two scenes so that the scenes appear to be “persistent”; IE: If I open a chest in one scene, hop over to another scene, and then hop back to the original scene, the chest should still be open.
The current system I have in place is working BEAUTIFULLY, but there’s a major catch: in order for it to work, I need to assign each persistent object an ID number, and this number has to be the same each time the scene is loaded up. At the moment, I’m just assigning this ID number by hand in the inspector when I place each individual object. However, I would like to have a system that automatically assigns each object a unique ID for me.
So far, I’ve tried doing something similar to this:
public class PersistenceRememberer : MonoBehaviour {
public static int nextID = 0;
private int myID;
//Events
void Awake(){
myID = nextID;
nextID++;
}
void OnLevelEnd(){
//Clear the nextID
nextID = 0;
}
}
However, this has a major flaw in it: each time a new scene is loaded, the objects end up getting different IDs.
So, I decided to come here for ideas. How can I automatically assign an ID to an object in such a way that it will always be the same, even when the player hops back and forth between scenes?
This can easily be done through C#'s GUID generator and Unity’s editor libraries for a powerful effect.
Following scripts will automatically generate a unique ID for you, all you have to do is attach the script component. And then KABOOM, you never have to worry about it again (as long as you don’t delete the script component). You’ll notice in the screenshot the unique ID is visible, but it cannot be edited for safety and stupidity reasons. If you really want to edit it, you can change it to an editable field through EditorGUI.
[34157-screen+shot+2014-10-23+at+2.02.06+am.png|34157]
UniqueIdDrawer.cs
using UnityEditor;
using UnityEngine;
using System;
// Place this file inside Assets/Editor
[CustomPropertyDrawer (typeof(UniqueIdentifierAttribute))]
public class UniqueIdentifierDrawer : PropertyDrawer {
public override void OnGUI (Rect position, SerializedProperty prop, GUIContent label) {
// Generate a unique ID, defaults to an empty string if nothing has been serialized yet
if (prop.stringValue == "") {
Guid guid = Guid.NewGuid();
prop.stringValue = guid.ToString();
}
// Place a label so it can't be edited by accident
Rect textFieldPosition = position;
textFieldPosition.height = 16;
DrawLabelField (textFieldPosition, prop, label);
}
void DrawLabelField (Rect position, SerializedProperty prop, GUIContent label) {
EditorGUI.LabelField(position, label, new GUIContent (prop.stringValue));
}
}
UniqueId.cs
using UnityEngine;
using System.Collections;
// Placeholder for UniqueIdDrawer script
public class UniqueIdentifierAttribute : PropertyAttribute {}
public class UniqueId : MonoBehaviour {
[UniqueIdentifier]
public string uniqueId;
}
DontDestroyOnLoad (theThingThatKeepsTrackOfYourIDs);
The persistent gameObject could keep a List.< List.< GameObject > >, with the index of the outer list corresponding to the level,
and the index in the inner list would serve as the ID for every tracked gameObject in that level.
You can then have another List.< List.< String > > that is populated at the same time as the first list; so the index numbers of this match to the index numbers of your gameObjects, and you can store whatever data you need to.
This would let you not only remember what chest was locked the last time you were in the level, but remotely unlock any chest on any level from any other level.
-edit-
Sorry, I think I misunderstood your problem. How about this:
The first time you load a level, if the persistent list doesn’t exist yet- do something like this for each tracked object:
float posHash = (1000transform.position.x) + transform.position.y + (.001transform.position.z);
Unless two objects are in the exact same spot- which they really shouldn’t be- this number will be unique, regardless of the order the objects’ individual scripts call “Awake().”
Then you can sort them by posHash and use their index in the sorted list as their permanent ID.
I had problems with the solutions presented here (and elsewhere). The problems cropped up when using prefabs (where ids would get copied into the prefab then duplicated in the instantiations), doing additive loading (when ids would not be unique across level loads) or when making minor cosmetic adjustments to scenes (e.g. moving something slightly or adding in some extra aesthetic objects).
This is my solution to the problem that is working fine for me (so far). More details in the comments of the code.
// 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 we end up changing
// our ID unnecessarily
void OnDestroy(){
allGuids.Remove(uniqueId);
}
#endif
}
-
You can generate a permanent ID when you attach this GameObjectGUID.cs script to your game object.
-
When you duplicate a game object that already contains the GameObjectGUID.cs script, a new ID is generated for the duplicated object.
GameObjectGUID.cs
// GameObjectGUID.cs (drag this onto your game object)
using UnityEngine;
public class GameObjectGUID : MonoBehaviour {
public int gameObjectID = 0;
}
GameObjectGUIDInspector.cs
// GameObjectGUIDInspector.cs
using UnityEngine;
using UnityEditor;
using System;
[CustomEditor(typeof(GameObjectGUID))]
public class GameObjectGUIDInspector : Editor
{
private GameObjectGUID id;
// assign GameObjectGUID instance to this inspector
void OnEnable()
{
id = (GameObjectGUID)target;
// generate new guid when you create a new game object
if (id.gameObjectID == 0) id.gameObjectID = new System.Random().Next(1000000, 9999999);
// generate new guid if guid already exists
else
{
GameObjectGUID[] objects = Array.ConvertAll(GameObject.FindObjectsOfType(typeof(GameObjectGUID)), x => x as GameObjectGUID);
int idCount = 0;
for (int i = 0; i < objects.Length; i++)
{
if (id.gameObjectID == objects*.gameObjectID)*
idCount++;
}
if (idCount > 1) id.gameObjectID = new System.Random().Next(1000000, 9999999);
}
}
}
This needs optimized, but it is possible to get a unique game object ID when you attach a script to a game object and when you duplicate the game object that contains the ID script.
I’d use a GUID. Random integers aren’t guaranteed to be unique
In the scene file, every gameobject has a unique identifier that is persistent. You can access it like this:
`PropertyInfo inspectorModeInfo =
typeof(SerializedObject).GetProperty(“inspectorMode”, BindingFlags.NonPublic | BindingFlags.Instance);
SerializedObject serializedObject = new SerializedObject(unityObject);
inspectorModeInfo.SetValue(serializedObject, InspectorMode.Debug, null);
SerializedProperty localIdProp =
serializedObject.FindProperty(“m_LocalIdentfierInFile”); //note the misspelling!
int localId = localIdProp.intValue;`
The only catch is that this only works in the editor, so you have to serialize it when you want to build your game.
Answer found here: https://forum.unity.com/threads/how-to-get-the-local-identifier-in-file-for-scene-objects.265686/
I end up with this =>
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;
}
Is there a problem with saving the game state?
https://unity3d.com/learn/tutorials/topics/scripting/persistence-saving-and-loading-data
Otherwise, if you want a unique ID then create a string that is a concatenation of the following
- Scene name
- World location that the object has when the scene is loaded
- World rotation the object has when the scene is loaded
If you use a Tuple as the key for a static hashtable you can have your MonoBehavior set ThatTable[thatkey] = this.gameObject on Awake