Search Unity

Contribution Editor Window To Efficiently/Productively find Scriptable Objects (SOs)

Discussion in 'Open Projects' started by Harsh-NJ, Dec 30, 2020.

  1. Harsh-NJ

    Harsh-NJ

    Joined:
    May 1, 2020
    Posts:
    315
    Hey there all (and @cirocontinisio) ,
    Now we have a big project and a big Assets Folder, and this is going to be bigger by the gem like contributions of all the people of community.

    We have all Scriptable Objects (SOs) in the ScriptableObjects folder (arranged folderwise also inside). But new people who will come here may find to difficult to locate a specific SO, or its Type.
    We have event system and many other other important systems based on SOs. If someone wants to find a specific SO to modify it, it would be time consuming and irritating to navigate through a large collection of SOs to find it (and it will increase with time).

    So, I found a solution to tackle this problem. (Thread title says all, but)
    I created an Editor window which finds all SOs in the Assets/Scriptable Objects folder and lists its types using a Popup. If user selects a type, window will list all SOs in project with that (selected) type, its name, and a locate button.
    If locate button is pressed, the project window is focused, (in case any other window is focused on top of project window) and the SOs is pinged in yellow (as Unity does).

    This way, we can locate all SOs of a specific type very quickly, and work on it.
    Thanks,
    Harsh
     
  2. Harsh-NJ

    Harsh-NJ

    Joined:
    May 1, 2020
    Posts:
    315
  3. Peter77

    Peter77

    QA Jesus

    Joined:
    Jun 12, 2013
    Posts:
    6,618
    This looks like an useful addition! :)

    Here is some feedback for the PR... There are multiple
    AssetDatabase.FindAssets
    and
    AssetDatabase.LoadAsset
    calls that seem to get triggered over and over again during
    OnGUI
    . The tool would be more efficient when it builds the
    objectGUIDs
    ,
    objects
    ,
    SOTypes
    , etc data not every "frame", but only when necessary. Otherwise when the project grows and ends up with more SOs, I'm afraid this can cause the editor to get slow.
     
  4. Harsh-NJ

    Harsh-NJ

    Joined:
    May 1, 2020
    Posts:
    315
    Thanks for feedback,
    one thing I could do is add in an another method which does all thing you said when window is opened and when a refresh all button is pressed.

    Also, one question, is
    GUILayout.Label()
    call is also necessary each frame?
     
  5. Peter77

    Peter77

    QA Jesus

    Joined:
    Jun 12, 2013
    Posts:
    6,618
    This sounds like a good change, thank you. Maybe the OnFocus event is interesting for this? So every time the window gets focused, it updates its content once.

    Yes, the IMGUI methods need to be called each frame.
     
  6. Harsh-NJ

    Harsh-NJ

    Joined:
    May 1, 2020
    Posts:
    315
    It's done.

    And Happy New Year
     
    Peter77 likes this.
  7. Peter77

    Peter77

    QA Jesus

    Joined:
    Jun 12, 2013
    Posts:
    6,618
    Thank you, happy new year to you too!

    Cool, thanks for the update! I've pulled the PR, when I open
    Tools > Quick SO Access Tool
    , Unity outputs the following error every frame until I press the "Refresh All" button:
    Code (CSharp):
    1. NullReferenceException: Object reference not set to an instance of an object
    2. SOsQuickAccessToolWindow.DrawSOsList () (at Assets/Scripts/Editor/SOsQuickAccessToolWindow.cs:76)
    3. SOsQuickAccessToolWindow.OnGUI () (at Assets/Scripts/Editor/SOsQuickAccessToolWindow.cs:59)
    The reason for this is because
    displayObjectsGUIDs
    is
    null
    and only gets set when I press "Refresh All".

    Here is how you can reproduce it:
    • Close Quick SO Access Tool window
    • Reimport SOsQuickAccessToolWindow.cs
    • Click Tools > Quick SO Access Tool


    Here are a few ideas from an UI point of view...

    I think you can compact the UI quite a bit when you use EditorGUILayout.ObjectField in
    DrawSOsList
    to display each item, rather than Label+Button+Space. You also get the "Ping" for free that way.

    I don't think the help text "Please select Scriptable Object Type to search for" is necessary and would get back some precious UI space as well.

    If you remove the label "Scriptable Object Types" and draw the "Popup" and "Refresh All" button on the same line, like a toolbar, would further compact the UI.

    If the UI is very light, it allows the window to be docked somewhere all the time, without feeling like it steals some work-area.
     
  8. Peter77

    Peter77

    QA Jesus

    Joined:
    Jun 12, 2013
    Posts:
    6,618
    Another thought would be to only have the Popup button and when you select a type, the tool inserts a search text in Unity's Project window search field. You can use ProjectBrowser.SetSearch (requires Reflection) to insert a search text like
    t:MyScriptableObjectType
    .

    Then you can get rid of the custom item list and re-use the Unity project window for that. What do you think?
     
  9. Harsh-NJ

    Harsh-NJ

    Joined:
    May 1, 2020
    Posts:
    315
    This shall be fixed by null check.
    This is because at start, nothing is selected in the popup, so displayObjectsGUIDs is not populated
     
  10. Harsh-NJ

    Harsh-NJ

    Joined:
    May 1, 2020
    Posts:
    315
    Yes , I tried the approach earlier, but there is also a line that is focusing the project window.
    The ObjectField will not Focus the project window if any other window (like Console) is focused.

    Second Major problem I found when testing ObjectField was, it can be changed.
    (it's reference)
     
  11. Harsh-NJ

    Harsh-NJ

    Joined:
    May 1, 2020
    Posts:
    315
    Yes, beginHorizontalLayout

    I will try it
     
  12. Harsh-NJ

    Harsh-NJ

    Joined:
    May 1, 2020
    Posts:
    315
    It was the first thought when I started making this.
    But I cannot find a thing like ProjectBrowser.SetSearch (I am seeing this method first time)

    But I have created this list approach and I dont think I should go back
     
  13. Harsh-NJ

    Harsh-NJ

    Joined:
    May 1, 2020
    Posts:
    315
    Thanks for your valuable suggestions, keep providing.
     
  14. Harsh-NJ

    Harsh-NJ

    Joined:
    May 1, 2020
    Posts:
    315
    Hey, I am not able to update the PR right now, but will do it tomorrow. Here's the screenshot.

    dock easily.png
     
  15. Harsh-NJ

    Harsh-NJ

    Joined:
    May 1, 2020
    Posts:
    315
    Also, I tried ProjectBrowser, but it says that ProjectBrowser is inaccessible due to its protection level.
     
  16. Harsh-NJ

    Harsh-NJ

    Joined:
    May 1, 2020
    Posts:
    315
    Done. PR updated.
     
  17. Harsh-NJ

    Harsh-NJ

    Joined:
    May 1, 2020
    Posts:
    315
    Hey @cirocontinisio , come here and sprinkle your suggestions (valuable)
     
  18. Harsh-NJ

    Harsh-NJ

    Joined:
    May 1, 2020
    Posts:
    315
    @Peter77, have you checked the latest PR (UI layout, and OnGUI() calls?)
     
  19. Harsh-NJ

    Harsh-NJ

    Joined:
    May 1, 2020
    Posts:
    315
  20. Harsh-NJ

    Harsh-NJ

    Joined:
    May 1, 2020
    Posts:
    315
    Thank You so much @Amel-Unity, finally it merged. Now for any improvements, it can be discussed here.
     
    Amel-Unity likes this.
  21. IsaiahKelly

    IsaiahKelly

    Joined:
    Nov 11, 2012
    Posts:
    418
    Great work @Harsh-099!

    As my first attempt to contribute to this project I decided to try and improve upon your script, since I have experience with editor scripting and saw lots of room for improvement. At first I was just going to improve the layout but things quickly escalated :) I also thought the name was a little too wordy, so I renamed it to "Scriptable Object Browser" instead, since that is exactly what this is.

    First off, I merged the locate button and file name together by making the button text the name of the file. I also changed the button style. A popup menu for all those types is also really difficult to navigate, so I converted it to another scroll view.

    sob_main.png

    So now clicking on anything in the type list opens up an asset file sub-menu list.

    sob_assets.png

    I also moved the menu location to "ChopChop/Scriptable Object Browser" since I think this makes it easier to discover, but we may want to just move everything to "Tools/..." or even "Window/Utilities/.." or something.

    sob_new_menu.png

    Let me know what you think.

    Edit: Pull request #387 Hmm, your PR for this very feature was #287! o_O what a strange coincidence.
     
    Last edited: Mar 6, 2021
    Amel-Unity, Smurjo and cirocontinisio like this.
  22. Harsh-NJ

    Harsh-NJ

    Joined:
    May 1, 2020
    Posts:
    315
    Very Great @IsaiahKelly, these are very nice improvements, looking at your code will help in improving my editor window skills.
    Yes, strange coincidence. This improvement is 100x better than mine original.
    Thanks.
     
  23. cirocontinisio

    cirocontinisio

    Joined:
    Jun 20, 2016
    Posts:
    884
    Great improvement, @IsaiahKelly!!
    Only one suggestion I'd make: I think by aligning the text to the left, it will be more readable and it will create a natural grouping, for instance:

    Music Beach
    Music Forest
    Music Glade
    Music Main Theme
    Music Town
    SFX Footsteps Grass
    SFX Footsteps Cobblestone
    SFX Footsteps Stone
    SFX Footsteps Sand
    SFX Jump

    You can see how at a glance you can see where music ends, where SFX starts, etc.
     
    Amel-Unity likes this.
  24. IsaiahKelly

    IsaiahKelly

    Joined:
    Nov 11, 2012
    Posts:
    418
    Ah thanks. That is very nice of you to say. Your script was a great starting point and the underlying structure is actually much the same. I just tried to optimize it as much as possible while improving the layout and look, which was surprisingly simple to do. I also probably spent haft the time just figuring out how to show type names without their namespaces. :rolleyes:

    Agreed. That is actually something I wanted to add but it would probably require some custom GUI Styling and I did not want to make this code too complex. But I can go ahead and add this if you think it's worth it.

    One other question I had was if I should remove underlines from file names when displaying them. At the moment I remove both dashes and underlines from names, but the latter is often used to show relationships or modifications, so I am not sure if it would be more helpful to keep that when displaying?

    E.g. "Play Sound_Swing Cane" instead of "Play Sound Swing Cane" as it is now.
     
    Last edited: Mar 7, 2021
  25. IsaiahKelly

    IsaiahKelly

    Joined:
    Nov 11, 2012
    Posts:
    418
    I also see many other possible improvements to this tool. For example, I think using a tree view to create a custom hierarchy window would be ideal. Then you could group ScriptableObjects by inheritance, namespaces and even naming conventions. But this of course might make the tool a little too heavy for this project. So I am trying to restrain my ambitions a little. :)
     
    Last edited: Mar 6, 2021
  26. IsaiahKelly

    IsaiahKelly

    Joined:
    Nov 11, 2012
    Posts:
    418
    Okay so I think the simplest way to change this is to use EditorStyles.foldout and EditorStyles.linkLabel instead of EditorStyles.toolbarButton.

    sob_main_left.png

    Using the foldout style helps indicate these are top selections I think.

    sob_assets_left.png
    I went with linkLabel instead of just Label style for the assets because I think it better indicates these are clickable.

    Thoughts?
     
  27. Harsh-NJ

    Harsh-NJ

    Joined:
    May 1, 2020
    Posts:
    315
    Yes, I agree, it's look like they are clickable (blue hyperlinks).
     
  28. IsaiahKelly

    IsaiahKelly

    Joined:
    Nov 11, 2012
    Posts:
    418
    Yeah, making sure they appear clickable is why I went with the EditorStyles.toolbarButton before, because it would highlight them when you hovered over with the mouse, just like the hierarchy. But making them look like links will work for now, and I do think the left alignment is much better.

    I think it might also be helpful to add "create" and "delete" buttons to maybe help speed up workflow. I am just not sure how best to implement this yet. Easiest way would be to just add a "create new" button at the top of any particular sub-menu and small delete buttons next to each entry. But you could also add a context menu with other additional options...
     
  29. Harsh-NJ

    Harsh-NJ

    Joined:
    May 1, 2020
    Posts:
    315
    Now I think this is out of the box for this particular tool. This tool is intended to find SO only, at most select them to show in inspector, but creating and deleting should not be the task of this tool. We can surely delete them in project window, and make new one also there. This implementation is more than required. (From my point of view, sorry if i'm not right.)
     
  30. SpookyCat

    SpookyCat

    Joined:
    Jan 25, 2010
    Posts:
    3,765
    Very handy little script, I changed it a bit so the Type List was sorted making it easier to locate the type you want. Don't have Github setup so I'll just paste the change here.
    Code (CSharp):
    1.     public struct TypeName
    2.     {
    3.         public string typeName;
    4.         public string displayName;
    5.     }
    6.  
    7.     bool HaveType(List<TypeName> names, string tname)
    8.     {
    9.         for ( int i = 0; i < names.Count; i++ )
    10.         {
    11.             if ( names[i].typeName == tname )
    12.                 return true;
    13.         }
    14.  
    15.         return false;
    16.     }
    17.  
    18.     static int SortTypes(TypeName t1, TypeName t2)
    19.     {
    20.         return System.String.Compare(t1.displayName, t2.displayName);
    21.     }
    22.  
    23.     private void GetTypes()
    24.     {
    25.         string[] GUIDs = AssetDatabase.FindAssets("t:ScriptableObject", new string[] { "Assets" });
    26.         ScriptableObject[] SOs = new ScriptableObject[GUIDs.Length];
    27.  
    28.         for ( int i = 0; i < GUIDs.Length; i++ )
    29.         {
    30.             string path = AssetDatabase.GUIDToAssetPath(GUIDs[i]);
    31.             SOs[i] = (ScriptableObject)AssetDatabase.LoadAssetAtPath(path, typeof(ScriptableObject));
    32.         }
    33.  
    34.         List<TypeName> names = new List<TypeName>();
    35.  
    36.         for ( int i = 0; i < SOs.Length; i++ )
    37.         {
    38.             if ( !HaveType(names, SOs[i].GetType().FullName) )
    39.             {
    40.                 TypeName tn = new TypeName();
    41.                 tn.typeName = SOs[i].GetType().FullName;
    42.                 tn.displayName = GetNiceName(SOs[i].GetType().Name);
    43.                 names.Add(tn);
    44.             }
    45.         }
    46.  
    47.         names.Sort(SortTypes);
    48.  
    49.         _typeNames.Clear();
    50.         _typeDisplayNames.Clear();
    51.  
    52.         for ( int i = 0; i < names.Count; i++ )
    53.         {
    54.             // Full type name, including namespaces.
    55.             _typeNames.Add(names[i].typeName);
    56.             // Just the type name alone for display purposes.
    57.             _typeDisplayNames.Add(names[i].displayName);
    58.         }
    59.     }
    60.  
     
    hamza_unity995 and IsaiahKelly like this.
  31. IsaiahKelly

    IsaiahKelly

    Joined:
    Nov 11, 2012
    Posts:
    418
    @Harsh-099 You may be right.

    I changed the button action to select the asset directly for immediate editing in the inspector, instead of just pinging it in the project window. This means you do not need the project window open or in focus at all to edit them. Making this tool a much faster replacement for editing ScriptableObjects. And this is why I was thinking "create" and "delete" buttons would be useful, because that is the only functionality the project window still has over this tool and it would be easy to add, but this might be overkill. Just some ideas though.

    I also want to add back the ping functionality somehow. I removed it completely because it changes window focus, and you do not always want that, but it is still very helpful for locating the actual file.
     
  32. IsaiahKelly

    IsaiahKelly

    Joined:
    Nov 11, 2012
    Posts:
    418
    @SpookyCat Hey thanks. I'll try to update the PR with your changes.

    I had not even thought about the fact that types are unordered. I was also going to search the root assets folder to ensure I found everything, but that adds non project specific types, which is kind of outside the scope. I think Ideally we should have a custom ScriptableObject base class that all project ScriptableObjects derive from, so we can filter by that.
     
  33. IsaiahKelly

    IsaiahKelly

    Joined:
    Nov 11, 2012
    Posts:
    418
    Okay, so I ended up just converting the two type name lists into a sorted dictionary to keep things simple, since this meant I did not need to add any custom sort or contains methods.

    sob_alphabetize.png

    Great improvement. Thanks @SpookyCat!
     
    Sarai likes this.
  34. IsaiahKelly

    IsaiahKelly

    Joined:
    Nov 11, 2012
    Posts:
    418
    I have noticed an issue with the way assets are sorted; they are being sorted by file path instead of name. So they are not always displayed in alphabetical order when placed in different folders. For example:

    sob_sorting_issue.png

    For Void Events you can see words starting with "C" are not at the top or together!

    I was going to fix this by just using the file name instead of the path as the dictionary key, but each key has to be unique, so this will throw an error when multiple files of the same type have the same name, such as with state names. So I am not sure how to (easily) solve this yet. But it looks like I may need to use a custom sorting method after all.
     
  35. SpookyCat

    SpookyCat

    Joined:
    Jan 25, 2010
    Posts:
    3,765
    I did a small rewrite for my own purposes, which might be useful, added an option to show full type name as that was useful in my project to sort by namespace as well, and I keep it all in one window which again I find a bit more useful for my personal use.
    Code (CSharp):
    1. using System.Collections.Generic;
    2. using UnityEditor;
    3. using UnityEngine;
    4.  
    5. class SOWindow : EditorWindow
    6. {
    7.     public class AssetType
    8.     {
    9.         public string                    typeName    = "";
    10.         public string                    displayName    = "";
    11.         public bool                        showObjs    = false;
    12.         public List<ScriptableObject>    assets        = new List<ScriptableObject>();
    13.     }
    14.  
    15.     Vector2            _typeScrollViewPosition;
    16.     List<AssetType>    assetTypes = new List<AssetType>();
    17.     public    bool    showFullName    = true;
    18.  
    19.     void OnEnable()
    20.     {
    21.         GetTypes();
    22.     }
    23.  
    24.     void OnFocus()
    25.     {
    26.         GetTypes();
    27.     }
    28.  
    29.     [MenuItem("Tools/Scriptable Object Browser")]
    30.     static void ShowWindow()
    31.     {
    32.         GetWindow<SOWindow>("Scriptable Objects");
    33.     }
    34.  
    35.     void OnGUI()
    36.     {
    37.         EditorGUILayout.BeginHorizontal();
    38.         EditorGUILayout.LabelField("Scriptable Object Types", EditorStyles.largeLabel);
    39.         showFullName = EditorGUILayout.Toggle("Full Name", showFullName);
    40.         if ( GUI.changed )
    41.             GetTypes();
    42.  
    43.         EditorGUILayout.EndHorizontal();
    44.  
    45.         if ( GUILayout.Button("Refresh List") )
    46.             GetTypes();
    47.  
    48.         _typeScrollViewPosition = GUILayout.BeginScrollView(_typeScrollViewPosition);
    49.  
    50.         for ( int i = 0; i < assetTypes.Count; i++ )
    51.         {
    52.             assetTypes[i].showObjs = EditorGUILayout.BeginFoldoutHeaderGroup(assetTypes[i].showObjs, assetTypes[i].displayName);
    53.  
    54.             if ( assetTypes[i].showObjs )
    55.             {
    56.                 for ( int j = 0; j < assetTypes[i].assets.Count; j++ )
    57.                 {
    58.                     if ( GUILayout.Button("\t" + GetNiceName(assetTypes[i].assets[j].name), EditorStyles.linkLabel) )
    59.                         Selection.activeObject = assetTypes[i].assets[j];
    60.                 }
    61.             }
    62.  
    63.             EditorGUILayout.EndFoldoutHeaderGroup();
    64.         }
    65.  
    66.         GUILayout.EndScrollView();
    67.     }
    68.  
    69.     AssetType HaveType(List<AssetType> atypes, string tname)
    70.     {
    71.         for ( int i = 0; i < atypes.Count; i++ )
    72.         {
    73.             if ( atypes[i].typeName == tname )
    74.                 return atypes[i];
    75.         }
    76.  
    77.         return null;
    78.     }
    79.  
    80.     static int SortTypes(AssetType t1, AssetType t2)
    81.     {
    82.         return System.String.Compare(t1.displayName, t2.displayName);
    83.     }
    84.  
    85.     static int SortAssets(ScriptableObject t1, ScriptableObject t2)
    86.     {
    87.         return System.String.Compare(t1.name, t2.name);
    88.     }
    89.  
    90.     void GetTypes()
    91.     {
    92.         string[] GUIDs = AssetDatabase.FindAssets("t:ScriptableObject", new string[] { "Assets" });
    93.         ScriptableObject[] SOs = new ScriptableObject[GUIDs.Length];
    94.  
    95.         for ( int i = 0; i < GUIDs.Length; i++ )
    96.             SOs[i] = (ScriptableObject)AssetDatabase.LoadAssetAtPath(AssetDatabase.GUIDToAssetPath(GUIDs[i]), typeof(ScriptableObject));
    97.  
    98.         assetTypes.Clear();
    99.  
    100.         for ( int i = 0; i < SOs.Length; i++ )
    101.         {
    102.             AssetType atype = HaveType(assetTypes, SOs[i].GetType().FullName);
    103.  
    104.             if ( atype == null )
    105.             {
    106.                 atype = new AssetType();
    107.                 atype.typeName = SOs[i].GetType().FullName;
    108.                 if ( showFullName )
    109.                     atype.displayName = GetNiceName(SOs[i].GetType().FullName);
    110.                 else
    111.                     atype.displayName = GetNiceName(SOs[i].GetType().Name);
    112.  
    113.                 assetTypes.Add(atype);
    114.             }
    115.  
    116.             if ( !atype.assets.Contains(SOs[i]) )
    117.                 atype.assets.Add(SOs[i]);
    118.         }
    119.  
    120.         assetTypes.Sort(SortTypes);
    121.  
    122.         for ( int i = 0; i < assetTypes.Count; i++ )
    123.             assetTypes[i].assets.Sort(SortAssets);
    124.     }
    125.  
    126.     string GetNiceName(string text)
    127.     {
    128.         string niceText = ObjectNames.NicifyVariableName(text);
    129.         niceText = niceText.Replace(" SO", "");
    130.         niceText = niceText.Replace("-", " ");
    131.         niceText = niceText.Replace("_", " ");
    132.         return niceText;
    133.     }
    134. }
     
    hamza_unity995 likes this.
  36. Harsh-NJ

    Harsh-NJ

    Joined:
    May 1, 2020
    Posts:
    315
    It's good that you are using our script for your use @SpookyCat, and even modified it for your adaptation. Can you please explain what and why you modified in it?
     
  37. SpookyCat

    SpookyCat

    Joined:
    Jan 25, 2010
    Posts:
    3,765
    I changed it so all the SO's were in one hierarchy view where you can expand the sections you want instead going back and forth between modes, also sorted the SO's by name, and added an option to show the full namespace for the SO type as in my project there were cases of SO's having the same class name but in different namespaces.
     
  38. cirocontinisio

    cirocontinisio

    Joined:
    Jun 20, 2016
    Posts:
    884
    Harsh-NJ and Amel-Unity like this.
  39. Harsh-NJ

    Harsh-NJ

    Joined:
    May 1, 2020
    Posts:
    315
    Thanks Ciro. But one kind request, my official (real) name is by the way, as on github, "Harsh Narayan Jha".
    Here on forums, Harsh-099 is just a username.
    Just a kind request so that if I share this with my friends and relatives, they can know that it is me...

    But anyway, Thanks
     
    cirocontinisio likes this.
  40. cirocontinisio

    cirocontinisio

    Joined:
    Jun 20, 2016
    Posts:
    884
    For sure. We usually start with the username by default to protect the privacy of people, some might not like to be mentioned online. But I'll keep it in mind :)
    And I'll use it for the credits. In fact it was already on our contributors spreadsheet :)
     
    Harsh-NJ likes this.