Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Question how can I find all GameObjects that directly reference a ScriptableObject

Discussion in 'Scripting' started by sass00n1, Mar 4, 2023.

  1. sass00n1

    sass00n1

    Joined:
    Oct 31, 2018
    Posts:
    6
    I want to ask, how can I find all GameObjects that directly reference a ScriptableObject. Note that it is a direct reference. I found the method of right-clicking the ScriptableObject and then clicking find Refrence in scene, but I found that this method found all the game objects that indirectly referenced SO, and it did not solve my problem.

    I also discovered the new search function launched by Unity, which is very powerful, but I studied it for a while and found that there is no way.

    Is there any good way? Or am I missing something? Finally I know it's possible to iterate through the asset database's API, but it seems to be very slow.
     
  2. SisusCo

    SisusCo

    Joined:
    Jan 29, 2019
    Posts:
    1,114
    Code (CSharp):
    1. public IEnumerable<GameObject> FindAllGameObjectsThatDirectlyReference(ScriptableObject target)
    2. {
    3.     HashSet<GameObject> results = new HashSet<GameObject>();
    4.  
    5.     foreach(var componentType in TypeCache.GetTypesDerivedFrom<MonoBehaviour>())
    6.     {
    7.         if(componentType.IsGenericTypeDefinition)
    8.         {
    9.             continue;
    10.         }
    11.  
    12.         Object[] instancesOfType = null;
    13.         const BindingFlags flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly;
    14.         foreach(var field in componentType.GetFields(flags))
    15.         {
    16.             if(!field.FieldType.IsInstanceOfType(target))
    17.             {
    18.                 continue;
    19.             }
    20.  
    21.             instancesOfType ??= FindObjectsOfType(componentType, true);
    22.  
    23.             foreach(var instance in instancesOfType)
    24.             {
    25.                 if(field.GetValue(instance) as Object == target)
    26.                 {
    27.                     results.Add((instance as Component).gameObject);
    28.                 }
    29.             }
    30.         }
    31.     }
    32.  
    33.     return results;
    34. }
     
    Last edited: Mar 5, 2023
    orionsyndrome likes this.
  3. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,043
    Haha reflection guru rips apart his libraries! Though I'm pretty sure BindingFlags (as the first thing I saw) requires
    using System.Reflection;
    .
     
  4. SisusCo

    SisusCo

    Joined:
    Jan 29, 2019
    Posts:
    1,114
    Yeaah, I may have had to implement
    Find All Instances Of
    "once or twice" before :D

    Yep, examining the fields of objects requires reflection to be used. I don't think there's any way around that.

    Since TypeCache is used and there are no GetCustomAttributes calls (which are notoriously slow) it should still be decently fast.

    There's definitely still some optimizations that could be done to this if not performing fast enough. For example using just one FindObjectsOfType<MonoBehaviour> would probably be quite a bit faster than calling FindObjectsOfType multiple times in worst case scenarios.
     
  5. dogmachris

    dogmachris

    Joined:
    Sep 15, 2014
    Posts:
    1,373
    I see how it's working, but using reflection with a set of nested loops to go through each property of each object is about as brute force as it gets. I can think of scenes where you can call it a day, when you run the script. I'm wondering, is there really no more efficient way to do this?
     
  6. SisusCo

    SisusCo

    Joined:
    Jan 29, 2019
    Posts:
    1,114
    It can be optimized a bit further using dynamic programming and caching:
    Code (CSharp):
    1. private readonly Dictionary<Type, List<FieldInfo>> assignableFieldsByComponentType = new Dictionary<Type, List<FieldInfo>>() { { typeof(MonoBehaviour), null } };
    2.  
    3. public IEnumerable<GameObject> FindAllGameObjectsThatDirectlyReference(ScriptableObject target)
    4. {
    5.     HashSet<GameObject> results = new HashSet<GameObject>();
    6.  
    7.     foreach(var component in FindObjectsOfType<MonoBehaviour>(true))
    8.     {
    9.         if(results.Contains(component.gameObject))
    10.         {
    11.             continue;
    12.         }
    13.  
    14.         Type componentType = component.GetType();
    15.         if(!assignableFieldsByComponentType.TryGetValue(componentType, out var assignableFields))
    16.         {
    17.             var type = componentType;
    18.             do
    19.             {
    20.                 const BindingFlags flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly;
    21.                 foreach(var field in type.GetFields(flags))
    22.                 {
    23.                     if(field.FieldType.IsInstanceOfType(target))
    24.                     {
    25.                         assignableFields ??= new List<FieldInfo>();
    26.                         assignableFields.Add(field);
    27.                     }
    28.                 }
    29.  
    30.                 type = type.BaseType;
    31.                 if(!assignableFieldsByComponentType.TryGetValue(type, out var assignableFieldsFromBaseTypes))
    32.                 {
    33.                     continue;
    34.                 }
    35.  
    36.                 if(assignableFieldsFromBaseTypes is null)
    37.                 {
    38.                     break;
    39.                 }
    40.  
    41.                 assignableFields.AddRange(assignableFieldsFromBaseTypes);
    42.                 break;
    43.             }
    44.             while(true);
    45.  
    46.             assignableFieldsByComponentType.Add(componentType, assignableFields);
    47.         }
    48.  
    49.         if(assignableFields is null)
    50.         {
    51.             continue;
    52.         }
    53.  
    54.         foreach(var field in assignableFields)
    55.         {
    56.             if(field.GetValue(component) as Object == target)
    57.             {
    58.                 results.Add(component.gameObject);
    59.                 break;
    60.             }
    61.         }
    62.     }
    63.  
    64.     return results;
    65. }
    Other than that the only alternative I can think of is using SerializedObjects, but I have no idea if that would be faster or slower. Something like this:
    Code (CSharp):
    1. public IEnumerable<GameObject> FindAllGameObjectsThatDirectlyReference(ScriptableObject target)
    2. {
    3.     HashSet<GameObject> results = new HashSet<GameObject>();
    4.  
    5.     foreach(var component in FindObjectsOfType<MonoBehaviour>(true))
    6.     {
    7.         if(results.Contains(component.gameObject))
    8.         {
    9.             continue;
    10.         }
    11.  
    12.         using(var serializedObject = new SerializedObject(component))
    13.         {
    14.             var iterator = serializedObject.GetIterator();
    15.             while(iterator.Next(false))
    16.             {
    17.                 if(iterator.propertyType == SerializedPropertyType.ObjectReference && iterator.objectReferenceValue is ScriptableObject value && value == target)
    18.                 {
    19.                     results.Add(component.gameObject);
    20.                     break;
    21.                 }
    22.             }
    23.         }
    24.     }
    25.     return results;
    26. }
    If neither of these approaches are fast enough for your scenes, then I'm afraid the only way to improve the performance would be to add custom code to your components that would make it possible to check if they contain references to an object without using any reflection.
    Code (CSharp):
    1. public interface IScriptableObjectUser
    2. {
    3.     bool IsUsedBy(ScriptableObject scriptableObject);
    4. }
    Example implementation:
    Code (CSharp):
    1. private SettingsAsset settings;
    2.  
    3. bool IScriptableObjectUser.IsUsedBy(ScriptableObject scriptableObject)
    4. {
    5.     return settings == scriptableObject;
    6. }
     
    Last edited: Mar 5, 2023
  7. dogmachris

    dogmachris

    Joined:
    Sep 15, 2014
    Posts:
    1,373
    IDK, maybe SerializedObject can indeed avoid some of the overhead of the reflection system. (Don't we all enjoy debugging code that's so convoluted it looks like it was written by a mad scientist? :D)
     
  8. SisusCo

    SisusCo

    Joined:
    Jan 29, 2019
    Posts:
    1,114
    Yeah it's very unfortunate that optimizing the performance of code and the optimizing the human readability of code rarely go hand in hand :)