For game saving purposes, I'm keeping track of references to ScriptableObjects through a lookup table, so that I can serialize/deserialize using the SO's position in that table. I have a lot of individual SOs that I want to be able to serialize, so I wrote an editor script to populate that table automatically. The table exists in a MonoBehaviour on a prefab.
Originally, I wasn't saving the prefab, because in the inspector it looked like the changes were successfully applying. However, those were showing in-memory changes; when you open the asset, the changes don't exist. The problem is, when I added the line to save the asset (PrefabUtility.SavePrefabAsset
) Unity started hanging at the end of every import while showing that it was importing SO Lookup Table. I feel like I'm missing something obvious here, but Google hasn't been any help. What's wrong with my code?
Here's the editor script. Every time assets finish loading, it adds any ScriptableObjects that aren't in the lookup table and exist in one of the specified directories, and removes any that were deleted from or moved out of those directories.
public class SOLookupTableAutoPopulator : AssetPostprocessor
{
const string SO_LOOKUP_TABLE_PATH = "Assets/Prefabs/SO Lookup Table.prefab";
// every ScriptableObject in these directories is automatically added to the lookup table prefab
static readonly string[] DIRECTORIES_TO_TRACK = new string[]
{
"Assets/ScriptableObjects/ExampleDirectory1",
"Assets/ScriptableObjects/ExampleDirectory2"
};
static void OnPostprocessAllAssets (string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths)
{
var lutPrefab = AssetDatabase.LoadAssetAtPath(SO_LOOKUP_TABLE_PATH, typeof(GameObject)) as GameObject;
var soLookupTable = lutPrefab.GetComponent<SOLookupTable>();
// remove first in case asset moved between two tracked directories
foreach (var path in pathsInTrackedDirectories(deletedAssets.Concat(movedFromAssetPaths)))
{
soLookupTable.LookUpTable.RemoveAll(o => o.Path == path);
}
var toAdd = pathsInTrackedDirectories(importedAssets.Concat(movedAssets))
.Where(p => AssetDatabase.GetMainAssetTypeAtPath(p).IsSubclassOf(typeof(ScriptableObject)));
foreach (var path in toAdd)
{
if (!soLookupTable.LookUpTable.Any(o => o.Path == path))
soLookupTable.LookUpTable.Add(new ScriptableObjectPathTuple(path));
}
PrefabUtility.SavePrefabAsset(lutPrefab);
}
static IEnumerable<string> pathsInTrackedDirectories (IEnumerable<string> pathList)
{
return pathList.Where(p => DIRECTORIES_TO_TRACK.Any(d => p.StartsWith(d)));
}
}
The lookup table looks like this:
public class SOLookupTable : MonoBehaviour
{
public List<ScriptableObjectPathTuple> LookUpTable;
public int GetID (ScriptableObject so)
{
return LookUpTable.FindIndex(o => o.Asset == so);
}
public ScriptableObject GetSO (int id)
{
return LookUpTable[id].Asset;
}
}
And here's the class that the lookup table contains. I track the path in addition to the asset itself for the deletion phase of OnPostprocessAllAssets - you can't retrieve an asset from a path that points to a deleted asset.
[Serializable]
public class ScriptableObjectPathTuple
{
public ScriptableObject Asset;
public string Path;
public ScriptableObjectPathTuple (string path)
{
Path = path;
Asset = AssetDatabase.LoadAssetAtPath(path, typeof(ScriptableObject)) as ScriptableObject;
}
}