Unity - Hooking Into OnSave

Recently, for a project I was working on, I wanted a way to implement some custom logic that ran when I saved a scene in Unity. Some background, the project is using Unity’s new Tilemap feature for a top-down 2D map. However the data I want to store per tile is complicated enough that the built-in mechanisms Unity has for serialization don’t work well, so I’m storing the Tilemap information in a custom json file which is read a runtime to populate the Tilemap.

I wrote a custom Tilemap Brush that writes data to this custom object (side note, I really like the way the Brushes and Tiles work in Unity, as they are flexible enough to support this scenario. There’s nothing directly tieing the Brush to a Tilemap instance, allowing it to be overwritten to add tiles to another object. That may turn into a post sometime in the future). This object is still stored in memory though, and needs to be written to disk at somepoint.

Ideally, I decided, I wanted it to write to disk whenever the user saves the Scene. That way it gets written to disk along with everything else. It took a bit of trial and error before I found a solution that works.

First attempt - ISerializationCallbackReceiver

The first thing I tried was implementing ISerializationCallbackReceiver on a GameObject, and have the OnBeforeSerialize method write the data to a json file on disk. I implemented this and quickly discovered that this is not the best way to do this. OnBeforeSerialize is called much more often than just when Save is called. It is also called constantly when the object is selected in the Inspector. I believe Unity serializes the object, then displays the serialized fields in the inspector. OnBeforeSerialize ended up being called too frequently, the file was constantly written to disk. Not a viable solution.

Solution - AssetModificationProcessor

The solution that ended up working for me was implementing a custom AssetModificationProcessor. Using it is pretty straightforward, we just need to write a class that inherits from AssetModificationProcessor, then override the methods we want to implement. In this case I implemented OnWillSaveAssets. Unity calls this method on every AssetModificationProcessor before a scene is saved.

The only real tricky part is that we don’t have a direct reference to the data we want to serialize (OnBeforeSerialize was at least called on the object containing the custom data). To get the data, I’m using GameObject.FindObjectsOfType to get a reference to all the objects in the scene that have the component I want to serialize, then I iterate over all those components, calling a Save method in each which is responsible for writing the file to disk. Here’s the full implementation:

public class SpaceOpsAssetModificationProcessor : UnityEditor.AssetModificationProcessor
{
    public static string[] OnWillSaveAssets(string[] paths)
    {
        bool isSavingScene = false;

        foreach(string path in paths)
        {
            if(string.Equals(Path.GetExtension(path), ".unity"))
            {
                isSavingScene = true;
                break;
            }
        }

        if (isSavingScene)
        {
            MapDesignerComponent[] objects = GameObject.FindObjectsOfType<MapDesignerComponent>();

            foreach (MapDesignerComponent component in objects)
            {
                component.Save();
            }
        }

        return paths;
    }
}

One more thing to note, AssetModificaitonProcessor is called for all Assets, not just scenes. So above, we explicitly look to see if a scene is being saved (.unity extension), and if so then we do the custom serialization.

And that’s it. The hardest part is finding the AssetModificationProcessor class. It’s pretty easy to implement once that class is known.