【Unity】【エディタ拡張】アセットインポート時の処理を自動化するAssetPostprocessor入門

テクスチャの圧縮設定とかFBX Importerの設定とか、人間の仕事じゃないですね。
ということでアセットのインポート時の設定を自動化する際に使われるAssetPostprocessorについての入門記事です。

基本の使い方: PreProcessとPostProcess

使い方をTextureをインポートする想定で確認してみます。
まずは最小コードです。

using UnityEngine;
using UnityEditor;

public class AssetPostProcessorTest : AssetPostprocessor {
    
    private void OnPreprocessTexture()
    {
        // インポート前の処理
    }

    private void OnPostprocessTexture(Texture2D tex)
    {
        // インポート後の処理
    }
}

AssetPostprocessorを継承したクラスを作り、決まった名前のメソッドを定義するだけです。
これをEditorフォルダに入れてテクスチャをインポートすると処理が走ります。

下記のタイミングで処理が行われることが確認できました。

  • 初回インポート時
  • Reimport時
  • リネーム時
  • インスペクタから設定を変更してApplyした時

また、基底クラスのフィールドであるassetImporterを使うと、インスペクタから設定するような項目を編集できます。

f:id:halya_11:20180405165735p:plain

テクスチャの場合、assetImporterを取得してTextureImporterにキャストして使います。

using UnityEngine;
using UnityEditor;

public class AssetPostProcessorTest : AssetPostprocessor {
    
    private void OnPreprocessTexture()
    {
        // TextureImporterの設定項目を書き換える
        var importer = assetImporter as TextureImporter;
        importer.alphaIsTransparency = true;
    }

    private void OnPostprocessTexture(Texture2D tex)
    {
    }
}

上記ではOnPreprocessで処理をしていますが、OnPostprocessでも問題なく設定できました。
ただPreとPostの間に何か処理が挟まれうることを考えるとPreでできることはやっておいたほうが無難そうでしょうか。

また、assetPathでインポート処理中のアセットのパスを取得できます。

using UnityEngine;
using UnityEditor;

public class AssetPostProcessorTest : AssetPostprocessor {
    
    private void OnPreprocessTexture()
    {
        // 取れる
        Debug.Log(AssetDatabase.AssetPathToGUID(assetPath));
        // (初回インポート時は)取れない
        Debug.Log(AssetDatabase.LoadAssetAtPath<Texture>(assetPath));
    }

    private void OnPostprocessTexture(Texture2D tex)
    {
        // 取れる
        Debug.Log(AssetDatabase.AssetPathToGUID(assetPath));
        // (初回インポート時は)取れない
        Debug.Log(AssetDatabase.LoadAssetAtPath<Texture>(assetPath));
    }
}

上記では試しにassetPathからGUIDとアセット自体を取得してみています。

GUIDはこの時点で決まっているようで、このアセットパスから取得できました。
しかしアセット自体が作られたり更新されたりするのはOnPostprocessよりも後のようで、この時点では取れませんでした。

アセット自体の情報にアクセスしたければ、OnPostprocessTextureの引数のTexture2Dを使います。
ただしこの引数のオブジェクトはこの後破棄される運命にあるためここで変更を加えても意味ないらしいです。

https://anchan828.github.io/editor-manual/web/assetpostprocessor.htmlanchan828.github.io

テクスチャ以外のPre/Postメソッド

Textureの他にもいろんな種類のPreprocessメソッドが存在します。

  • OnPreprocessModel
  • OnPreprocessAudio
  • OnPreprocessAnimation
  • OnPreprocessSpeedTree

Postも色々定義されています。

  • OnPostprocessModel
  • OnPostprocessAudio
  • OnPostprocessSprites
  • OnPostprocessSpeedTree
  • OnPostprocessGameObjectWithUserProperties
  • OnPostprocessAssetbundleNameChanged

詳しくはマニュアルを参照してください。

docs.unity3d.com

OnPostprocessAllAssets()はすべてのアセットがインポートされた後に呼ばれる

OnPostprocessAllAssets()というメソッドはすべてのアセットのインポート処理が終わった後に呼ばれます。
また、PostprocessXXX系よりも後によばれます。

例えば2つのテクスチャを選択して再インポートを掛けたら、OnPostprocessTextureが二回呼ばれた後にOnPostprocessAllAssetsが一回呼ばれます。

using UnityEngine;
using UnityEditor;

public class AssetPostProcessorTest : AssetPostprocessor {
    
    // staticメソッドとして定義する
    private static void OnPostprocessAllAssets(string[] importedAssetPaths, string[] deletedAssetPaths, string[] movedAssetPaths, string[] movedFromAssetPaths)
    {
        // あらゆるアセットのインポート処理の後によばれる
        // AssetDatabase.LoadAssetAtPath()でアセットを取得できる
    }
}

引数にはアセットのパスが入っています。
また、この時点ではアセットが作られているため、AssetDatabase.LoadAssetAtPath()でアセットを取得できます。

引数のimportedAssetsとdeletedAssetsは名前のままの意味です。
アセットを移動したときには、移動先のパスがmovedAssetsに、移動前のパスがmovedFromAssetPathsに入ってきます。

docs.unity3d.com

処理の順番を決めるGetPostprocessOrder()

AssetPostprocessorを継承したクラスを複数作った場合にはGetPostprocessOrder()を定義することで処理の順番を決められます。
値が小さい順に処理されます。

using UnityEngine;
using UnityEditor;

public class AssetPostProcessorTest : AssetPostprocessor {

    public override int GetPostprocessOrder()
    {
        return 1;
    }

    private void OnPreprocessTexture()
    {
        Debug.Log(GetPostprocessOrder());
    }

    private void OnPostprocessTexture(Texture2D tex)
    {
        Debug.Log(GetPostprocessOrder());
    }
}

public class AssetPostProcessorTest2 : AssetPostprocessor {

    public override int GetPostprocessOrder()
    {
        return 2;
    }

    private void OnPreprocessTexture()
    {
        Debug.Log(GetPostprocessOrder());
    }

    private void OnPostprocessTexture(Texture2D tex)
    {
        Debug.Log(GetPostprocessOrder());
    }
}

なお、OnPostprocessAllAssets()にはこの値は影響しないようです。

docs.unity3d.com

GetVersion()でバージョン管理

GetVersion()を使うとバージョンを定義できます。
バージョンが変わると、そのPostprocessorの処理がすべてのアセットに対して走ります。

using UnityEngine;
using UnityEditor;

public class AssetPostProcessorTest : AssetPostprocessor {

    public override uint GetVersion()
    {
        return 1;
    }

    private void OnPreprocessTexture()
    {
        Debug.Log(assetPath);
    }
}

いつか使いたいときが来るかもしれないので覚えておいたらいいくらいの機能かと思います。

docs.unity3d.com

dllにすべき?

マニュアルでは、AssetPostprocessorをdll化して使うことを推奨しています。
これはスクリプトコンパイルエラーが生じたときに、アセットがインポートされると、AssetPostprocessorが前回コンパイルが成功したときの処理でインポートを行ってしまうためです。

確かにケースによっては思いもよらぬことが起こりそう・・
ですがdllにしなくても使えはするので、ケースバイケースで良いのかなと思います。

2018ではOnPreprocessAssetが追加される

ちなみにですがUnity2018からはOnPreprocessAsset()が追加されているようです。
baba-s.hatenablog.com

参考

docs.unity3d.com

https://anchan828.github.io/editor-manual/web/assetpostprocessor.htmlanchan828.github.io