Wednesday, February 15, 2012

Automatically locate all unused unity assets

Did you ever work on a unity project where either you or a co-worker did not uphold complete and total order with regards to cleaning up the project folder. If you're one of those rare specimens that has never experienced a touch of kaos, don't read on!

On the other hand if you, as I, from time to time postpones the cleanup to another day, I've made a handy little editorwindow that should make the cleanup process a lot less tedious.

Basically it reads from the editor log after you have build a project, and then compares the included assets with all assets in your project. If an asset exist that is not used in the final build it is listed. It is also clickable so you can easily find it.

The unused assets are organized in 3 folders. "editor", "plugins" and "some other folder". This is helpful for me, as there is a lot in the "editor" folder that I use, but that is not used in final build.

It also lists which .dll's are included in build. If you need it, fine. If not, just delete that part.

DISCLAIMER: It does not locate unused assets in the "Ressources" folder, since everything in there should by default be included in the build. It's a tool to help locate the unused, but should be used with a bit of common sense.

[***UPDATE 2014***] By popular demand a GREATLY improved version of this tool has been released on the Unity Asset Store for 10 bucks. This old version is not stable as of Unity 4.6b20 and will not be updated

Place the following in the "editor" folder:
 using UnityEngine;  
 using UnityEditor;  
 using System.Collections;  
 using System.Collections.Generic;  
   
 public class CleanUpWindow : EditorWindow  
 {  
   bool groupEnabled = false;  
   List<string> usedAssets = new List<string>();  
   List<string> includedDependencies = new List<string>();  
   private Vector2 scrollPos;  
   private List<Object> unUsed;  
   private Dictionary<string, List<Object>> unUsedArranged;  
   private bool needToBuild = false;  

// Add menu named "CleanUpWindow" to the Window menu  
   [MenuItem("Window/CleanUpWindow")]  
   static void Init()  
   {  
     // Get existing open window or if none, make a new one:  
     CleanUpWindow window = (CleanUpWindow)EditorWindow.GetWindow(typeof(CleanUpWindow));
window.Show();  
   }  
   
   void OnGUI()  
   {  
     if (needToBuild)  
     {  
       GUI.color = Color.red;  
       GUILayout.Label("Are you sure you remembered to build project? Because you really need to...", EditorStyles.boldLabel);  
     }  
     GUI.color = Color.white;  
   
     if (GUILayout.Button("Load EditorLog"))  
     {  
       loadEditorLog();  
     }  
   
       
     if(!needToBuild)  
     {  
       EditorGUILayout.BeginHorizontal();  
       EditorGUILayout.BeginVertical();  
       if (groupEnabled)  
       {  
         GUILayout.Label("DEPENDENCIES");  
         for (int i = 0; i < includedDependencies.Count; i++)  
         {  
           EditorGUILayout.LabelField(i.ToString(), includedDependencies[i]);  
         }  
       }  
       EditorGUILayout.EndVertical();  
       scrollPos = EditorGUILayout.BeginScrollView(scrollPos);  
       EditorGUILayout.BeginVertical();  
   
       if (groupEnabled)  
       {  
         if (unUsedArranged != null)  
         {  
           foreach (KeyValuePair<string, List<Object>> objList in unUsedArranged)  
           {  
             if (objList.Value.Count >= 1)  
             {  
               GUILayout.Label(objList.Key.ToUpper());  
               for (int i = 0; i < objList.Value.Count; i++)  
               {  
                 EditorGUILayout.ObjectField(objList.Value[i], typeof(Object), false);  
               }  
             }  
           }  
         }  
       }  
       EditorGUILayout.EndVertical();  
       EditorGUILayout.EndScrollView();  
       EditorGUILayout.EndHorizontal();  
     }  
       
   }  
   
   private void loadEditorLog()  
   {  
     UsedAssets.GetLists(ref usedAssets, ref includedDependencies);  
   
     if (usedAssets.Count == 0)  
     {  
       needToBuild = true;  
     }  
     else  
     {  
       compareAssetList(UsedAssets.GetAllAssets());  
       groupEnabled = true;  
       needToBuild = false;  
     }  
   }  
   
   private void compareAssetList(string[] assetList)  
   {  
       
     unUsed = new List<Object>();  
   
     unUsedArranged = new Dictionary<string, List<Object>>();  
     unUsedArranged.Add("plugins", new List<Object>());  
     unUsedArranged.Add("editor", new List<Object>());  
     unUsedArranged.Add("some other folder", new List<Object>());  
   
     for (int i = 0; i < assetList.Length; i++)  
     {   
       if(!usedAssets.Contains(assetList[i]))  
       {  
           
         Object objToFind = AssetDatabase.LoadAssetAtPath(assetList[i], typeof(Object));  
         unUsed.Add(objToFind);  
         if (objToFind != null)
         {
             unUsedArranged[getArrangedPos(objToFind)].Add(objToFind);  
         }
       }  
     }  
   }  
   
   private string getArrangedPos(Object value)  
   {  
     string path = AssetDatabase.GetAssetPath(value).ToLower();  
   
     if (path.Contains("/plugins/"))  
     {  
       return "plugins";  
     }  
     else if (path.Contains("/editor/"))  
     {  
       return "editor";  
     }  
     else  
     {  
       return "some other folder";  
     }  
   }  
 }  
