Request of features that add clarity on 'guid' based address.

So I’ve been using Addressables for a little over a month now and I’m really liking it.

But there is something that just erks me a bit.

And that is Addressables seem to want to ‘hide’ the guid aspect of Addressables from us. While I can use a guid as the token… knowing the guid is a whole other story.

Of course I can go look it up for the asset (it’s just in the meta file), or I can write my own custom editor script to get it (which I’ve done… this allows me to right click->Copy Asset Guid, from the Project window).

But it’d be nice if Addressables allowed me to put it into “all guid, all the time” mode.

Now before I continue, I will preface that if something like what I describe does exist, I don’t know about it. Please inform me of it as it would be very helpful.

What I mean is like this:

It defaults to using the folder path as the “address”. This “address” is pretty arbitrary as far as I understand it though (it just has to be unique I presume). We even get drop down options to “simplify” the name, or to change it all together to whatever we’d like.

Personally… I’d prefer the ‘guid’ just be that address.

I know in the asset group I can make the “internal asset naming” always guid:
7921846--1011013--upload_2022-2-23_17-57-6.png

I’d just like it to not just be “internal”.

Also… it’d be nice to be able to right-click the asset in the group and select a ‘Copy Guid To Clipboard’, like there is already a ‘Copy Address To Clipboard’.

The reason this erks me so… is that it feels like Addressables relies heavily on guids, but wants to protect the user from ever having to see those guids. And it’s like… I’m fine with guids. I use them all the time in my line of work. I’m a software engineer… guids aren’t scary to me. Really I’m using guids through out my current project both in and out of addressables as the game pretty much requires it (it’s why I like the idea of using guids in addressables… it’s consistent with the existing design).

I understand that this current convention is probably best for default.

But it’d be nice if I could drop it into “hardcore” mode or something.

Cause as is… I’m effectively writing my own extra scripts around Addressables to uncover this functionality for myself. And I just ignore the “address” field in this group view I screenshotted above.

Hi @lordofduct thanks for the feedback! I would say that hiding guids is convention in the Editor in general. Will add this to our list of requests.

Yeah, this is something I have found peculiar as well.

For starters System.Guid is not serializable by Unity. Not only that, Unity has their own internal GUID type which is effectively the same (there might be some underlying differences in how a new guid is created? no idea, not like I have internal access). And it’s part of UnityEditor.dll and not UnityEngine meaning it’s editor time only.

But that’s not Addressables specific, so I didn’t really mention it here.

But I wanted my own guid that was interoperable with System.Guid so I just wrote my own:

using UnityEngine;

namespace com.spacepuppy
{

    [System.Serializable]
    public struct SerializableGuid
    {

        #region Fields

        [SerializeField]
        private int a;
        [SerializeField]
        private short b;
        [SerializeField]
        private short c;
        [SerializeField]
        private byte d;
        [SerializeField]
        private byte e;
        [SerializeField]
        private byte f;
        [SerializeField]
        private byte g;
        [SerializeField]
        private byte h;
        [SerializeField]
        private byte i;
        [SerializeField]
        private byte j;
        [SerializeField]
        private byte k;

        #endregion

        #region CONSTRUCTOR

        public SerializableGuid(System.Guid guid)
        {
            var arr = guid.ToByteArray(); //unfortunately unoptimized for gc, but this is how System.Guid grants access to the bytes of the guid
            a = System.BitConverter.ToInt32(arr, 0);
            b = System.BitConverter.ToInt16(arr, 4);
            c = System.BitConverter.ToInt16(arr, 6);
            d = arr[8];
            e = arr[9];
            f = arr[10];
            g = arr[11];
            h = arr[12];
            i = arr[13];
            j = arr[14];
            k = arr[15];
        }

        public SerializableGuid(int a, short b, short c, byte d, byte e, byte f, byte g, byte h, byte i, byte j, byte k)
        {
            this.a = a;
            this.b = b;
            this.c = c;
            this.d = d;
            this.e = e;
            this.f = f;
            this.g = g;
            this.h = h;
            this.i = i;
            this.j = j;
            this.k = k;
        }

        #endregion

        #region Methods

