Skip to content

Instantly share code, notes, and snippets.

@giacomelli
Last active September 10, 2021 21:43
Show Gist options
  • Save giacomelli/2d561e29beadab641a4f8b56954f53f7 to your computer and use it in GitHub Desktop.
Save giacomelli/2d561e29beadab641a4f8b56954f53f7 to your computer and use it in GitHub Desktop.
Using an AssetPostprocessor + EditorWindow to keep assets organized on Unity projects: http://diegogiacomelli.com.br/using-an-assetpostprocessor-editorwindow-to-keep-assets-organized-on-unity-projects
using UnityEditor;
using UnityEngine;
/// <summary>
/// Folder organizer editor window.
/// http://diegogiacomelli.com.br/using-an-assetpostprocessor-editorwindow-to-keep-assets-organized-on-unity-projects
/// </summary>
public class FolderOrganizerEditorWindow : EditorWindow
{
FolderOrganizerSettings _settings;
SerializedObject _settingsSO;
SerializedProperty _ignoreFolders;
SerializedProperty _validateOnImport;
SerializedProperty _foldersRule;
[MenuItem("Window/Folder organizer")]
public static void ShowWindow()
{
EditorWindow.GetWindow<FolderOrganizerEditorWindow>("Folder organizer");
}
private void OnEnable()
{
_settings = FolderOrganizerSettings.Instance;
_settingsSO = new SerializedObject(_settings);
_ignoreFolders = _settingsSO.FindProperty(nameof(FolderOrganizerSettings.IgnoreFolders));
_validateOnImport = _settingsSO.FindProperty(nameof(FolderOrganizerSettings.ValidateOnImport));
_foldersRule = _settingsSO.FindProperty(nameof(FolderOrganizerSettings.FoldersRule));
}
private void OnGUI()
{
EditorGUI.BeginChangeCheck();
EditorGUILayout.PropertyField(_ignoreFolders, true);
EditorGUILayout.PropertyField(_validateOnImport);
EditorGUILayout.PropertyField(_foldersRule, true);
if (GUILayout.Button("Validate"))
{
FolderOrganizerUtility.Validate(AssetDatabase.GetAllAssetPaths(), true);
}
if (EditorGUI.EndChangeCheck())
_settingsSO.ApplyModifiedProperties();
}
}
using System.Collections.Generic;
using UnityEditor;
/// <summary>
/// Folder organizer post processor.
/// http://diegogiacomelli.com.br/using-an-assetpostprocessor-editorwindow-to-keep-assets-organized-on-unity-projects
/// </summary>
public class FolderOrganizerPostProcessor : AssetPostprocessor
{
static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths)
{
if (FolderOrganizerSettings.Instance.ValidateOnImport)
{
var assets = new List<string>();
assets.AddRange(importedAssets);
assets.AddRange(movedAssets);
FolderOrganizerUtility.Validate(assets);
}
}
}
using System;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text.RegularExpressions;
using UnityEditor;
using UnityEngine;
/// <summary>
/// Folder organizer settings.
/// http://diegogiacomelli.com.br/using-an-assetpostprocessor-editorwindow-to-keep-assets-organized-on-unity-projects
/// </summary>
public class FolderOrganizerSettings : ScriptableObject
{
[Tooltip("Validation rules for folders organization")]
public FolderRule[] FoldersRule;
[Tooltip("Should run validations when an asset is imported")]
public bool ValidateOnImport = true;
[Tooltip("Folders ignored by the Folder organizer")]
public string[] IgnoreFolders = new string[] { "Assets/Standard Assets", "Packages" };
static FolderOrganizerSettings _instance;
public static FolderOrganizerSettings Instance => _instance ?? (_instance = LoadAsset());
private static FolderOrganizerSettings LoadAsset()
{
var path = GetAssetPath();
var asset = AssetDatabase.LoadAssetAtPath<FolderOrganizerSettings>(path);
if (asset == null)
{
asset = CreateInstance<FolderOrganizerSettings>();
AssetDatabase.CreateAsset(asset, path);
AssetDatabase.SaveAssets();
}
return asset;
}
private static string GetAssetPath([CallerFilePath] string callerFilePath = null)
{
var folder = Path.GetDirectoryName(callerFilePath);
folder = folder.Substring(folder.LastIndexOf("/Assets/", StringComparison.Ordinal) + 1);
return Path.Combine(folder, "FolderOrganizerSettings.asset");
}
}
/// <summary>
/// Folder rule.
/// http://diegogiacomelli.com.br/using-an-assetpostprocessor-editorwindow-to-keep-assets-organized-on-unity-projects
/// </summary>
[Serializable]
public class FolderRule
{
[Tooltip("Regular expression pattern to find assets by path, e.g., '.+\\.png'")]
public string AssetFindPattern;
[Tooltip("Expected folders for assets found by the Asset Find Pattern, e.g., 'Assets/Sprites'")]
public string[] ExpectedFolders = new string[1];
public bool Validate(string assetPath)
{
if (Regex.IsMatch(assetPath, AssetFindPattern))
{
var currentAssetFolder = Path.GetDirectoryName(assetPath);
if (ExpectedFolders.All(expected => expected != currentAssetFolder))
{
var assetName = Path.GetFileName(assetPath);
Debug.LogWarning($"Asset <b>{assetName}</b> is in folder <b><color=red>{currentAssetFolder}</color></b>, but should be moved to folder <b><color=green>{String.Join(" or ", ExpectedFolders)}</color></b>.");
return false;
}
}
return true;
}
}
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEngine;
/// <summary>
/// Folder organizer utility.
/// http://diegogiacomelli.com.br/using-an-assetpostprocessor-editorwindow-to-keep-assets-organized-on-unity-projects
/// </summary>
public static class FolderOrganizerUtility
{
public static void Validate(IEnumerable<string> assets, bool interactable = false)
{
if (interactable)
Debug.Log("Folder organizer validation started.");
var settings = FolderOrganizerSettings.Instance;
var ignoreFolders = settings.IgnoreFolders;
var folderRules = settings.FoldersRule;
var assetsCount = assets.Count();
float step = 0;
var validAssetsCount = 0;
var invalidAssetsCount = 0;
foreach (string assetPath in assets)
{
step++;
if (interactable && EditorUtility.DisplayCancelableProgressBar("Folder organizer", "Validating assets...", step / assetsCount))
break;
// If asset path starts with any ignore folders.
if (ignoreFolders.Any(assetPath.StartsWith))
continue;
foreach (var folderRule in folderRules)
{
if (folderRule.Validate(assetPath))
validAssetsCount++;
else
invalidAssetsCount++;
}
}
if (interactable)
{
EditorUtility.ClearProgressBar();
var msg = invalidAssetsCount > 0
? $"{validAssetsCount} are in the right folder and {invalidAssetsCount} are in the wrong folder.\nMore details on Console Window."
: "All assets are valid.";
Debug.Log(msg);
EditorUtility.DisplayDialog("Folder organizer", msg, "OK");
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment