Search Unity

Feedback How to move a Component without losing all the connections pointing to it? Or how to find them.

Discussion in 'General Discussion' started by AlanMattano, May 31, 2020.

  1. AlanMattano

    AlanMattano

    Joined:
    Aug 22, 2013
    Posts:
    1,501
    upload_2020-5-31_0-52-18.png

    I use to grab and drop game objects to connect components into the inspector.

    If I want to reorganize and move a component from one GameObject to another GO. How do I know which components are pointing to it? Is there any automatic solution that guarantees not losing connections?
     
  2. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,571
    As far as I'm aware, you don't know which components are pointing to it. It is many to one relationship. In theory, you could try abusing reflection, iterating thorugh every field of every component, determining if a [SerializeField] or public field is of Object or Component type, then finding corresponding reference.

    However, I think it is not worth the effort. I suggest to reconsider design and reduce number of bidirectional references in your code. Preferably to zero.
     
    angrypenguin and RecursiveFrog like this.
  3. AlanMattano

    AlanMattano

    Joined:
    Aug 22, 2013
    Posts:
    1,501
    upload_2020-5-31_19-41-7.png
    They pop up everywhere.

    Code (CSharp):
    1. public GameObject myCat_GO;
    2. public GameObject dragButtonGO;
    3. etc...
    Or simple GUI button needs a reference. And as I understand is a common practice.

    How to achieve no bidirectional references in C#? making a child? And in the case of a button using your if (null)?


     
  4. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,571
    If you think up a situation where you can't see a solution without bidirectional reference, I'll be probably able to make it singular directional reference.

    For example, in GUI, a button invokes a function on some object. However, said object has no need to know what invoked that function. Thus, there's no need for the object to hold a "back reference" to the button. A lot of function work this way.

    The other thing to keep in mind is that you don't need to establish all reference at design time. For example, if you're making a car, it could look like it is sensible to assign references to Wheels at design time, BUT, instead of doing that you can simply make Car look for Wheels when it is activated and assign roles automatically.

    One practical example, where you might THINK that you need back reference is a GUI where buttons are being enabled/disabled. Because in this case it might make sense to have one button disable another, and so on. In this case, you can do this:
    1. Make a class akin to OptionMenuGUIManager, and make it root.
    2. Make a state machine Enum corresponding to each page of GUI.
    3. Make some sort of "OptionMenuPage" class which would have indicate in which state of menu it will be activated, set it for relevant elements, and assign this component as option menu panel
    4. Upon startup of OptionMenuGUIManager, find all OptionMenuPage components and store them in a list, dictionary or anythign else. When option menu state changes, have OptionMenuGUIManager enable/disable relevant components.

    In this case, each button will only need to know reference to OptionMenuGUIManager.

    But. Even that reference is not necessary. Because you can find OptionMenuGUIManager at runtime using GetComponentInParent<T> funciton.

    So, there's no real need to specify any connection by dragging components in this case, and all relationships can be established automatically.
    ------

    Basically, in simpler terms, if you have a Car with Wheels attached to it, you might think that it is a good idea to store reference to Wheels in a Car, and references to Car in a Wheel. But those references can be found automacially.
    Car can find its Wheels using GetComponentsInChildren, and Wheels can find their car using GetComponentInParent.

    This can be done within Awake/OnEnable then said values can be cached, and it won't be necessary to call those functions repeatedly.

    Obviously you should be using templated version of those funcitons....
     
  5. Marble

    Marble

    Joined:
    Aug 29, 2005
    Posts:
    1,268
    There are some hacks out there to help you find broken references. I haven't used them, but I imagine they would help with the time consuming part, even if they're not exactly automatic, which would indeed be nice.

    I like using references initialized in the Editor like those and avoid GetComponent when it's easy to do so, so if I find myself running into this issue I try to mitigate it by:
    1. Using prefabs and keeping references contained within prefab structures. If a reference breaks, I only have to fix it once instead of repeatedly across my scene.
    2. If I can't keep a reference contained within a prefab (e.g. references to a UI button like in your example), AND if more than one component references the target AND the reference doesn't ever get reassigned at runtime, I usually merge them all to a single reference in a singleton. So the UI root might have a singleton that refers to those buttons in its prefab structure, making them available from anywhere.
    Of course, I agree that finding ways to minimize unnecessary references is desirable.
     
    AlanMattano likes this.
  6. Ryiah

    Ryiah

    Joined:
    Oct 11, 2012
    Posts:
    21,189
    https://assetstore.unity.com/packages/tools/utilities/reference-finder-69028

    It's not automatic by default and I don't know how difficult it would be to automate it but one solution is to use scriptable objects as the connections between components.

     
    AlanMattano likes this.
  7. AlanMattano

    AlanMattano

    Joined:
    Aug 22, 2013
    Posts:
    1,501
    Dear forum moderator @hippocoder can you move this thread to C#?

    For a novice user, this is hard can trigger hiding problems!
    As an artist, now coding, I feel like this cloud be improved from the Unity side.
    The problem is that when you press play all those missing references on Button script are lost. So if you do not coach them before hitting play, they are lost in time like tears in the rain.



    Thx @Ryiah and all you,
    Video and link! the link was perfect.

    InspectorComponents.png

    For how is using Reference Finder, adding console filters is simple.
    Code (CSharp):
    1. static void ShowError (string context, GameObject go, string componentName, string propertyName)
    2.     {
    3.         var ERROR_TEMPLATE = "Missing Ref in: [{3}]{0}. Component: {1}, Property: {2}";
    4.  
    5.         // FILTERS:
    6.         if (propertyName.Equals("Sprite") || propertyName.Equals("Highlighted Sprite") )
    7.         {
    8.                 //do nothing.
    9.         }
    10.         else
    11.         {
    12.             Debug.LogError(string.Format(ERROR_TEMPLATE, GetFullPath(go), componentName, propertyName, context), go);
    13.         }
    14.     }
    But if you forget to hit the Reference Finder button, buttons will lose the missing warning.

    Yha, time ago I was using plugins tools,
    https://assetstore.unity.com/packages/tools/utilities/qhierarchy-28577#description
    but for some reason, was running at runtime under the ground.
     
    Last edited: Jun 2, 2020
  8. AlanMattano

    AlanMattano

    Joined:
    Aug 22, 2013
    Posts:
    1,501
    Another solution could be:
    When the user presses Play Mode, to run a search for missing references (using MissingReferencesUnity)
    And stop, prevent running or executing PlayMode if they are found.
    In this way, "Missing" references are not lost.
     
  9. AlanMattano

    AlanMattano

    Joined:
    Aug 22, 2013
    Posts:
    1,501
    Before I press play I try to run this script. That is a modification of Marble suggestion

    Code (CSharp):
    1. using System.Reflection;
    2. using System.Linq;
    3. using UnityEditor;
    4. using UnityEditor.SceneManagement;
    5. using UnityEngine;
    6. using System;
    7.  
    8. /**************************************************************************************
    9. *    
    10. *          . Author: https://github.com/liortal53
    11. *          . From: https://github.com/liortal53/MissingReferencesUnity
    12. *          . Talk: [URL]https://forum.unity.com/threads/how-to-move-a-component-without-losing-all-the-connections-pointing-to-it-or-how-to-find-them.902069/[/URL]
    13. *          . Link: [URL]https://forum.unity.com/threads/major-always-bydesign-refactoring-renaming-functions-missing-references-and-no-console-warning.915914/[/URL]
    14. *
    15. * ************************************************************************************/
    16.  
    17.  
    18. public class MissingReferencesFinder : MonoBehaviour
    19. {
    20.     // CUSTOMISATION PATH
    21.     private const string MENU_ROOT = "Alan/";
    22.  
    23.     // general variables
    24.     private static bool thereAreReferences = false;
    25.  
    26.     // CUSTOMISATION FILTER
    27.     static void ShowError (string context, GameObject go, string componentName, string propertyName)
    28.     {
    29.         var ERROR_TEMPLATE = "Missing Ref in: [{3}]{0}. Component: {1}, Property: {2}";
    30.  
    31.         // FILTERS: Hide not important elements on this list
    32.         if (propertyName.Equals("Sprite") || propertyName.Equals("Highlighted Sprite") ||
    33.             propertyName.Contains("Material") || propertyName.Contains("Prob") ||
    34.             propertyName.Contains("Material") || propertyName.Contains("Lightmap") ||
    35.             componentName.Contains("Slider") || componentName.Contains("InputField") ||
    36.             componentName.Contains("Scrollbar") || propertyName.Contains("Select On") ||
    37.             componentName.Contains("Image") || propertyName.Contains("Sprite") ||
    38.             componentName.Contains("SmartChart") || propertyName.Contains("Labels") ||
    39.             propertyName.Contains("Texture") || propertyName.Contains("First Selected") ||
    40.             propertyName.Contains("Avatar") || propertyName.Contains("Horizontal Scrollbar") ||
    41.             propertyName.Contains("Animation") || propertyName.Contains("Slider Controller Value"))
    42.         {
    43.                 //do nothing. this prevent a bug using ! and "&&" in the if statement.
    44.         }
    45.         else
    46.         {
    47.             if (componentName.Contains("Button") || propertyName.Contains("Object Argument"))
    48.             {
    49.                 // display the actual missing reference
    50.                 Debug.LogWarning(string.Format(ERROR_TEMPLATE, GetFullPath(go), componentName, propertyName, context), go);
    51.                 thereAreReferences = true;
    52.             } else
    53.             {
    54.                 // display the actual missing reference
    55.                 Debug.LogError(string.Format(ERROR_TEMPLATE, GetFullPath(go), componentName, propertyName, context), go);
    56.                 thereAreReferences = false;
    57.             }
    58.          
    59.         }
    60.     }
    61.  
    62.  
    63.     /// <summary>
    64.     /// Finds all missing references to objects in the currently loaded scene.
    65.     /// </summary>
    66.     [MenuItem(MENU_ROOT + "Missing References Console Search", false, 50)]
    67.     public static void FindMissingReferencesInCurrentScene()
    68.     {
    69.         Utils.ClearLogConsole();
    70.         var sceneObjects = GetSceneObjects();
    71.         FindMissingReferences(EditorSceneManager.GetActiveScene().path, sceneObjects);
    72.     }
    73.  
    74.  
    75.  
    76.     // PRIVATE
    77.  
    78.     private static void FindMissingReferences(string context, GameObject[] gameObjects)
    79.     {
    80.         if (gameObjects == null)
    81.         {
    82.             return;
    83.         }
    84.  
    85.         thereAreReferences = false;
    86.  
    87.         foreach (var go in gameObjects)
    88.         {
    89.             var components = go.GetComponents<Component>();
    90.          
    91.             foreach (var component in components)
    92.             {
    93.                 // Missing components will be null, we can't find their type, etc.
    94.                 if (!component)
    95.                 {
    96.                     Debug.LogErrorFormat(go, $"Missing Component {0} in GameObject: {1}", component.GetType().FullName, GetFullPath(go));
    97.  
    98.                     continue;
    99.                 }
    100.              
    101.                 SerializedObject so = new SerializedObject(component);
    102.                 var sp = so.GetIterator();
    103.  
    104.                 var objRefValueMethod = typeof(SerializedProperty).GetProperty("objectReferenceStringValue",
    105.                     BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
    106.  
    107.                 // Iterate over the components' properties.
    108.                 while (sp.NextVisible(true))
    109.                 {
    110.                     if (sp.propertyType == SerializedPropertyType.ObjectReference)
    111.                     {
    112.                         string objectReferenceStringValue = string.Empty;
    113.                      
    114.                         if (objRefValueMethod != null)
    115.                         {
    116.                             objectReferenceStringValue = (string) objRefValueMethod.GetGetMethod(true).Invoke(sp, new object[] { });
    117.                         }
    118.  
    119.                         if (sp.objectReferenceValue == null
    120.                             && (sp.objectReferenceInstanceIDValue != 0 || objectReferenceStringValue.StartsWith("Missing") ||
    121.                                 objectReferenceStringValue.StartsWith("None (")))
    122.                         {
    123.                             ShowError(context, go, component.GetType().Name, ObjectNames.NicifyVariableName(sp.name));
    124.                         }
    125.                     }
    126.                 }
    127.             }
    128.         }
    129.  
    130.         if (thereAreReferences)
    131.         {
    132.             // display a message at the end of the process
    133.             Debug.Log("There are References ----------------------------------- END.\n");
    134.         }
    135.         else
    136.         {
    137.             Debug.Log("There are no missing References ---------------------- END OF TASK!\n");
    138.             EditorApplication.isPlaying = true;
    139.         }
    140.     }
    141.  
    142.     private static GameObject[] GetSceneObjects()
    143.     {
    144.         // Use this method since GameObject.FindObjectsOfType will not return disabled objects.
    145.         return Resources.FindObjectsOfTypeAll<GameObject>()
    146.             .Where(go => string.IsNullOrEmpty(AssetDatabase.GetAssetPath(go))
    147.                    && go.hideFlags == HideFlags.None).ToArray();
    148.     }
    149.  
    150.     private static string GetFullPath(GameObject go)
    151.     {
    152.         return go.transform.parent == null
    153.             ? go.name
    154.                 : GetFullPath(go.transform.parent.gameObject) + "/" + go.name;
    155.     }
    156. }
    157.  
    158.  
    159. public static class Utils
    160. {
    161.     static MethodInfo _clearConsoleMethod;
    162.     static MethodInfo clearConsoleMethod
    163.     {
    164.         get
    165.         {
    166.             if (_clearConsoleMethod == null)
    167.             {
    168.                 Assembly assembly = Assembly.GetAssembly(typeof(SceneView));
    169.                 Type logEntries = assembly.GetType("UnityEditor.LogEntries");
    170.                 _clearConsoleMethod = logEntries.GetMethod("Clear");
    171.             }
    172.             return _clearConsoleMethod;
    173.         }
    174.     }
    175.  
    176.     public static void ClearLogConsole()
    177.     {
    178.         clearConsoleMethod.Invoke(new object(), null);
    179.     }
    180. }
     
    Last edited: Jun 24, 2020
  10. AlanMattano

    AlanMattano

    Joined:
    Aug 22, 2013
    Posts:
    1,501
    In Unity, Is missing a connection panel?

     
  11. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,620
    I haven't watched that video, but keep in mind that a piece of software is not a house. In many cases the analogy will hold up, but in many it also won't.

    back to the OP, my policy is to always minimise the number of manual Inspector connections that need to be made in the first place. Anything that can be looked up automatically should be. You can do this at edit time rather than runtime if you're trying to optimise load times or whatever, the important thing is that every step you make a human do is a step which can be missed or where a mistake can be made.

    Of course a badly coded automatic hookup can also fail, but fixing that once will solve it for every time, where fixing it manually only fixes that one case.

    Plus, even if hookups only take 3 seconds each... just think about how darn many you have to make! So I've never bothered figuring out how to "move" a component and keep references intact, because that's never been a big deal in the first place.
     
    Ryiah likes this.
  12. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,571
    That was a point driven home in one of the "Extreme Programming" brochures long time ago. Nevermind that house can be less complex than a program.

    If we designed houses like we deal with software, it would go like this.

    "We need to build an outhouse"
    "Scratch that, it needs a living room attached".
    "Also kitchen and bathroom".
    "And jaccuzi"
    "We forgot to tell you about a pool".
    "Oh, and it's on the cliff"
    "It should also be able to withstand earthquakes. We have earthquakes".
    "We also want a helipad now".
    "Also, would be cool if it could float in the ocean".
    "Scratch it, we need to hover instead"
    "Say, would be great if it could achieve escape velocity, reach orbit and then survive the return trip"

    Add 2 week pause between each requirement.

    From an outhouse. To a spaceship that has a pool onboard.