前書き
この記事は、2023のUnityアドカレの12/21の記事です。
今年は、完走賞に挑戦してみたいと思います。Qiita君ぬい欲しい!
はじめに
Unityには、任意の拡張子のファイルに対し、ScriptedImporter
クラスを実装することで、そのファイルをUnityのアセットとして対応させることができます。以前には、これを使って、Shaderアセットを非標準のShader言語から作り出す方法を紹介しました。
このScriptedImporterですが、Unity組み込みで対応している拡張子(PNGやmp3など)は定義することができません。
[ScriptedImporter(1, "png")]
class MyPngImporter : ScriptedImporter
{
...
}
しかし、これは本当に不可能なのでしょうか?(疑心暗鬼)
PSDのインポータ
Unityは昔からPSDのインポートをサポートしています。それは、単純に全レイヤーを結合して一枚のTexture2Dとして取り込むという機能です。
しかし、追加パッケージにもcom.unity.2d.psdimporter
というものがあります。これは、レイヤー構造を認識してもっと賢く構造的にインポートをしてくれる代物です。
上記の理屈では、PSDはもう組み込みでサポートされてしまっているはずなので、独自のScriptedImporterで対応することができないはずです。
マニュアルを読んでみると…
デフォルトでは、.psd ファイルのインポートにはテクスチャインポーターを使用します。PSD Importer を使用して .psd ファイルをインポートする場合は、.psd ファイルを選択し、Importer ドロップダウンをクリックして、UnityEditor.U2D.PSD.PSDImporter を選択します。
どうやらやっぱり対応しているようです。そしてその下に、AssetDatabase.SetImporterOverride<TImporter>
を使うことで、スクリプトからもインポータを切り替えることができると書いてあります。
このプロセスを自動化するスクリプトを作成することもできます。以下は、AssetDatabase.SetImporterOverride メソッドを使用して自動化スクリプトを作成する方法の例です。
[MenuItem("Assets/2D Importer/Change PSD File Importer", false, 30)] static void ChangeImporter() { ...// すべての.psdファイルをサーチ AssetDatabase.SetImporterOverride<PSDImporter>(path); }
ということで、PSDImporterはなんだかpsd対応できています。PSDImporter.cs
を見るとその理由がわかります。
[ScriptedImporter(23100002, new string[] {"psb"}, new[] {"psd"}, AllowCaching = true)]
public partial class PSDImporter : ScriptedImporter, ISpriteEditorDataProvider
{
...
}
パラメータ名が書いていないので少しわかりづらいですが、第2引数が対象拡張子(exts
)、第3引数が「オーバライド対象の拡張子(overrideExts
)」となっています。これはUnity2021で追加されたオーバーロードのようです。
ということで、PSDを直接対象とはせずoverrideExts
の方に入れることで、デフォルト適用はされないものの、インポータのインスペクタから切り替えられるようになるようです。
ちなみに、2019まではAutoSelect
がこの代わりだったみたいです。
無理やりデフォルトにする
AssetPostProcessorを使うことで、アセットがインポートされる直前に差し替えてしまうことができます。そして、フィルターも自由にかけることができるので、拡張拡張子(??なんて呼ぶのでしょうか?)のようなものだけに対応することもできます。
class Processor : AssetPostprocessor
{
void OnPreprocessAsset()
{
if(assetPath.EndsWith(".neg.png"))
{
var overrideImporter = AssetDatabase.GetImporterOverride(assetPath);
if (overrideImporter == null)
{
AssetDatabase.SetImporterOverride<NegaPosiPngImporter>(assetPath);
}
}
}
}
これで、*.neg.png
だけNegaPosiPngImporter
でインポートすることができます。
[ScriptedImporter(1, new[] { "__png__" }, new [] { "png" })]
class NegaPosiPngImporter : ScriptedImporter
{
public override void OnImportAsset(AssetImportContext ctx)
{
var png = new Texture2D(2, 2);
png.LoadImage(System.IO.File.ReadAllBytes(assetPath));
png.Apply();
ctx.AddObjectToAsset("main obj", png);
ctx.SetMainObject(png);
var nega = new Texture2D(png.width, png.height){ name = $"nega {png.name}"};
nega.SetPixels(png.GetPixels()
.Select(c => new Color(1 - c.r, 1 - c.g, 1 - c.b, c.a))
.ToArray());
nega.Apply();
ctx.AddObjectToAsset("nega obj", nega);
}
}
ちなみに、このインポータを変更したという情報はMETAファイルにシリアライズされます。
C#ファイル(MonoScript)は無理
残念ながら、C#ファイルではこのインポータを切り替える機能は封印されていました。
[ScriptedImporter(1, new[] { "__cs__" }, new [] { "cs" })]
public class MonoScriptImporter2 : ScriptedImporter
{
public override void OnImportAsset(AssetImportContext ctx)
{
// ↓ このメソッド(`CreateMonoScript`)も使えるのかよくわからない謎API
// (どの道、ここまでたどり着かない)
var cs = MonoScripts.CreateMonoScript(File.ReadAllText(assetPath), "", "", "", false);
ctx.AddObjectToAsset("main obj", cs);
ctx.SetMainObject(cs);
ctx.AddObjectToAsset("txt obj", new TextAsset(File.ReadAllText(assetPath)));
}
}
また、AssetPostProcessorによる方法についてですが、MonoScriptはOnPreprocessAsset
で呼び出されません。代わりにOnPostprocessAllAssets
でSetImporterOverride
しても、無視されてしまいます。
AssetDatabase.SetImporterOverride<NegaPosiPngImporter>(assetPath);
Debug.Log(AssetDatabase.GetImporterOverride(assetPath)); // null
まとめ
従来、Unityの組み込みで対応している拡張子にはScriptedImporterを作ることができませんでした。しかし、ScriptedImporterAttributeで、拡張子を直指定するのではなく、overrideExts
に指定することでインスペクタから選択可能になります。また、AssetPostProcessor.OnPreprocessAsset
でAssetDatabase.SetImporterOverride
することで、自動選択させることができ、実質的に組み込み対応の拡張子のインポータを上書きすることができました。しかし、C#ファイルのMonoScriptAssetに関しては特別に不可能でした。
組み込みのインポータが気に入らない方は、ぜひ試してみてください!
プロジェクト