How to set Sprite or Texture2D AssetPath in editor script?

I have an editor script that creates buttons in a menu. Each button displays an asset preview, loaded with AssetPreview.GetAssetPreview. I create a new Sprite from the returned Texture2D, attach it to a SpriteRenderer of a newly created button GameObject, and it displays just right.

Problem is that because it is not an actual asset, it does not show up in the actual game. In game mode, the preview sprite is gone, and I see the following error in the console:

rd->texture.IsValid()

UnityEditor.DockArea:OnGUI()

When leaving play mode, the preview also does not return. I would say that editor script changes should be permanent (especially since it looks just fine in editor mode). I am assuming however that Unity 5 does not yet save these memory-backed assets to the scene file or some other part of the asset database, and thus is not persisted in any form or shape, resulting in oddities.

So I was wondering if there is any way, I can use an editor script to tell the importer where to look for a Texture or Sprite, after I persisted it to a file?

Of course, I could use a run-time script to load the asset on start-up, but I would rather have Unity take care of all the asset loading, to keep things simple and save myself a lot of unnecessary trouble. After all, why should things be any different, just because I use a script to set a sprite, instead of setting it manually?

I finally produced a solution, mostly based on this answer (which I found in the “Related Questions” on the right).

All the magic lies in AssetDatabase.CreateAsset. However, it’s rather tricky and is quick to unapologetically spew out non-explanatory error messages. So here are my lessons learned and the code I put together in the past hour:

  1. When creating an asset from an AssetPreview, first create a copy, else it will consider the original Texture as an existing asset. This might actually work in your favor (i.e. you don’t have to re-create it if it’s already recognized as an asset), but it could also quickly create more issues, so making a copy is the safest way.
  2. The expected path must be relative to the CWD, which is your project folder. I.e. in almost all cases, you want the path to be prefixed with Assets/.
  3. The resulting file apparently is an asset file, and not just a plain *.png file.
  4. To be 100% safe, you want to re-load the asset and use it’s Texture2D object, so the editor fully understands the connection to the asset and not lose it again.
  5. For some reason, with this script, when inspecting the result in the editor (e.g. in my case, in SpriteRenderer), it will not show a name nor will it recognize the connection to the actual file, and yet everything else works fine.

Code:

public static readonly string PreviewFileName = "MenuButtonPreview";
public static readonly string PreviewFileFolder = "AutoGenerated";

Texture2D StorePreviewAsAsset(Object asset, int index) {
	var folder = "Assets/" + PreviewFileFolder;
	var fileNamePrefix = PreviewFileName + index;
	var fileName = fileNamePrefix + ".asset";
	var completePath = folder + '/' + fileName;

	// get preview texture from cache
	var textureOrig = AssetPreview.GetAssetPreview(asset);

	// create new texture that is copy of original
	var texture = new Texture2D (textureOrig.width, textureOrig.height, textureOrig.format, false);
	texture.SetPixels32 (textureOrig.GetPixels32 ());
	texture.name = folder + '/' + fileNamePrefix;

	// create folders
	if (!Directory.Exists (folder)) {
		Directory.CreateDirectory (folder);
	}

	// delete any existing version of it (if any)
	AssetDatabase.DeleteAsset (completePath);

	// add to AssetDatabase
	AssetDatabase.CreateAsset(texture, completePath);

	// load and from here on only reference the texture from the AssetDatabase
	var previewAsset = AssetDatabase.LoadAssetAtPath(completePath, typeof(Texture2D));
	return (Texture2D)EditorUtility.InstanceIDToObject (previewAsset.GetInstanceID ());
}