Version: Unity 6.0 (6000.0)
Language : English
Set default preset assets
Add preset support to custom types

Apply default presets to assets by folder

For large projects, you can use several presets for importing the same type of asset. For example, you might have a preset for importing default textures and another for lightmap textures. You can have separate subfolders in the Assets folder for each texture type and each subfolder can have its own preset that applies for its contents.

The following example applies a preset based on an asset’s location, with the following logic:

  • Unity applies presets from every folder between the project root and the asset’s own folder, starting with the outermost folder.
  • If more than one preset sets the same property, Unity applies the preset closest to the asset.
  • If no folder in the chain has a preset, Unity applies the default preset configured in the Preset Manager window.

Once you apply a preset to an asset, this script doesn’t update the asset if you later change the preset asset’s settings. This reflects the default behavior of presets.

To use this example, create a new script in an Editor folder, then copy and paste the following code, which applies presets based on their folder location:

using System;
using System.IO;
using UnityEditor;
using UnityEditor.Presets;

public class EnforcePresetPostProcessor : AssetPostprocessor
{
    void OnPreprocessAsset()
    {
        // Skip assets in packages, folders, scripts, and presets themselves.
        if (assetPath.StartsWith("Assets/") &&
            !AssetDatabase.IsValidFolder(assetPath) &&
            !assetPath.EndsWith(".cs") &&
            !assetPath.EndsWith(".preset"))
        {
            string folder = Path.GetDirectoryName(assetPath);
            ApplyPresetsFromFolderRecursively(folder);
        }
    }

    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.
        string parentFolder = Path.GetDirectoryName(folder);
        if (!string.IsNullOrEmpty(parentFolder))
        {
            ApplyPresetsFromFolderRecursively(parentFolder);
        }

        // Find all preset assets in this folder. Use the System.IO.Directory
        // method instead of the AssetDatabase, because the import might run
        // in a separate process, which prevents the AssetDatabase from
        // performing a global search.
        string[] presetPaths = Directory.GetFiles(folder, "*.preset",
            SearchOption.TopDirectoryOnly);
        Array.Sort(presetPaths);

        foreach (string presetPath in presetPaths)
        {
            Preset preset = AssetDatabase.LoadAssetAtPath<Preset>(presetPath);
            if (preset != null)
            {
                preset.ApplyTo(assetImporter);
            }
        }
    }
}

Update assets already imported

Because Unity’s asset pipeline caches import results, updating them requires registering a custom dependency between each asset and the presets in its folder hierarchy, so Unity knows to reimport the asset when a relevant preset changes. Unity doesn’t save these dependencies between Editor sessions, so the extended script also rebuilds them on every domain reload.

The following extended script adds this dependency tracking. It also adds a second class, UpdateFolderPresetDependency, which keeps the dependencies up to date whenever you add, remove, or move a preset after the initial import:

using System;
using System.Collections.Generic;
using System.IO;
using UnityEditor;
using UnityEditor.Experimental;
using UnityEditor.Presets;
using UnityEngine;

public class EnforcePresetPostProcessorWithDependencies : AssetPostprocessor
{
    const string k_DependencyPrefix = "PresetPostProcessor_";

    void OnPreprocessAsset()
    {
        // Skip assets in packages, folders, scripts, and presets themselves.
        if (assetPath.StartsWith("Assets/") &&
            !AssetDatabase.IsValidFolder(assetPath) &&
            !assetPath.EndsWith(".cs") &&
            !assetPath.EndsWith(".preset"))
        {
            string folder = Path.GetDirectoryName(assetPath);
            ApplyPresetsFromFolderRecursively(folder);
        }
    }

    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.
        string parentFolder = Path.GetDirectoryName(folder);
        if (!string.IsNullOrEmpty(parentFolder))
        {
            ApplyPresetsFromFolderRecursively(parentFolder);
        }

        // Add a dependency on the folder's custom key, so Unity reimports
        // the asset whenever a preset is added to or removed from
        // this folder.
        context.DependsOnCustomDependency(k_DependencyPrefix + folder);

        // Find all preset assets in this folder. Use the System.IO.Directory
        // method instead of the AssetDatabase, because the import might run
        // in a separate process, which prevents the AssetDatabase from
        // performing a global search.
        string[] presetPaths = Directory.GetFiles(folder, "*.preset",
            SearchOption.TopDirectoryOnly);
        Array.Sort(presetPaths);