        public override string ToString()
        {
            return this.ToGuid().ToString();
        }

        public string ToString(string format)
        {
            return this.ToGuid().ToString(format);
        }

        public System.Guid ToGuid()
        {
            return new System.Guid(a, b, c, d, e, f, g, h, i, j, k);
        }

        public override bool Equals(object obj)
        {
            if (obj is SerializableGuid sg)
            {
                return this.ToGuid() == sg.ToGuid();
            }
            else if (obj is System.Guid g)
            {
                return this.ToGuid() == g;
            }
            else
            {
                return false;
            }
        }

        public override int GetHashCode()
        {
            return this.ToGuid().GetHashCode();
        }

        #endregion

        #region Static Utils

        public static SerializableGuid NewGuid()
        {
            return new SerializableGuid(System.Guid.NewGuid());
        }

        public static SerializableGuid Parse(string input)
        {
            return new SerializableGuid(System.Guid.Parse(input));
        }

        public static bool TryParse(string input, out SerializableGuid result)
        {
            System.Guid output;
            if (System.Guid.TryParse(input, out output))
            {
                result = new SerializableGuid(output);
                return true;
            }
            else
            {
                result = default(SerializableGuid);
                return false;
            }
        }

        public static bool Coerce(object input, out SerializableGuid result)
        {
            System.Guid guid;
            if (CoerceGuid(input, out guid))
            {
                result = new SerializableGuid(guid);
                return true;
            }
            else
            {
                result = default(SerializableGuid);
                return false;
            }
        }

        public static bool CoerceGuid(object input, out System.Guid result)
        {
            switch (input)
            {
                case System.Guid guid:
                    result = guid;
                    return true;
                case SerializableGuid sguid:
                    result = sguid.ToGuid();
                    return true;
                case string s:
                    return System.Guid.TryParse(s, out result);
                case object o:
                    return System.Guid.TryParse(o.ToString(), out result);
                default:
                    result = default(SerializableGuid);
                    return false;
            }
        }

        public static implicit operator System.Guid(SerializableGuid guid)
        {
            return guid.ToGuid();
        }

        public static bool operator ==(SerializableGuid a, SerializableGuid b)
        {
            return a.ToGuid() == b.ToGuid();
        }

        public static bool operator ==(System.Guid a, SerializableGuid b)
        {
            return a == b.ToGuid();
        }

        public static bool operator ==(SerializableGuid a, System.Guid b)
        {
            return a.ToGuid() == b;
        }

        public static bool operator !=(SerializableGuid a, SerializableGuid b)
        {
            return a.ToGuid() != b.ToGuid();
        }

        public static bool operator !=(System.Guid a, SerializableGuid b)
        {
            return a != b.ToGuid();
        }

        public static bool operator !=(SerializableGuid a, System.Guid b)
        {
            return a.ToGuid() != b;
        }

        #endregion

        #region Special Types

        public class ConfigAttribute : System.Attribute
        {
            public bool AllowZero;
            /// <summary>
            /// Attempts to make the guid match the guid associated with the asset this is on.
            /// Note this only works if it's on an asset that exists on disk (ScriptableObject, Prefab).
            /// Also it means guids will match across all scripts that are on a prefab with this flagged. (since the prefab only has one guid)
            /// Really... this should be mainly done with ScriptableObjects only, Prefab's are just a
            /// sort of bonus but with caveats.
            ///
            /// New instances created via 'Instantiate' or 'CreateInstance' will not get anything.
            /// This is editor time only for assets on disk!
            /// </summary>
            public bool LinkToAsset;
        }

        #endregion

    }

}

Editor:

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

using com.spacepuppy;
using com.spacepuppy.Utils;
using System.Diagnostics.Eventing.Reader;
using com.spacepuppy.Collections;
using System.Reflection;
using System.Linq.Expressions;

namespace com.spacepuppyeditor.Core
{

    [CustomPropertyDrawer(typeof(SerializableGuid))]
    public class SerializableGuidPropertyDrawer : PropertyDrawer
    {

        public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
        {
            return EditorGUIUtility.singleLineHeight;
        }

