大きなプロジェクトの場合、同じタイプのアセットをインポートするためにいくつかのプリセットを使用する場合があります。例えば、テクスチャアセットの場合、デフォルトのテクスチャをインポートするためのプリセットと、ライトマップテクスチャのためのプリセットが必要な場合があります。プロジェクトの Assets フォルダーには、これらのテクスチャのタイプごとに別々のフォルダーがあります。
次のスクリプトは、アセットを加えるフォルダーに基づいてプリセットを適用します。このスクリプトは、アセットと同じフォルダーにあるプリセットを選択します。フォルダーにプリセットがない場合、このスクリプトは親フォルダーを検索します。親フォルダーにプリセットがない場合、Preset ウィンドウで指定されているデフォルトのプリセットを使用します。
このスクリプトを使用するには、Project ウィンドウで Editor という名前の新しいフォルダーを作成し、このフォルダーに新しい C# スクリプトを作成し、このスクリプトをコピーして貼り付けます。
using System.Collections.Generic;
using System.IO;
using System.Linq;
using UnityEditor;
using UnityEditor.Experimental;
using UnityEditor.Presets;
using UnityEngine;
namespace PresetsPerFolder
{
/// <summary>
/// このサンプルクラスは、プリセットを含むフォルダと任意のサブフォルダ内のアセットに自動的にプリセットを適用します。
/// コードは 3 つの部分に分かれており、インポータの依存関係を設定し、すべての Asset のインポーターを確定的にします。
///
/// OnPreprocessAsset:
/// このメソッドは、インポートされた各アセットに対し、ルートフォルダーから Asset フォルダーまで移動します。
/// そして、後でプリセットが追加/削除された場合に備えて、各フォルダーに CustomDependency を登録します。
/// その後、そのフォルダーからすべてのプリセットをロードし、Asset インポーターに適用しようとします。
/// 適用された場合、このメソッドは各プリセットに直接依存関係を追加し、プリセットの値が変更されたときにアセットを再インポートできるようにします。
/// </summary>
public class EnforcePresetPostProcessor : AssetPostprocessor
{
void OnPreprocessAsset()
{
// if(assetPath...) 行は、AssetPostprocessor がパッケージ内の Assets に適用されないように、アセットのパスを "Assets/" で始めさせます。
//プリセットが作成または削除されるたびにコードのコンパイルをトリガーするのを避けるために、
// Asset の拡張子は .cs で終わらせることはできません。
// Asset の拡張子は .preset で終わることはできません。これは、Preset がそれ自体に依存して、無限 import ループを引き起こすことがないようにするためです。
// プロジェクトによっては、さらに多くの例外を追加する必要があるかもしれません。
if (assetPath.StartsWith("Assets/") && !assetPath.EndsWith(".cs") && !assetPath.EndsWith(".preset"))
{
var path = Path.GetDirectoryName(assetPath);
ApplyPresetsFromFolderRecursively(path);
}
}
void ApplyPresetsFromFolderRecursively(string folder)
{
// 順に親フォルダーから Asset にプリセットを適用し、Asset に最も近いプリセットが最後に適用されるようにします。
var parentFolder = Path.GetDirectoryName(folder);
if (!string.IsNullOrEmpty(parentFolder))
ApplyPresetsFromFolderRecursively(parentFolder);
// フォルダープリセットカスタム キーに依存関係を追加します。
// プリセットがこのフォルダーに追加または削除されるたびに、Asset が再インポートされるようにします。
context.DependsOnCustomDependency($"PresetPostProcessor_{folder}");
// このフォルダー内のすべてのプリセットアセットを検索します。AssetDatabase の代わりに System.Directory メソッドを使用します。
// インポートが別のプロセスで実行され、AssetDatabase がグローバル検索を実行できなくなる可能性があるためです。
var presetPaths =
Directory.EnumerateFiles(folder, "*.preset", SearchOption.TopDirectoryOnly)
.OrderBy(a => a);
foreach (var presetPath in presetPaths)
{
// プリセットをロードし、インポーターに適用しようとします。
var preset = AssetDatabase.LoadAssetAtPath<Preset>(presetPath);
// このスクリプトは、以下の 2 つのケースでアセットにプリセットの依存関係を追加します。
//1 アセットがプリセットより先にインポートされた場合、プリセットはまだインポートされていないため、ロードされません。
//アセットとプリセットの間に依存関係を加えると、アセットを再インポートして、
//Unity は割り当てられたプリセットをロードし、その値を適用することができます。
//2 プリセットのロードに成功した場合、プリセットがこのアセットのインポート設定に適用されると、ApplyTo メソッドは true を返します。
//アセットへの依存関係としてプリセットを追加すると、プリセット値の変更が発生すると、新しい値を使用してアセットが再インポートされます。
if (preset == null || preset.ApplyTo(assetImporter))
{
// プリセットはネイティブ アセットであり、DependsOnSourceAsset を使用すると機能しないため、ここで DependsOnArtifact を使用します。
context.DependsOnArtifact(presetPath);
}
}
///<summary>
/// このメソッドと didDomainReload 引数は、プロジェクトがロードされたり、コードがコンパイルされたりするたびに呼び出されます。
/// スタートアップ時にすべてのハッシュを正しく設定することが非常に重要です。
///なぜなら、Unity は以前にインポートしたプリセットに OnPostprocessAllAssets メソッドを適用しないためです。
/// また、CustomDependencies はセッション間で保存されないため、毎回再構築する必要があります。
/// </summary>
private static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets,
string[] movedFromAssetPaths, bool didDomainReload)
{
if (didDomainReload)
{
// AssetDatabase.FindAssets は、プロジェクト内のすべてのオブジェクトのインポートを避けるため、グロブフィルターを使用します。
// このグロブ検索では、.preset ファイルのみを検索します。
var allPaths = AssetDatabase.FindAssets("glob:\"**.preset\"")
.Select(AssetDatabase.GUIDToAssetPath)
.OrderBy(a => a)
.ToList();
bool atLeastOnUpdate = false;
string previousPath = string.Empty;
Hash128 hash = new Hash128();
for (var index = 0; index < allPaths.Count; index++)
{
var path = allPaths[index];
var folder = Path.GetDirectoryName(path);
if (folder != previousPath)
{
// 新しいフォルダーが見つかったら、プリセット名とプリセットタイプを指定して新しい CustomDependency を作成します。
if (previousPath != string.Empty)
{
AssetDatabase.RegisterCustomDependency($"PresetPostProcessor_{previousPath}", hash);
atLeastOnUpdate = true;
}
hash = new Hash128();
previousPath = folder;
}
// プリセットタイプが変更されたときに、アセットが再インポートされるように、パスとプリセットタイプの両方を追加します。
hash.Append(path);
hash.Append(AssetDatabase.LoadAssetAtPath<Preset>(path).GetTargetFullTypeName());
}
// 最後のパスを登録します。
if (previousPath != string.Empty)
{
AssetDatabase.RegisterCustomDependency($"PresetPostProcessor_{previousPath}", hash);
atLeastOnUpdate = true;
}
// 少なくとも 1 つの依存関係がここで更新された場合にのみ、Refresh をトリガーします。
if (atLeastOnUpdate)
AssetDatabase.Refresh();
}
}
///<summary>
/// InitPresetDependencies:
/// このメソッドは、プロジェクトがロードされたときに呼び出されます。このメソッドは、プロジェクトにインポートされたすべてのプリセットを見つけます。
/// プリセットを含む各フォルダーに対して、フォルダー名から CustomDependency を作成し、フォルダー内のプリセット名と種類のリストからハッシュを作成します。
///
/// OnAssetsModified:
/// プリセットが追加、削除、またはフォルダーから移動されるたびに、このフォルダーの CustomDependency を更新する必要があります。
/// プリセットに依存する可能性のあるアセットは再インポートされます。
///
/// TODO: 理想的には、各 CustomDependency は PresetType にも依存する必要があります。
/// フォルダーに新しい FBXImporterPreset を追加しても Texture が再インポートされないようにするためです。
/// それは、InitPresetDependencies と OnPostprocessAllAssets メソッドを、この例の目的のためにはあまりにも複雑にしてしまいます。
/// Unity では、CustomDependency を "Preset_{presetType}_{folder}" のような形式にすることを提案しています。
/// そして、ハッシュは、そのフォルダーの指定された presetType のプリセットだけを含むよう提案しています。
/// </summary>
public class UpdateFolderPresetDependency : AssetsModifiedProcessor
{
///<summary>
/// OnAssetsModified メソッドは、プロジェクト内で Asset が変更されるたびに呼び出されます。
/// このメソッドは、プリセットが追加、削除、または移動されたかどうかを判断し、CustomDependency を更新します。
/// そして、変更されたフォルダに関連する CustomDependency を更新します。
/// </summary>
protected override void OnAssetsModified(string[] changedAssets, string[] addedAssets, string[] deletedAssets, AssetMoveInfo[] movedAssets)
{
HashSet<string> folders = new HashSet<string>();
foreach (var asset in changedAssets)
{
// プリセットが変更されました。そのため、プリセットタイプが変更された場合、このフォルダーの依存関係を更新する必要があります。
if (asset.EndsWith(".preset"))
{
folders.Add(Path.GetDirectoryName(asset));
}
}
foreach (var asset in addedAssets)
{
// 新しいプリセットが追加されました。そのため、このフォルダーの依存関係を更新する必要があります。
if (asset.EndsWith(".preset"))
{
folders.Add(Path.GetDirectoryName(asset));
}
}
foreach (var asset in deletedAssets)
{
// プリセットが削除されました。そのため、このフォルダーの依存関係を更新する必要があります。
if (asset.EndsWith(".preset"))
{
folders.Add(Path.GetDirectoryName(asset));
}
}
foreach (var movedAsset in movedAssets)
{
// プリセットが移動されました。そのため、既存のおよび新しいフォルダーの依存関係を更新する必要があります。
if (movedAsset.destinationAssetPath.EndsWith(".preset"))
{
folders.Add(Path.GetDirectoryName(movedAsset.destinationAssetPath));
}
if (movedAsset.sourceAssetPath.EndsWith(".preset"))
{
folders.Add(Path.GetDirectoryName(movedAsset.sourceAssetPath));
}
}
// 理由なしに依存関係の更新はしないでください。
if (folders.Count != 0)
{
// 依存関係は AssetPostprocessor 以外の呼び出しで更新する必要があります。
// このメソッドを次のエディターの更新に登録します。
EditorApplication.delayCall += () =>
{
DelayedDependencyRegistration(folders);
};
}
}
///<summary>
/// このメソッドは、与えられたフォルダパスのそれぞれにあるすべてのプリセットを読み込みます。
/// そして、そのフォルダーに現在あるプリセットに基づいて CustomDependency ハッシュを更新します。
/// </summary>
static void DelayedDependencyRegistration(HashSet<string> folders)
{
foreach (var folder in folders)
{
var presetPaths =
AssetDatabase.FindAssets("glob:\"**.preset\"", new[] { folder })
.Select(AssetDatabase.GUIDToAssetPath)
.Where(presetPath => Path.GetDirectoryName(presetPath) == folder)
.OrderBy(a => a);
Hash128 hash = new Hash128();
foreach (var presetPath in presetPaths)
{
// プリセットタイプが変更されるたびにアセットが再インポートされるように、パスとプリセットタイプの両方を追加します。
hash.Append(presetPath);
hash.Append(AssetDatabase.LoadAssetAtPath<Preset>(presetPath).GetTargetFullTypeName());
}
AssetDatabase.RegisterCustomDependency($"PresetPostProcessor_{folder}", hash);
}
// 手動でリフレッシュをトリガーします
//すると、 AssetDatabase が更新されたフォルダーのハッシュに対して依存関係チェックをトリガーします。
AssetDatabase.Refresh();
}
}
}