🧹 Bringing Order to Chaos: A Tool to Keep Your Prefabs Clean

Importing asset packs into Unity can feel like grocery shopping while you're hungry: you end up with way more than you actually need. In the end, you use one or two prefabs and are left with 200 files cluttering your project, slowing you down, polluting your Git repo, and quietly sabotaging your sanity.
To avoid this mess, I came up with a method: I use a separate Unity project just for importing asset packs. It's like a clean room where I sort things out. I prepare clean, polished prefabs there, and then I want to export only the essential files into my main project via a .unitypackage
. And that, my friend, is often where the pain begins.
So I built a tool to fix that: Prefab Asset Organizer.
The Concept
Here's the pitch: you select a prefab, hit a button, and voilĂ ! The tool:
- creates a new folder named after your prefab,
- adds neatly organized subfolders inside ("Textures", "Materials", "Animations", etc.),
- and copies over all the prefab's dependencies, sorted properly like a well-trained intern.
Optionally, it can also export the scripts used in the prefab, without judging your code quality.
What the Tool Actually Does
This isn’t just a brute-force copy job. The tool:
- smartly identifies every dependency your prefab needs,
- places each file into the appropriate folder,
- updates the prefab so that it points to the new asset locations (even those hidden sprites buried inside textures),
- and saves you from the classic “why did everything break when I moved a file?” scenario.
It’s like Marie Kondo meets EditorWindow
, minus the Netflix contract.
Why This Tool Exists
This tool was born from a very real frustration: navigating a project where assets are scattered everywhere is tedious and error-prone. I needed a reliable way to bundle just the necessary files for a prefab, without resorting to tedious manual sorting or bloated automation tools. The goal was to build something lightweight, focused, and effective, a productivity booster that doesn’t get in your way.
The Code
using System.IO;
using System.Linq;
using UnityEditor;
using UnityEngine;
namespace Editor
{
public class PrefabAssetOrganizer : EditorWindow
{
private GameObject selectedPrefab;
private bool exportScripts = true;
[MenuItem("Tools/Prefab Asset Organizer")]
public static void ShowWindow()
{
GetWindow<PrefabAssetOrganizer>("Prefab Asset Organizer");
}
private void OnGUI()
{
GUILayout.Label("Select Prefab to organize", EditorStyles.boldLabel);
selectedPrefab = (GameObject)EditorGUILayout.ObjectField("Prefab", selectedPrefab, typeof(GameObject), false);
exportScripts = EditorGUILayout.Toggle("Export Scripts?", exportScripts);
if (GUILayout.Button("Organize Assets"))
{
if (selectedPrefab)
{
OrganizeAssets(selectedPrefab);
}
else
{
Debug.LogError("No prefab selected.");
}
}
}
private void OrganizeAssets(GameObject prefab)
{
string prefabPath = AssetDatabase.GetAssetPath(prefab);
string baseDirectory = Path.Combine(Path.GetDirectoryName(prefabPath), prefab.name);
var folders = exportScripts
? new string[] { "Prefabs", "Materials", "Models", "Textures", "Scripts", "Animations", "RenderTextures" }
: new string[] { "Prefabs", "Materials", "Models", "Textures", "Animations", "RenderTextures" };
CreateFolders(baseDirectory, folders);
string newPrefabPath = Path.Combine(baseDirectory, "Prefabs", prefab.name + ".prefab");
AssetDatabase.CopyAsset(prefabPath, newPrefabPath);
GameObject newPrefab = AssetDatabase.LoadAssetAtPath<GameObject>(newPrefabPath);
string[] dependencies = AssetDatabase.GetDependencies(prefabPath, true);
foreach (string dependencyPath in dependencies)
{
if (dependencyPath == prefabPath) continue;
string fileName = Path.GetFileName(dependencyPath);
string targetPath = "";
if (dependencyPath.EndsWith(".mat"))
targetPath = Path.Combine(baseDirectory, "Materials", fileName);
else if (dependencyPath.EndsWith(".fbx") || dependencyPath.EndsWith(".obj"))
targetPath = Path.Combine(baseDirectory, "Models", fileName);
else if (dependencyPath.EndsWith(".png") || dependencyPath.EndsWith(".jpg") || dependencyPath.EndsWith(".tga"))
targetPath = Path.Combine(baseDirectory, "Textures", fileName);
else if (dependencyPath.EndsWith(".anim") || dependencyPath.EndsWith(".controller"))
targetPath = Path.Combine(baseDirectory, "Animations", fileName);
else if (dependencyPath.EndsWith(".renderTexture"))
targetPath = Path.Combine(baseDirectory, "RenderTextures", fileName);
else if (exportScripts && dependencyPath.EndsWith(".cs") && IsCustomScript(dependencyPath))
targetPath = Path.Combine(baseDirectory, "Scripts", fileName);
if (!string.IsNullOrEmpty(targetPath))
{
AssetDatabase.CopyAsset(dependencyPath, targetPath);
Object oldAsset = AssetDatabase.LoadAssetAtPath<Object>(dependencyPath);
Object newAsset = AssetDatabase.LoadAssetAtPath<Object>(targetPath);
ReplaceReferences(newPrefab, oldAsset, newAsset);
if (oldAsset is Texture2D)
{
var sprites = AssetDatabase.LoadAllAssetsAtPath(dependencyPath).OfType<Sprite>().ToArray();
var newSprites = AssetDatabase.LoadAllAssetsAtPath(targetPath).OfType<Sprite>().ToArray();
for (int i = 0; i < sprites.Length; i++)
ReplaceReferences(newPrefab, sprites[i], newSprites[i]);
}
}
}
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
Debug.Log("Asset organization completed successfully!");
}
private void ReplaceReferences(GameObject prefab, Object oldAsset, Object newAsset)
{
var allObjects = prefab.GetComponentsInChildren<Transform>(true).Select(t => t.gameObject).ToList();
allObjects.Add(prefab);
foreach (var obj in allObjects)
{
var components = obj.GetComponents<Component>();
foreach (var component in components)
{
if (!component) continue;
SerializedObject serializedComponent = new SerializedObject(component);
SerializedProperty prop = serializedComponent.GetIterator();
while (prop.NextVisible(true))
{
if (prop.propertyType == SerializedPropertyType.ObjectReference && prop.objectReferenceValue == oldAsset)
{
prop.objectReferenceValue = newAsset;
serializedComponent.ApplyModifiedProperties();
}
}
}
}
EditorUtility.SetDirty(prefab);
}
private bool IsCustomScript(string path)
{
return !path.Contains("Packages/") && !path.Contains("UnityEngine") && !path.Contains("UnityEditor");
}
private void CreateFolders(string basePath, string[] folderNames)
{
if (!AssetDatabase.IsValidFolder(basePath))
{
AssetDatabase.CreateFolder(Path.GetDirectoryName(basePath), Path.GetFileName(basePath));
}
foreach (var folder in folderNames)
{
string fullFolderPath = Path.Combine(basePath, folder);
if (!AssetDatabase.IsValidFolder(fullFolderPath))
{
AssetDatabase.CreateFolder(basePath, folder);
}
}
}
}
}
In Conclusion
Prefab Asset Organizer won’t boost your FPS or revolutionize your gameplay. But it will help you keep your asset folders clean, tidy, and easy to maintain. And sometimes, a well-organized project is the first step toward a smooth and sane development experience.
Take a moment to clean up, you’ll thank yourself later.
Importer des packs d'assets dans Unity, c'est un peu comme aller faire les courses en ayant faim : tu repars avec bien plus que ce dont tu as besoin. Au final, tu n'utilises qu'un ou deux prefabs, et tu te retrouves avec 200 fichiers qui squattent ton projet, ralentissent les recherches, polluent ton repo Git, et t'empĂŞchent de dormir la nuit (si, si).
Pour éviter ce carnage, j'ai une approche : j'ai un projet Unity secondaire que j'utilise uniquement pour importer mes packs. Une sorte de salle blanche où je fais le tri. Je crée mes prefabs propres, bien configurés, puis je veux exporter uniquement les fichiers utiles vers mon vrai projet via un .unitypackage
. Et lĂ ... c'est souvent l'enfer pour rassembler tous les bons fichiers Ă la main.
C'est pour ça que j'ai codé ce petit outil : Prefab Asset Organizer.
Le concept
L’idée est simple : tu sélectionnes un prefab, tu appuies sur un bouton, et hop ! L’outil :
- crée un dossier à son nom,
- te claque dedans des sous-dossiers bien rangés (“Prefabs”, “Textures”, “Materials”, “Animations”, etc.),
- et y place toutes les dépendances du prefab, en les copiant méthodiquement dans les bons dossiers.
Si tu veux, tu peux même lui dire d’exporter les scripts liés. Il va te créer un dossier Scripts
avec amour, sans juger ton code.
Ce que fait réellement l'outil
Ce n’est pas juste un copier-coller bête. L’outil :
- identifie intelligemment les dépendances du prefab,
- copie chaque fichier dans son dossier approprié,
- remplace toutes les références dans le nouveau prefab (même les sprites planqués dans les Textures),
- et te sauve des heures de “oh non, c’est cassé quand je déplace le fichier !”.
C’est un peu comme si Marie Kondo s’était réincarnée en EditorWindow
.
Pourquoi cet outil ??
Ce besoin est né d’une frustration simple : naviguer dans un projet où les assets sont éparpillés rend le travail pénible et source d’erreurs. J’avais besoin d’un moyen clair et fiable de regrouper uniquement les fichiers nécessaires à mes prefabs, sans dépendre de processus manuels laborieux ou d’outils trop complexes. L’objectif était donc de concevoir un utilitaire épuré, centré sur l’essentiel, pour accélérer mon flux de travail tout en garantissant un projet propre.
The Code
using System.IO;
using System.Linq;
using UnityEditor;
using UnityEngine;
namespace Editor
{
public class PrefabAssetOrganizer : EditorWindow
{
private GameObject selectedPrefab;
private bool exportScripts = true;
[MenuItem("Tools/Prefab Asset Organizer")]
public static void ShowWindow()
{
GetWindow<PrefabAssetOrganizer>("Prefab Asset Organizer");
}
private void OnGUI()
{
GUILayout.Label("Select Prefab to organize", EditorStyles.boldLabel);
selectedPrefab = (GameObject)EditorGUILayout.ObjectField("Prefab", selectedPrefab, typeof(GameObject), false);
exportScripts = EditorGUILayout.Toggle("Export Scripts?", exportScripts);
if (GUILayout.Button("Organize Assets"))
{
if (selectedPrefab)
{
OrganizeAssets(selectedPrefab);
}
else
{
Debug.LogError("No prefab selected.");
}
}
}
private void OrganizeAssets(GameObject prefab)
{
string prefabPath = AssetDatabase.GetAssetPath(prefab);
string baseDirectory = Path.Combine(Path.GetDirectoryName(prefabPath), prefab.name);
var folders = exportScripts
? new string[] { "Prefabs", "Materials", "Models", "Textures", "Scripts", "Animations", "RenderTextures" }
: new string[] { "Prefabs", "Materials", "Models", "Textures", "Animations", "RenderTextures" };
CreateFolders(baseDirectory, folders);
string newPrefabPath = Path.Combine(baseDirectory, "Prefabs", prefab.name + ".prefab");
AssetDatabase.CopyAsset(prefabPath, newPrefabPath);
GameObject newPrefab = AssetDatabase.LoadAssetAtPath<GameObject>(newPrefabPath);
string[] dependencies = AssetDatabase.GetDependencies(prefabPath, true);
foreach (string dependencyPath in dependencies)
{
if (dependencyPath == prefabPath) continue;
string fileName = Path.GetFileName(dependencyPath);
string targetPath = "";
if (dependencyPath.EndsWith(".mat"))
targetPath = Path.Combine(baseDirectory, "Materials", fileName);
else if (dependencyPath.EndsWith(".fbx") || dependencyPath.EndsWith(".obj"))
targetPath = Path.Combine(baseDirectory, "Models", fileName);
else if (dependencyPath.EndsWith(".png") || dependencyPath.EndsWith(".jpg") || dependencyPath.EndsWith(".tga"))
targetPath = Path.Combine(baseDirectory, "Textures", fileName);
else if (dependencyPath.EndsWith(".anim") || dependencyPath.EndsWith(".controller"))
targetPath = Path.Combine(baseDirectory, "Animations", fileName);
else if (dependencyPath.EndsWith(".renderTexture"))
targetPath = Path.Combine(baseDirectory, "RenderTextures", fileName);
else if (exportScripts && dependencyPath.EndsWith(".cs") && IsCustomScript(dependencyPath))
targetPath = Path.Combine(baseDirectory, "Scripts", fileName);
if (!string.IsNullOrEmpty(targetPath))
{
AssetDatabase.CopyAsset(dependencyPath, targetPath);
Object oldAsset = AssetDatabase.LoadAssetAtPath<Object>(dependencyPath);
Object newAsset = AssetDatabase.LoadAssetAtPath<Object>(targetPath);
ReplaceReferences(newPrefab, oldAsset, newAsset);
if (oldAsset is Texture2D)
{
var sprites = AssetDatabase.LoadAllAssetsAtPath(dependencyPath).OfType<Sprite>().ToArray();
var newSprites = AssetDatabase.LoadAllAssetsAtPath(targetPath).OfType<Sprite>().ToArray();
for (int i = 0; i < sprites.Length; i++)
ReplaceReferences(newPrefab, sprites[i], newSprites[i]);
}
}
}
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
Debug.Log("Asset organization completed successfully!");
}
private void ReplaceReferences(GameObject prefab, Object oldAsset, Object newAsset)
{
var allObjects = prefab.GetComponentsInChildren<Transform>(true).Select(t => t.gameObject).ToList();
allObjects.Add(prefab);
foreach (var obj in allObjects)
{
var components = obj.GetComponents<Component>();
foreach (var component in components)
{
if (!component) continue;
SerializedObject serializedComponent = new SerializedObject(component);
SerializedProperty prop = serializedComponent.GetIterator();
while (prop.NextVisible(true))
{
if (prop.propertyType == SerializedPropertyType.ObjectReference && prop.objectReferenceValue == oldAsset)
{
prop.objectReferenceValue = newAsset;
serializedComponent.ApplyModifiedProperties();
}
}
}
}
EditorUtility.SetDirty(prefab);
}
private bool IsCustomScript(string path)
{
return !path.Contains("Packages/") && !path.Contains("UnityEngine") && !path.Contains("UnityEditor");
}
private void CreateFolders(string basePath, string[] folderNames)
{
if (!AssetDatabase.IsValidFolder(basePath))
{
AssetDatabase.CreateFolder(Path.GetDirectoryName(basePath), Path.GetFileName(basePath));
}
foreach (var folder in folderNames)
{
string fullFolderPath = Path.Combine(basePath, folder);
if (!AssetDatabase.IsValidFolder(fullFolderPath))
{
AssetDatabase.CreateFolder(basePath, folder);
}
}
}
}
}
En conclusion
Prefab Asset Organizer n'est peut-être pas un outil qui transforme votre gameplay ou optimise vos performances, mais il vous aide à structurer vos assets avec clarté et efficacité. Et parfois, un projet bien rangé, c’est le premier pas vers un développement serein et maîtrisé.
Prenez le temps de nettoyer, votre futur vous vous remerciera.