        public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
        {
            if(property.serializedObject.isEditingMultipleObjects)
            {
                EditorGUI.LabelField(position, label, EditorHelper.TempContent("Not Supported in Multi-Edit Mode"));
                return;
            }

            EditorGUI.BeginProperty(position, label, property);

            position = EditorGUI.PrefixLabel(position, label);
            EditorHelper.SuppressIndentLevel();

            try
            {
                var attrib = this.fieldInfo.GetCustomAttribute<SerializableGuid.ConfigAttribute>();
                bool resetOnZero = attrib == null || !attrib.AllowZero;
                bool linkToAsset = attrib != null && attrib.LinkToAsset;

                System.Guid guid = FromSerializedProperty(property);

                if (linkToAsset)
                {
                    string gid;
                    long lid;
                    System.Guid assetguid;
                    if (AssetDatabase.TryGetGUIDAndLocalFileIdentifier(property.serializedObject.targetObject, out gid, out lid) && System.Guid.TryParse(gid, out assetguid))
                    {
                        if(assetguid != guid)
                        {
                            guid = assetguid;
                            ToSerializedProperty(property, guid);
                        }

                        EditorGUI.SelectableLabel(position, guid.ToString("D"), EditorStyles.textField);
                        EditorGUI.EndProperty();
                        return;
                    }
                }

                //if we made it here we want to draw default
                float w = Mathf.Min(position.width, 60f);
                var r2 = new Rect(position.xMax - w, position.yMin, w, position.height);
                var r1 = new Rect(position.xMin, position.yMin, Mathf.Max(position.width - w, 0f), position.height);

                EditorGUI.SelectableLabel(r1, guid.ToString("D"), EditorStyles.textField);

                if (GUI.Button(r2, "New Id") || (resetOnZero && guid == System.Guid.Empty))
                {
                    guid = System.Guid.NewGuid();
                    ToSerializedProperty(property, guid);
                }
                EditorGUI.EndProperty();
            }
            finally
            {
                EditorHelper.ResumeIndentLevel();
            }
        }

        public static System.Guid FromSerializedProperty(SerializedProperty prop)
        {
            return new System.Guid(prop.FindPropertyRelative("a").intValue, (short)prop.FindPropertyRelative("b").intValue, (short)prop.FindPropertyRelative("c").intValue,
                                   (byte)prop.FindPropertyRelative("d").intValue, (byte)prop.FindPropertyRelative("e").intValue, (byte)prop.FindPropertyRelative("f").intValue,
                                   (byte)prop.FindPropertyRelative("g").intValue, (byte)prop.FindPropertyRelative("h").intValue, (byte)prop.FindPropertyRelative("i").intValue,
                                   (byte)prop.FindPropertyRelative("j").intValue, (byte)prop.FindPropertyRelative("k").intValue);
        }

        public static void ToSerializedProperty(SerializedProperty prop, System.Guid guid)
        {
            var arr = guid.ToByteArray();
            prop.FindPropertyRelative("a").intValue = System.BitConverter.ToInt32(arr, 0);
            prop.FindPropertyRelative("b").intValue = System.BitConverter.ToInt16(arr, 4);
            prop.FindPropertyRelative("c").intValue = System.BitConverter.ToInt16(arr, 6);
            prop.FindPropertyRelative("d").intValue = arr[8];
            prop.FindPropertyRelative("e").intValue = arr[9];
            prop.FindPropertyRelative("f").intValue = arr[10];
            prop.FindPropertyRelative("g").intValue = arr[11];
            prop.FindPropertyRelative("h").intValue = arr[12];
            prop.FindPropertyRelative("i").intValue = arr[13];
            prop.FindPropertyRelative("j").intValue = arr[14];
            prop.FindPropertyRelative("k").intValue = arr[15];
        }

        private class SerializableGuidPostProcessor : AssetPostprocessor
        {

            private static readonly HashSet<System.Reflection.FieldInfo> _knownSOFields = new HashSet<System.Reflection.FieldInfo>();
            private static readonly HashSet<System.Reflection.FieldInfo> _knownGOFields = new HashSet<System.Reflection.FieldInfo>();