        foreach (string presetPath in presetPaths)
        {
            Preset preset = AssetDatabase.LoadAssetAtPath<Preset>(presetPath);

            // Adding the preset as a dependency re-imports the asset once
            // the preset becomes available, or whenever the preset's values
            // change afterward.
            if (preset == null || preset.ApplyTo(assetImporter))
            {
                // Use DependsOnArtifact because presets are built-in assets.
                context.DependsOnArtifact(presetPath);
            }
        }
    }

    // Hashes every preset in a folder and registers the result as that
    // folder's custom dependency.
    internal static void RegisterFolderDependency(string folder,
        IEnumerable<string> presetPaths)
    {
        List<string> sortedPresetPaths = new List<string>(presetPaths);
        sortedPresetPaths.Sort();

        Hash128 hash = new Hash128();
        foreach (string presetPath in sortedPresetPaths)
        {
            Preset preset = AssetDatabase.LoadAssetAtPath<Preset>(presetPath);
            if (preset == null)
            {
                continue;
            }

            // Append both the path and the preset type, so assets reimport
            // whenever a preset's type changes.
            hash.Append(presetPath);
            hash.Append(preset.GetTargetFullTypeName());
        }

        AssetDatabase.RegisterCustomDependency(k_DependencyPrefix + folder,
            hash);
    }

    // Called every time the project loads or the code compiles.
    private static void OnPostprocessAllAssets(string[] importedAssets,
        string[] deletedAssets, string[] movedAssets,
        string[] movedFromAssetPaths, bool didDomainReload)
    {
        if (!didDomainReload)
        {
            return;
        }

        // AssetDatabase.FindAssets uses a glob filter, so this only looks
        // for .preset files. Group the paths by folder, because each folder
        // has a single custom dependency.
        Dictionary<string, List<string>> foldersWithPresets = new();
        foreach (string guid in AssetDatabase.FindAssets("glob:\"**.preset\""))
        {
            string presetPath = AssetDatabase.GUIDToAssetPath(guid);
            string folder = Path.GetDirectoryName(presetPath);
            if (!foldersWithPresets.TryGetValue(folder,
                out List<string> folderPresetPaths))
            {
                folderPresetPaths = new List<string>();
                foldersWithPresets.Add(folder, folderPresetPaths);
            }

            folderPresetPaths.Add(presetPath);
        }

        foreach (KeyValuePair<string, List<string>> folder in foldersWithPresets)
        {
            RegisterFolderDependency(folder.Key, folder.Value);
        }

        if (foldersWithPresets.Count > 0)
        {
            // Refresh so the AssetDatabase compares the rebuilt hashes with
            // the imported assets, and reimports any that are out of date.
            AssetDatabase.Refresh();
        }
    }
}

public class UpdateFolderPresetDependency : AssetsModifiedProcessor
{
    protected override void OnAssetsModified(string[] changedAssets,
        string[] addedAssets, string[] deletedAssets,
        AssetMoveInfo[] movedAssets)
    {
        // A preset has changed, been added, removed, or moved, so update
        // the dependency for its folder.
        HashSet<string> folders = new();
        AddPresetFolders(folders, changedAssets);
        AddPresetFolders(folders, addedAssets);
        AddPresetFolders(folders, deletedAssets);

        foreach (AssetMoveInfo movedAsset in movedAssets)
        {
            AddPresetFolders(folders, movedAsset.destinationAssetPath,
                movedAsset.sourceAssetPath);
        }

        if (folders.Count != 0)
        {
            // Update the dependencies outside of the AssetPostprocessor
            // calls, on the next Editor update.
            EditorApplication.delayCall +=
                () => DelayedDependencyRegistration(folders);
        }
    }

    // Collects the folder of every asset that's a preset. 
    static void AddPresetFolders(HashSet<string> folders,
        params string[] assets)
    {
        foreach (string asset in assets)
        {
            if (asset.EndsWith(".preset"))
            {
                folders.Add(Path.GetDirectoryName(asset));
            }
        }
    }

    static void DelayedDependencyRegistration(HashSet<string> folders)
    {
        foreach (string folder in folders)
        {
            // The glob search includes subfolders, so filter the results
            // down to the presets directly inside this folder.
            List<string> presetPaths = new List<string>();
            foreach (string guid in AssetDatabase.FindAssets(
                "glob:\"**.preset\"", new[] { folder }))
            {
                string presetPath = AssetDatabase.GUIDToAssetPath(guid);
                if (Path.GetDirectoryName(presetPath) == folder)
                {
                    presetPaths.Add(presetPath);
                }
            }

            EnforcePresetPostProcessorWithDependencies.RegisterFolderDependency(
                folder, presetPaths);
        }

        // Trigger a refresh so the AssetDatabase checks the updated
        // folder hashes.
        AssetDatabase.Refresh();
    }
}

Additional resources

Set default preset assets
Add preset support to custom types