【Unity】指定したアセットだけを保存する方法【AssetDatabase.SaveAssetIfDirty】

# はじめに
`EditorUtility.SetDirty()` してあるアセットは `AssetDatabase.SaveAssets()` によって一括で保存することができますが、エディタ拡張を作ってると「このアセット１つだけ保存したい！」という場面はけっこうあると思います。

* [[Unity Editor] There is any way to tell unity to save a single asset? I dont want to call AssetDatabase.SaveAssets(), since save all assets...](https://www.reddit.com/r/Unity3D/comments/j8t7bx/unity_editor_there_is_any_way_to_tell_unity_to/)（１年前のRedditの投稿）


これを実現するやり方は個人的に３年くらい前から探してたのですが、最近になってようやくやり方がわかったのでご紹介します。

* Unity2020.3.16以降のやり方：
    * `AssetDatabase.SaveAssetIfDirty()` を使う
* それ以前のUnityでのやり方：
    * `AssetModificationProcessor.OnWillSaveAssets(string[])` で頑張る

の２種類があります。

# Unity2020.3.16以降のやり方

[AssetDatabase.SaveAssetIfDirty](AssetDatabase.SaveAssetIfDirty
) を使います。

以下はリファレンスからの引用です。

><b>Description</b>
>Writes all unsaved changes to the specified asset to disk.
>
>This function is similar to SaveAssets, and likewise should not be called during serialization.
>
>It does not invoke AssetModificationProcessor.OnWillSaveAssets, as the asset is directly specified.

DeepL翻訳：
>指定されたアセットに対する保存されていないすべての変更をディスクに書き込みます。
>
>この関数は、SaveAssets に似ており、同様にシリアライズ中に呼び出されるべきではありません。
>
>アセットが直接指定されているので、AssetModificationProcessor.OnWillSaveAssets は呼び出されません。

以下の２種類のオーバーロードがあります。

* `public static void SaveAssetIfDirty(Object obj);`
* `public static void SaveAssetIfDirty(GUID guid);`

余談ですが `GUID` っていう構造体があることを今回初めて知りました。

# それ以前のUnityでのやり方

[AssetModificationProcessor.OnWillSaveAssets(string[])](https://docs.unity3d.com/2019.4/Documentation/ScriptReference/AssetModificationProcessor.OnWillSaveAssets.html) を使用します。

以下のようなヘルパークラスを作ると便利です。

```AssetDatabaseHelper.cs
#if UNITY_EDITOR
using System.Linq;
using UnityEditor;
using UnityEngine;

public static class AssetDatabaseHelper
{
    private static string _pathToSave;

    public static void SaveAssetIfDirty(Object obj)
    {
        _pathToSave = AssetDatabase.GetAssetPath(obj);
        AssetDatabase.SaveAssets();
        _pathToSave = null;
    }

    public static void SaveAssetIfDirty(GUID guid)
    {
        _pathToSave = AssetDatabase.GUIDToAssetPath(guid);
        AssetDatabase.SaveAssets();
        _pathToSave = null;
    }

    private class AssetModificationProcessor : UnityEditor.AssetModificationProcessor
    {
        private static readonly string[] _pathsToSave = new string[1];

        // 指定したアセットだけ保存する
        static string[] OnWillSaveAssets(string[] paths)
        {
            _pathsToSave[0] = null;

            if (paths.Length == 0) return paths;
            if (_pathToSave == null) return paths;

            if (paths.Contains(_pathToSave) == false)
            {
                // NOTE: 空の配列を返す
                return _pathsToSave;
            }

            _pathsToSave[0] = _pathToSave;
            return _pathsToSave;
        }
    }
}
#endif
```

動作原理としては

* `OnWillSaveAssets(string[] paths)` に保存しようとしているアセットのパスが渡ってくる
* 保存したいアセットのパスだけを返すことで保存するアセットをフィルタする

みたいな感じです。

`AssetDatabaseHelper` 以外からの保存には影響を与えないようにするため、保存し終わったら何もしない状態に戻しておきます。

　
`AssetModificationProcessor.OnWillSaveAssets(string[])` についてはUnity2019くらいから `SaveAssetsProcessor` を継承する形から `UnityEditor.AssetModificationProcessor` を継承する形に変わったみたいですが、機能自体は変わってなさそうなので比較的古いUnityでも実現できそうです。

# あとがき
今回の `AssetDatabase.SaveAssetIfDirty()` は「こういうAPIほしいんだよな〜」となんとなくタイプしたら出てきた補完一覧から知ったので、ほしいAPIがある場合はたまにそういうことをするといつの間にか実装されてたりするのでおすすめです。