Managing Id's of Scriptable Objects

Hi Guys!

I have the following problem and I was wondering if you could help me with it.

Currently in my game I use scriptable objects for my items. I collect all these items into a database and have a function that retrieves these items by an Id. However I was wondering if there is a good way to automatically manage these Id’s, since I currently assign them by hand. Ideally I would like to assign an Id as soon as the scriptable object is made through the Assets/Create submenu, however I don’t really see any events that can be called at that moment? Is there something I could hook into or is there some other initialize function I can hook into? I can store the nexId number somewhere statically and increment it, I’m just not sure when to do this.

I think you could use the OnValidate() method to fill out some kind of identifier that gets serialized.

Be careful going down a route like this; you might want to just use an existing identifier, such as its name or else name and pathname. That can help guarantee a) you don’t forget to add one, and b) you never have conflicts.

In my Datasacks module I auto-generate unique pathname/filename entries for each Datasack, and those strings are also used on code generation to allow compiler-safe checking rather than using strings. This work is done in this file:

And there is a master collection of all these created Datasacks for rapid centralized access.

OnValidate can be troublesome when dealing with assets created through duplication, and you’ll need to handle dirtying and resaving the asset if it generates a new ID, which can be a little tricky because some API is unavailable there.

The best way I know to detect when an asset has been created is to use the first parameter in AssetPostprocessor.OnPostProcessAllAssets. It also detects when an object has been created by means like ctrl+D. It has a parameter with deleted asset paths that you might be able to use to remove ids from your database. If you only want to intercept creation through the menu, you can use something like this:

using UnityEngine;
using UnityEditor;
using UnityEditor.ProjectWindowCallback;

public static class SomeSOMenuItems
{
        [MenuItem("Assets/Create/SomeSO", false, 0)]
        static void CreateNewAsset()
        {
            var newObject = ScriptableObject.CreateInstance<SomeSO>();

            ProjectWindowUtil.StartNameEditingIfProjectWindowExists(
                newObject.GetInstanceID(),
                ScriptableObject.CreateInstance<DoCreateSomeSOAsset>(),
                "Default SomeSO Name.asset",
                AssetPreview.GetMiniThumbnail(newObject),
                null);
        }
}

class DoCreateSomeSOAsset : EndNameEditAction
{
    public override void Action(int instanceId, string path, string resFile)
    {
        var obj = EditorUtility.InstanceIDToObject(instanceId) as SomeSO;
  
        // Do something to obj here.

        path = AssetDatabase.GenerateUniqueAssetPath(path);
        AssetDatabase.CreateAsset(obj, path);
        ProjectWindowUtil.FrameObjectInProjectWindow(instanceId);
    }

    public override void Cancelled(int instanceId, string path, string resFile) => Selection.activeObject = null;
}

I’d suggest using a proper GUID stored in a string instead of a number. A plain number, specially if it’s obtained by increments, will have high chances of collision when using version control or when multiple persons are collaborating. That is, unless you don’t need the IDs to survive through multiple sessions, then you can just use GetInstanceID().

I feel like I must warn you: Serializing references to assets through custom unique IDs can still get you into lots of trouble. Like, if two persons create an asset in the same path, now you have a new layer of issues to deal with when merging. Or If an asset gets created or deleted outside of the Editor (i.e. in Version Control) there could easily be a mess. If there’s a bug in Unity’s code or yours that prevents an ID from being generated, you can have a silent ID inconsistency. Even when everything is properly caught in the Editor, you need to deal with missing IDs and database inconsistencies manually.

I think Kurt’s suggestion of using name+pathName as IDs can be a good compromise to solve many of those problems, because then your IDs are visible to the users, and you can count on everything, even the file explorer, for dealing with some of the potential issues, like duplication.

1 Like