큰 프로젝트에서는 동일한 에셋 타입을 임포트하기 위한 프리셋을 몇 개 사용할 수 있습니다. 예를 들어 텍스처 에셋에는 기본 텍스처를 임포트하는 프리셋을 사용하고 라이트맵 텍스처에는 다른 프리셋을 사용할 수 있습니다. 프로젝트 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.
방문하는 모든 웹사이트의 정보가 브라우저에서 쿠키 형태로 저장되거나 수집될 수 있습니다. 본 정보는 귀하와 귀하의 선호도, 기기에 대한 것이며, 귀하의 선호도에 따라 사이트가 동작하도록 하는 데 사용됩니다. 본 정보는 귀하를 직접적으로 식별하지 않으나 보다 개인화된 웹 경험을 제공하기 위해 사용됩니다. 그러나 일부 쿠키를 거부할 수 있습니다. 더 자세한 정보를 확인하고 기본 설정을 변경하려면 해당 카테고리의 제목을 클릭하세요. 그러나 일부 쿠키를 차단하면 귀하의 사이트 경험과 회사에서 제공하는 서비스에 영향을 미칠 수 있습니다.
추가 정보
본 쿠키는 동영상 및 실시간 채팅과 같은 고급 기능과 개인화를 허용합니다. 쿠키는 회사 또는 회사 페이지에 추가된 제3 서비스 사업자가 설정할 수 있습니다. 쿠키를 허용하지 않으면 일부 기능이 정상 작동하지 않을 수 있습니다.
이 쿠키는 방문자 수, 데이터 트래픽 정보를 확인해 회사 사이트의 성능을 측정하고 개선할 수 있도록 합니다. 또한 가장 인기가 많거나 인기가 적은 페이지를 확인하며 방문자가 사이트를 이동하는 방법을 확인할 수 있도록 합니다. 쿠키가 수집하는 모든 정보는 누적되며 익명 처리됩니다. 쿠키를 허용하지 않으면 귀하가 회사 사이트에 방문한 시기를 알 수 없습니다.
이 쿠키는 회사의 광고 협력사가 회사 사이트에 설정한 것입니다. 해당 협력사는 귀하의 관심사에 대한 프로파일을 만들고 다른 사이트에서도 관련 광고를 표시하기 위해 이 쿠키를 사용합니다. 이 쿠키는 귀하의 브라우저와 기기를 식별함으로써 동작합니다. 이 쿠키를 허용하지 않으면 다른 웹사이트에서 회사가 제공하는 맞춤형 광고를 경험할 수 없습니다.
이 쿠키는 웹사이트의 기능을 위해 필수적이며, 회사 시스템 내에서 종료할 수 없습니다. 이 쿠키는 개인정보 선호도, 로그인 또는 양식 작성과 같은 서비스 요청에 해당하는 귀하의 행위에 따라서만 주로 설정됩니다. 귀하의 브라우저에서 이 쿠키를 차단하거나 쿠키에 대해 알림 설정을 할 수 있지만, 이 경우 해당 사이트의 일부 기능이 동작하지 않을 수 있습니다.