You,ll need this too:
using UnityEngine;
using UnityEditor;
using System;
using System.IO;
using System.Collections;
using System.Collections.Generic;

public class UsedAssets
{
    public static string[] GetAllAssets()
    {
        string[] tmpAssets1 = Directory.GetFiles(Application.dataPath, "*.*", SearchOption.AllDirectories);
        string[] tmpAssets2 = Array.FindAll(tmpAssets1, name => !name.EndsWith(".meta"));
        string[] allAssets;

        allAssets = Array.FindAll(tmpAssets2, name => !name.EndsWith(".unity"));

        for (int i = 0; i < allAssets.Length; i++)
        {
            allAssets[i] = allAssets[i].Substring(allAssets[i].IndexOf("/Assets") + 1);
            allAssets[i] = allAssets[i].Replace(@"\", "/");
        }

        return allAssets;
    }

    public static void GetLists(ref List<string> assetResult, ref List<string> dependencyResult)
    {
        assetResult.Clear();
        dependencyResult.Clear();

        string LocalAppData = string.Empty;
        string UnityEditorLogfile = string.Empty;

        if (Application.platform == RuntimePlatform.WindowsEditor)
        {
            LocalAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
            UnityEditorLogfile = LocalAppData + "\\Unity\\Editor\\Editor.log";
        }
        else if (Application.platform == RuntimePlatform.OSXEditor)
        {
            LocalAppData = Environment.GetFolderPath(Environment.SpecialFolder.Personal);
            UnityEditorLogfile = LocalAppData + "/Library/Logs/Unity/Editor.log";
        }
        
        try
        {
            // Have to use FileStream to get around sharing violations!
            FileStream FS = new FileStream(UnityEditorLogfile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
            StreamReader SR = new StreamReader(FS);

            
            string line;
            while (!SR.EndOfStream && !(line = SR.ReadLine()).Contains("Mono dependencies included in the build"));
            while (!SR.EndOfStream && (line = SR.ReadLine()) != "")
            {
                dependencyResult.Add(line);
            }
            while (!SR.EndOfStream && !(line = SR.ReadLine()).Contains("Used Assets,"));
            while (!SR.EndOfStream && (line = SR.ReadLine()) != "")
            {
               
                line = line.Substring(line.IndexOf("% ") + 2);
                assetResult.Add(line);
            }
        }
        catch (Exception E)
        {
            Debug.LogError("Error: " + E);
        }
    }
}
Note that script files might not be shown as unused since the unity build includes then.
Here's an example: Unity Package

28 comments:

  1. Thank you, this is certainly a great script to use. I don't have a co-worker, but I forget a lot of things I've put in there and now they are not used at all. I did some manual clean-ups but it is a long and boring task, so thank you for your automation script :)

    ReplyDelete
  2. No problem, glad it's of help to anyone

    ReplyDelete
  3. Awesome!! Very useful for me. When I experiment my projects are a mess.

    For Mac make sure to replace lines 29 and 30 of the 2nd script with:

    string LocalAppData = Environment.GetFolderPath (Environment.SpecialFolder.Personal);
    string UnityEditorLogfile = LocalAppData + "/Library/Logs/Unity/Editor.log";

    Also remember to call the 1st script "CleanUpWindow" and the 2nd script "UsedAssets'