            static SerializableGuidPostProcessor()
            {
                var guidtp = typeof(SerializableGuid);
                var sotp = typeof(ScriptableObject);
                var ctp = typeof(MonoBehaviour);
                foreach (var tp in TypeUtil.GetTypes(t => TypeUtil.IsType(t, sotp) || TypeUtil.IsType(t, ctp)))
                {
                    foreach (var field in tp.GetFields(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance))
                    {
                        if (field.FieldType != guidtp) continue;

                        var attrib = field.GetCustomAttribute<SerializableGuid.ConfigAttribute>();
                        if(attrib != null && attrib.LinkToAsset)
                        {
                            if(TypeUtil.IsType(field.DeclaringType, sotp))
                            {
                                _knownSOFields.Add(field);
                            }
                            else if(TypeUtil.IsType(field.DeclaringType, ctp))
                            {
                                _knownGOFields.Add(field);
                            }
                        }
                    }
                }
            }

            static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths)
            {
                if (_knownGOFields.Count == 0 && _knownSOFields.Count == 0) return;

                var sotp = typeof(ScriptableObject);
                var gotp = typeof(GameObject);
                if (importedAssets != null && importedAssets.Length > 0)
                {
                    foreach (var path in importedAssets)
                    {
                        var atp = AssetDatabase.GetMainAssetTypeAtPath(path);
                        if (_knownSOFields.Count > 0 && TypeUtil.IsType(atp, sotp))
                        {
                            HandleScriptableObject(path, atp);
                        }
                        else if (_knownGOFields.Count > 0 && TypeUtil.IsType(atp, gotp))
                        {
                            HandleGameObject(AssetDatabase.LoadAssetAtPath<GameObject>(path));
                        }
                    }
                }
            }

            static void HandleGameObject(GameObject go)
            {
                if (go == null) return;

                bool edited = false;
                foreach(var field in _knownGOFields)
                {
                    var c = go.GetComponent(field.DeclaringType);
                    if(c != null)
                    {
                        try
                        {
                            var guid = (SerializableGuid)field.GetValue(c);

                            string gid;
                            long lid;
                            System.Guid assetguid;
                            if (AssetDatabase.TryGetGUIDAndLocalFileIdentifier(c, out gid, out lid) && System.Guid.TryParse(gid, out assetguid))
                            {
                                if (assetguid != guid)
                                {
                                    field.SetValue(c, new SerializableGuid(assetguid));
                                    edited = true;
                                }
                            }
                        }
                        catch(System.Exception ex)
                        {
                            Debug.LogException(ex);
                        }
                    }
                }

                if(edited)
                {
                    PrefabUtility.SavePrefabAsset(go);
                }
            }

            static void HandleScriptableObject(string path, System.Type atp)
            {
                ScriptableObject so = null;

                foreach (var field in _knownSOFields)
                {
                    if (TypeUtil.IsType(atp, field.DeclaringType))
                    {
                        if (object.ReferenceEquals(so, null)) so = AssetDatabase.LoadAssetAtPath<ScriptableObject>(path);

                        try
                        {
                            var guid = (SerializableGuid)field.GetValue(so);

                            string gid;
                            long lid;
                            System.Guid assetguid;
                            if (AssetDatabase.TryGetGUIDAndLocalFileIdentifier(so, out gid, out lid) && System.Guid.TryParse(gid, out assetguid))
                            {
                                if (assetguid != guid)
                                {
                                    field.SetValue(so, new SerializableGuid(assetguid));
                                    EditorUtility.SetDirty(so);
                                    AssetDatabase.SaveAssetIfDirty(so);
                                }
                            }
                        }
                        catch (System.Exception ex)
                        {
                            Debug.LogException(ex);
                        }
                    }
                }
            }



        }

    }
}

@lordofduct Hmm so I did some digging around this and the main reason why UnityEditor.GUID is still around is because it’s used internally by AssetDatabase and PlayerConnection. It would require some effort to modify the APIs to use System.Guid instead.

If you want you could submit a request to the engineering product board https://unity.com/roadmap/unity-platform/engineering. I’ve already added the Addressables-related request our team doc.

Oh, I figured something like that.

And I’m not holding my breathe on that one, I was mainly mentioning it because you brought up how it’s convention in editor.

1 Like