Can we get individualized userData please?

Right now asset importers have a ‘userData’ field which is a string. This string can be used to store any arbitrary information such as whether a user wants this asset to import in a special way, not available in its original importer, through adding an asset post processor and checking for the userdata.

Because it is a single string, all developers and features have to work on this single string or use other means to work on an asset in unison.

For example, should one developer choose to read this as json, but another one just decide it stores some single arbitrary name, or another one chooses to make it store a project relative path - they’ll all be overriding one another’s information or fail to read their own because of another’s tamper.

Without this adjustment, to the best of my knowledge, the only other practical solution is creating less intuitive, unique companion assets, with a lot of boilerplate code involved.

In a previous lengthy feedback thread I made , This has already been noted as a nice improvement to make. I’d love to know if this can ever be addressed, please! Something as simple as a string,string dictionary would already be more than sufficient for most people to not conflict, as they can have unique string paths per feature!

Hi @Yoraiz0r !

I had a read through your post and I can see you hit a lot of walls along the way, sorry to hear that!

Since reading this post, I tried 2 different approaches (one of them failed and the 2nd one seems like it could work).

The first failed approach was to use a SerializedObject and try to wrap a Texture in it, such that I could Add a property to it. There’s no AddProperty method to SerializedObject, but CopyFromSerializedProperty does exist and I tried adding a new serialized property that way. I had it compiling, and then when I ran it I got the error:
Destination property could not be found

Which means that a new property can’t be applied, which is a bit of a downer.

So, anyways, I looked through the codebase and found we could do something with the following APIs:

  1. AssetImporter.AddRemap
  2. AssetImporter.GetExternalObjectMap

The idea here, is to add a reference to another object, such that we could load the object if needed (and the object could be anything we want, like a ScriptableObject or a Texture).

I’ve got some code working here, where we can add a reference to a texture for a specific Model:

public class ExternalReferenceTest
{
    [MenuItem("AssetDatabase/VerifyExternalReferences")]
    public static void VerifyExternalReferences()
    {
        var texturePath = "Assets/test.png";
        var texture = AssetDatabase.LoadAssetAtPath<Texture2D>(texturePath);

        var fbxPath = "Assets/coloredCube.fbx";

        var allFbxAssets = AssetDatabase.LoadAllAssetsAtPath(fbxPath);

        var fbxImporter = (ModelImporter)AssetImporter.GetAtPath(fbxPath);

        var textureCounter = 0;
        var externalReferences = fbxImporter.GetExternalObjectMap();
        foreach (var curRef in externalReferences)
        {
            if (curRef.Value is Texture2D)
            {
                Debug.Log($"Found reference to {curRef.Value.name}");
                textureCounter++;
            }
        }

        if(textureCounter == 0)
            Debug.Log("Found no textures");

        fbxImporter.AddRemap(new AssetImporter.SourceAssetIdentifier(texture), texture);
        fbxImporter.SaveAndReimport();

        externalReferences = fbxImporter.GetExternalObjectMap();
        foreach (var curRef in externalReferences)
        {
            if(curRef.Value is Texture2D)
                Debug.Log($"Found reference to {curRef.Value.name}");
        }
    }
}

If you look at the corresponding .meta file (in my case coloredCube.fbx.meta) you’ll notice that the externalObjects array has been populated. For example, it would look like this:

ModelImporter:
  serializedVersion: 22200
  internalIDToNameTable: []
  externalObjects:
  - first:
      type: UnityEngine:Texture2D
      assembly: UnityEngine.CoreModule
      name: test
    second: {fileID: 2800000, guid: d38ec503f07e72140b7fa54c8abed249, type: 3}

This way we can add extra typed data to your objects, without having to mess with the userData.

Does this help?

Thank you for bothering to read through the whole original post, and introducing AddRemap as a solution, Javier!
If I understand this right, I would be in need to construct a unique identifier and then include its value in a form of a UnityEngine.Object derived class? strings would still need to be wrapped as TextAssets and exist in the project, then? I’m considering project file bloat for said extra settings / movability of objects with said extra data around the project, if you have to start selecting associated assets as you move the original asset around that would be a loss in quality of life.

Is there a way to make a string/textAsset that exists solely within the confines of the asset being edited / not outside it, then? would you recommend involving AddAssetToObject to do this?

If yes, I can imagine a case of…

  • making a unique asset identifier for a not-yet-existing text asset e.g. “ThisObject/ThisFeatureSettings” to bind to a TextAsset
  • if TextAsset does not yet exist, create the matching text needed, and AddAssetToObject for that text asset
  • read said text asset, involve it in the import process?

How would I be able to change the asset’s contents reliably once I do this? if I start dragging the object around in the project, is there any risk of losing the remapped data?

I am wondering how ‘external’ are external objects considered. Is an object added via AddAssetToObject counted as external, or internal, in this case?

Thanks again!

That’s right, for this approach to work you’ll either need one ScriptableObject per field, or your might even be able to get away with having one main Scriptable Object with multiple sub-assets which are then referenced from the external object map of whichever object you want.

I would only recommend it if your target asset is a ScriptableObject, but if you call AddAssetToObject for any other type of Asset, then the reference would be lost. We added that info to the API docs sometime ago, but I’m just copying it here:

This is because ScriptableObjects are quite special, in the sense that the contents are the same as what an Artifact would look like, whereas a Texture is a .png file (or some other extension) and that means every paint program would have to know about how Unity saves data to a source asset, which is not realistic (same would apply to all DCC tools). So, ScriptableObjects are really the only supported type which can guarantee AddObjectToAsset works on it.

As long as you use the AssetDatabase APIs, this should be OK, since we also move the .meta file with it and we update the AssetDatabase internals as well. If you’re using Explorer/Finder and just dragging and dropping files there, then the references will get broken.

As long as call AddRemap, the object will be considered “external”. Internal objects would probably be referred to as sub-assets :slight_smile:

Just to note, I’m making all these suggestions because its very likely we won’t be doing any work on the “userData” field anytime soon, and even if we do, backporting it would not be possible so best case you could get something for Unity 2024 (we just hit feature freeze for 2023!).

Happy to keep iterating on this, especially since it does look like something more people would want as well.