큰 프로젝트에서는 동일한 에셋 타입을 임포트하기 위한 프리셋을 몇 개 사용할 수 있습니다. 예를 들어 텍스처 에셋에는 기본 텍스처를 임포트하는 프리셋을 사용하고 라이트맵 텍스처에는 다른 프리셋을 사용할 수 있습니다. 프로젝트 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>
/// This sample class applies Presets automatically to Assets in the folder containing the Preset and any subfolders.
/// The code is divided into three parts that set up importer dependencies to make sure the importers of all Assets stay deterministic.
///
/// OnPreprocessAsset:
/// This method goes from the root folder down to the Asset folder for each imported asset
/// and registers a CustomDependency to each folder in case a Preset is added/removed at a later time.
/// It then loads all Presets from that folder and tries to apply them to the Asset importer.
/// If it is applied, the method adds a direct dependency to each Preset so that the Asset can be re-imported when the Preset values are changed.
/// </summary>
public class EnforcePresetPostProcessor : AssetPostprocessor
{
void OnPreprocessAsset()
{
// The if(assetPath....) line ensures that the asset path starts with "Assets/" so that the AssetPostprocessor is not applied to Assets in a package.
// The Asset extension cannot end with .cs to avoid triggering a code compilation every time a Preset is created or removed.
// The Asset extension cannot end with .preset so that Presets don't depend on themselves, which would cause an infinite import loop.
// There may be more exceptions to add here depending on your project.
if (assetPath.StartsWith("Assets/") && !assetPath.EndsWith(".cs") && !assetPath.EndsWith(".preset"))
{
var path = Path.GetDirectoryName(assetPath);
ApplyPresetsFromFolderRecursively(path);
}
}
void ApplyPresetsFromFolderRecursively(string folder)
{
// Apply Presets in order starting from the parent folder to the Asset so that the Preset closest to the Asset is applied last.
var parentFolder = Path.GetDirectoryName(folder);
if (!string.IsNullOrEmpty(parentFolder))
ApplyPresetsFromFolderRecursively(parentFolder);
// Add a dependency to the folder Preset custom key
// so whenever a Preset is added to or removed from this folder, the Asset is re-imported.
context.DependsOnCustomDependency($"PresetPostProcessor_{folder}");
// Find all Preset Assets in this folder. Use the System.Directory method instead of the AssetDatabase
// because the import may run in a separate process which prevents the AssetDatabase from performing a global search.
var presetPaths =
Directory.EnumerateFiles(folder, "*.preset", SearchOption.TopDirectoryOnly)
.OrderBy(a => a);
foreach (var presetPath in presetPaths)
{
// Load the Preset and try to apply it to the importer.
var preset = AssetDatabase.LoadAssetAtPath<Preset>(presetPath);
// The script adds a Presets dependency to an Asset in two cases:
//1 If the Asset is imported before the Preset, the Preset will not load because it is not yet imported.
//Adding a dependency between the Asset and the Preset allows the Asset to be re-imported so that Unity loads
//the assigned Preset and can try to apply its values.
//2 If the Preset loads successfully, the ApplyTo method returns true if the Preset applies to this Asset's import settings.
//Adding the Preset as a dependency to the Asset ensures that any change in the Preset values will re-import the Asset using the new values.
if (preset == null || preset.ApplyTo(assetImporter))
{
// Using DependsOnArtifact here because Presets are native assets and using DependsOnSourceAsset would not work.
context.DependsOnArtifact(presetPath);
}
}
}
/// <summary>
/// This method with the didDomainReload argument will be called every time the project is being loaded or the code is compiled.
/// It is very important to set all of the hashes correctly at startup
/// because Unity does not apply the OnPostprocessAllAssets method to previously imported Presets
/// and the CustomDependencies are not saved between sessions and need to be rebuilt every time.
/// </summary>
private static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets,
string[] movedFromAssetPaths, bool didDomainReload)
{
if (didDomainReload)
{
// AssetDatabase.FindAssets uses a glob filter to avoid importing all objects in the project.
// This glob search only looks for .preset files.
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)
{
// When a new folder is found, create a new CustomDependency with the Preset name and the Preset type.
if (previousPath != string.Empty)
{
AssetDatabase.RegisterCustomDependency($"PresetPostProcessor_{previousPath}", hash);
atLeastOnUpdate = true;
}
hash = new Hash128();
previousPath = folder;
}
// Append both path and Preset type to make sure Assets get re-imported whenever a Preset type is changed.
hash.Append(path);
hash.Append(AssetDatabase.LoadAssetAtPath<Preset>(path).GetTargetFullTypeName());
}
// Register the last path.
if (previousPath != string.Empty)
{
AssetDatabase.RegisterCustomDependency($"PresetPostProcessor_{previousPath}", hash);
atLeastOnUpdate = true;
}
// Only trigger a Refresh if there is at least one dependency updated here.
if (atLeastOnUpdate)
AssetDatabase.Refresh();
}
}
}
/// <summary>
/// InitPresetDependencies:
/// This method is called when the project is loaded. It finds every imported Preset in the project.
/// For each folder containing a Preset, create a CustomDependency from the folder name and a hash from the list of Preset names and types in the folder.
///
/// OnAssetsModified:
/// Whenever a Preset is added, removed, or moved from a folder, the CustomDependency for this folder needs to be updated
/// so Assets that may depend on those Presets are reimported.
///
/// TODO: Ideally each CustomDependency should also be dependent on the PresetType,
/// so Textures are not re-imported by adding a new FBXImporterPreset in a folder.
/// This makes the InitPresetDependencies and OnPostprocessAllAssets methods too complex for the purpose of this example.
/// Unity suggests having the CustomDependency follow the form "Preset_{presetType}_{folder}",
/// and the hash containing only Presets of the given presetType in that folder.
/// </summary>
public class UpdateFolderPresetDependency : AssetsModifiedProcessor
{
/// <summary>
/// The OnAssetsModified method is called whenever an Asset has been changed in the project.
/// This methods determines if any Preset has been added, removed, or moved
/// and updates the CustomDependency related to the changed folder.
/// </summary>
protected override void OnAssetsModified(string[] changedAssets, string[] addedAssets, string[] deletedAssets, AssetMoveInfo[] movedAssets)
{
HashSet<string> folders = new HashSet<string>();
foreach (var asset in changedAssets)
{
// A Preset has been changed, so the dependency for this folder must be updated in case the Preset type has been changed.
if (asset.EndsWith(".preset"))
{
folders.Add(Path.GetDirectoryName(asset));
}
}
foreach (var asset in addedAssets)
{
// A new Preset has been added, so the dependency for this folder must be updated.
if (asset.EndsWith(".preset"))
{
folders.Add(Path.GetDirectoryName(asset));
}
}
foreach (var asset in deletedAssets)
{
// A Preset has been removed, so the dependency for this folder must be updated.
if (asset.EndsWith(".preset"))
{
folders.Add(Path.GetDirectoryName(asset));
}
}
foreach (var movedAsset in movedAssets)
{
// A Preset has been moved, so the dependency for the previous and new folder must be updated.
if (movedAsset.destinationAssetPath.EndsWith(".preset"))
{
folders.Add(Path.GetDirectoryName(movedAsset.destinationAssetPath));
}
if (movedAsset.sourceAssetPath.EndsWith(".preset"))
{
folders.Add(Path.GetDirectoryName(movedAsset.sourceAssetPath));
}
}
// Do not add a dependency update for no reason.
if (folders.Count != 0)
{
// The dependencies need to be updated outside of the AssetPostprocessor calls.
// Register the method to the next Editor update.
EditorApplication.delayCall += () =>
{
DelayedDependencyRegistration(folders);
};
}
}
/// <summary>
/// This method loads all Presets in each of the given folder paths
/// and updates the CustomDependency hash based on the Presets currently in that folder.
/// </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)
{
// Append both path and Preset type to make sure Assets get re-imported whenever a Preset type is changed.
hash.Append(presetPath);
hash.Append(AssetDatabase.LoadAssetAtPath<Preset>(presetPath).GetTargetFullTypeName());
}
AssetDatabase.RegisterCustomDependency($"PresetPostProcessor_{folder}", hash);
}
// Manually trigger a Refresh
// so that the AssetDatabase triggers a dependency check on the updated folder hash.
AssetDatabase.Refresh();
}
}
}
Did you find this page useful? Please give it a rating:
Thanks for rating this page!
What kind of problem would you like to report?
Thanks for letting us know! This page has been marked for review based on your feedback.
If you have time, you can provide more information to help us fix the problem faster.
Provide more information
You've told us this page needs code samples. If you'd like to help us further, you could provide a code sample, or tell us about what kind of code sample you'd like to see:
You've told us there are code samples on this page which don't work. If you know how to fix it, or have something better we could use instead, please let us know:
You've told us there is information missing from this page. Please tell us more about what's missing:
You've told us there is incorrect information on this page. If you know what we should change to make it correct, please tell us:
You've told us this page has unclear or confusing information. Please tell us more about what you found unclear or confusing, or let us know how we could make it clearer:
You've told us there is a spelling or grammar error on this page. Please tell us what's wrong:
You've told us this page has a problem. Please tell us more about what's wrong:
Thank you for helping to make the Unity documentation better!
Your feedback has been submitted as a ticket for our documentation team to review.
We are not able to reply to every ticket submitted.