Unity: Create Assets with Dynamic Name/Properties

Problem

The CreateAssetMenu attribute is handy if you want a quick way to instantiate scriptable objects.  But it falls short when a dynamic name or the initialization of different properties is needed, for example, the generation of a custom ID.

 

Solution

The solution is to create the asset yourself. The CreateAssetMenu attribute is only a wrapper for the UnityEditor.MenuItem attribute. Use the magic string Assets/Create as itemName to place it in the create menu. You can extend the itemName in the same way as the menuName in the CreateAssetMenu to add subfolders.

 

A simple script to create a scriptable object with a custom name could look like this:

using UnityEngine;

public class CustomObject : ScriptableObject
{
	public int RandomNumber;
	public Texture2D Graphic;

#if UNITY_EDITOR
	[UnityEditor.MenuItem("Assets/Create/Custom/Object at Root (Menu Item)", priority = 10)]
	private static void CreateWithRandomName()
	{
		CustomObject myObject = UnityEditor.ObjectFactory.CreateInstance<CustomObject>();
		myObject.RandomNumber = Random.Range(0, 42);

		string directory = "Assets";
		string assetName = $"NewAssetName{myObject.RandomNumber}.asset";
		string path = System.IO.Path.Combine(directory, assetName);

		UnityEditor.AssetDatabase.CreateAsset(myObject, path);
	}
#endif
}

 

Create at Selection

You can change the path of the created object to spawn it wherever you want. If you would like to mirror the behaviour of the CreateAssetMenu attribute, place it at the position of the current selection. The selection can be accessed via the UnityEditor.Selection.activeObject. Be aware that the active object can be ba an asset or a folder. If the first is the case the asset name needs to be stipped from the path.

 

	[UnityEditor.MenuItem("Assets/Create/Custom/Object at Selection (Menu Item)", priority = 10)]
	private static void CreateAtSelection()
	{
		CustomObject myObject = UnityEditor.ObjectFactory.CreateInstance<CustomObject>();
		myObject.RandomNumber = Random.Range(0, 42);

		string directory = UnityEditor.AssetDatabase.GetAssetPath(UnityEditor.Selection.activeObject);
		if (System.IO.Path.HasExtension(directory))
			directory = System.IO.Path.GetDirectoryName(directory);

		string assetName = "NewAssetName.asset";
		string path = System.IO.Path.Combine(directory, assetName);

		UnityEditor.AssetDatabase.CreateAsset(myObject, path);
		UnityEditor.Selection.activeObject = myObject;
	}

 

Create from Selection

Now it’s only a short step to a neat asset integration workflow.
Search the selected assets for the objects you need and add the references to the object instance.

 

Real-life example

We use custom AudioEffect scriptable objects in our games to wrap audio clips to support randomisation and variants.

 

You just have to select the clips and use the create menu. The object pulls the clip references from the selection and generates the correct name based on the source clips names. It’s a reall time saver if you have 100+ sounds with variants in your game.

 

	[UnityEditor.MenuItem("Assets/Create/Custom/Object from Selection (Menu Item)", priority = 10)]
	private static void CreateFromSelection()
	{
		Texture2D[] graphics = UnityEditor.Selection.GetFiltered<Texture2D>(UnityEditor.SelectionMode.Assets);
		if (graphics != null && graphics.Length > 0)
		{
			CustomObject myObject = UnityEditor.ObjectFactory.CreateInstance<CustomObject>();
			myObject.RandomNumber = Random.Range(0, 42);
			myObject.Graphic = graphics[0];

			string directory = UnityEditor.AssetDatabase.GetAssetPath(UnityEditor.Selection.activeObject);
			if (System.IO.Path.HasExtension(directory))
				directory = System.IO.Path.GetDirectoryName(directory);

			string assetName = $"MyObject_{myObject.Graphic.name}.asset";
			string path = System.IO.Path.Combine(directory, assetName);

			UnityEditor.AssetDatabase.CreateAsset(myObject, path);
			UnityEditor.Selection.activeObject = myObject;
		}
		else
			Debug.LogError("Select a texture to create a CustomObject.");
	}

 

Following the complete script with all examples:

using UnityEngine;

[CreateAssetMenu(menuName = "Custom/Object (Create Asset Menu)")]
public class CustomObject : ScriptableObject
{
	public int RandomNumber;
	public Texture2D Graphic;

#if UNITY_EDITOR
	[UnityEditor.MenuItem("Assets/Create/Custom/Object at Root (Menu Item)", priority = 10)]
	private static void CreateWithRandomName()
	{
		CustomObject myObject = UnityEditor.ObjectFactory.CreateInstance<CustomObject>();
		myObject.RandomNumber = Random.Range(0, 42);

		string directory = "Assets";
		string assetName = $"NewAssetName{myObject.RandomNumber}.asset";
		string path = System.IO.Path.Combine(directory, assetName);

		UnityEditor.AssetDatabase.CreateAsset(myObject, path);
	}

	[UnityEditor.MenuItem("Assets/Create/Custom/Object at Selection (Menu Item)", priority = 10)]
	private static void CreateAtSelection()
	{
		CustomObject myObject = UnityEditor.ObjectFactory.CreateInstance<CustomObject>();
		myObject.RandomNumber = Random.Range(0, 42);

		string directory = UnityEditor.AssetDatabase.GetAssetPath(UnityEditor.Selection.activeObject);
		if (System.IO.Path.HasExtension(directory))
			directory = System.IO.Path.GetDirectoryName(directory);

		string assetName = "NewAssetName.asset";
		string path = System.IO.Path.Combine(directory, assetName);

		UnityEditor.AssetDatabase.CreateAsset(myObject, path);
		UnityEditor.Selection.activeObject = myObject;
	}

	[UnityEditor.MenuItem("Assets/Create/Custom/Object from Selection (Menu Item)", priority = 10)]
	private static void CreateFromSelection()
	{
		Texture2D[] graphics = UnityEditor.Selection.GetFiltered<Texture2D>(UnityEditor.SelectionMode.Assets);
		if (graphics != null && graphics.Length > 0)
		{
			CustomObject myObject = UnityEditor.ObjectFactory.CreateInstance<CustomObject>();
			myObject.RandomNumber = Random.Range(0, 42);
			myObject.Graphic = graphics[0];

			string directory = UnityEditor.AssetDatabase.GetAssetPath(UnityEditor.Selection.activeObject);
			if (System.IO.Path.HasExtension(directory))
				directory = System.IO.Path.GetDirectoryName(directory);

			string assetName = $"MyObject_{myObject.Graphic.name}.asset";
			string path = System.IO.Path.Combine(directory, assetName);

			UnityEditor.AssetDatabase.CreateAsset(myObject, path);
			UnityEditor.Selection.activeObject = myObject;
		}
		else
			Debug.LogError("Select a texture to create a CustomObject.");
	}
#endif
}