    ReplyDelete
    Replies
    1. Thats great stuff ozRocker, thanks. Did this on a PC and never thought about it could be different on a mac :) I'll modify the script, when i get the time, to check if its a pc or a mac and get correct path accordingly

      Delete
    2. I've modified the script so it tests which runtime environment it is in. I'll reupload the unitypackage in a few minutes.
      Keep the corrections or ideas comming, so this script can become better. Dont be shy, participate :)

      Delete
  4. boah.... how long did i wait for this ???? 7 Years now ... duhhh Unity Dev's are slow if it comes to User Wishes that are standard in other professional App's....
    I'm curious WHEN Unity will build this in...

    Many Many Thxxxx !!!!!
    This is still what i like on Unity Community... that there help & share... normally this should do the Company !!!

    awesome thank you !!!!

    ReplyDelete
    Replies
    1. Hi MadMac
      Glad you like it :) It's still a bit limited because it relies on the log output from the unity build process. But its still pretty useful. If you improve the scripts in any way plz let me know

      Delete
  5. Wow, this is great! I get these two warnings however. I'm just wondering if I've placed something in the wrong spot or haven't done something right.

    Assets/Editor/CustomWindows/CleanUpWindow.cs(66,49): warning CS0618: `UnityEditor.EditorGUILayout.ObjectField(UnityEngine.Object, System.Type, params UnityEngine.GUILayoutOption[])' is obsolete: `Check the docs for the usage of the new parameter 'allowSceneObjects'.'

    Assets/Editor/CustomWindows/CleanUpWindow.cs(21,23): warning CS0219: The variable `window' is assigned but its value is never used

    Thanks!

    ReplyDelete
    Replies
    1. Hi Nick
      Thx for informing me :)

      The first issue is that there has been released new version of unity since I wrote this script which means some methods have been deprecated.

      If you use this:
      EditorGUILayout.ObjectField(objList.Value[i], typeof(Object), false);

      instead of the old one, there should be no warning.

      The other issue, is just me forgetting to add a line of code. It has no real implication, but if you add this:
      window.Show() in the "Init()" method then Unity automatically shows the relevant window.

      Delete
  6. Hi - I'm confused on how to use the package - I've imported it and built my project, but nothing seems to get organized.

    ReplyDelete
    Replies
    1. Hi Mike. Do you have the Pro version of Unity. I believe that is a requirement.

      Delete
  7. Just tried this today with our incredibly large project, and got a memory allocation error. Any ideas as to what I can do to get around this? We're currently using version 3.5.6f4, and I would REALLY like to clean out our unused assets. Any more help would be greatly appreciated, thank you! Let me know if the error log would be of use and I'll post it.

    ReplyDelete
    Replies
    1. Hi Tim

      It's a bit hard saying anything profound without any more information. Perhaps take a look here: http://answers.unity3d.com/questions/13779/unity-out-of-memory.html

      Or perhaps you should just try to debug using breakpoints to figure out where it goes wrong

      Delete
  8. Hey Krisitian, awesome stuff here. I extended your interface a bit to include more organizational categories, as well as delete methods.

    Pic of extended editor in action:
    http://i.imgur.com/X3GSO.png

    gist with code:
    https://gist.github.com/4076464

    ReplyDelete
    Replies
    1. Hey. Thats great! Looking forward to taking a look at it. Thanks for sharing

      Delete
    2. Oh wow! EVERYONE WHO WANTS TO USE THIS: Use this advanced/extended version of the interface, 'cause it'll save you lots of time. (Includes a delete button too! )

      Delete
  9. Very useful scripts!
    Thank you very much.

    ReplyDelete
  10. Thanks for this script, it looks very helpful. However it doesnt do anything for me. When I follow the instructions and make a build I do not get any window popup. I checked the console and nothing seems to be logged there either. I have unity pro and I am building for IOS. Is there a step I could be missing? I am on a mac and using unity 3.5.6 if that is relevant.

    Thanks again for publishing this.

    ReplyDelete
    Replies
    1. Hi meandering

      Perhaps try to look in the menu "Window/CleanUpWindow".
      Its located there. If its not, then you must have an compile error somewhere.

      Delete
    2. Also make sure the script is in the "editor" folder

      Delete
  11. Sell this as a Tool on Unity Asset Store. Make some money :D

    ReplyDelete
  12. Thank you!
    I will give it a try.

    ReplyDelete
  13. Very, VERY nice. It's going to be a pain in my ass to delete hundreds of items, but this definitely helped. I concur with the asset store idea. You could make at least a little money off this.

    ReplyDelete
  14. this work per scene or per project?

    ReplyDelete
    Replies
    1. Hi Klarax. It looks at the build log, so its based on the included scenes in the build. This is something I wrote in the beginning of 2012, but actually just published a much better revisited version on the Unity Asset Store. It's currently awaiting approval. This version will stay online for free, but the 5$ improved version is vastly superior.

      Delete
    2. You'll be able to find it here https://www.assetstore.unity3d.com/en/#!/publisher/5473 when released

      Delete