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

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?

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.

2 Likes

5923793--633056--upload_2020-5-31_19-41-7.png

They pop up everywhere.

public GameObject myCat_GO;
public GameObject dragButtonGO;
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)?

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 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…

3 Likes

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:

  • 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.
  • 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.

1 Like

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.

Unite Austin 2017 - Game Architecture with Scriptable Objects

1 Like

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.

For how is using Reference Finder, adding console filters is simple.

static void ShowError (string context, GameObject go, string componentName, string propertyName)
    {
        var ERROR_TEMPLATE = "Missing Ref in: [{3}]{0}. Component: {1}, Property: {2}";

        // FILTERS:
        if (propertyName.Equals("Sprite") || propertyName.Equals("Highlighted Sprite") )
        {
                //do nothing.
        }
        else
        {
            Debug.LogError(string.Format(ERROR_TEMPLATE, GetFullPath(go), componentName, propertyName, context), go);
        }
    }

But if you forget to hit the Reference Finder button, buttons will lose the missing warning.

Yha, time ago I was using plugins tools,

but for some reason, was running at runtime under the ground.

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.

Before I press play I try to run this script. That is a modification of Marble suggestion

using System.Reflection;
using System.Linq;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
using System;

/**************************************************************************************
*    
*          . Author: https://github.com/liortal53
*          . From: https://github.com/liortal53/MissingReferencesUnity
*          . Talk: [URL] https://discussions.unity.com/t/793565 [/URL]
*          . Link: [URL] https://discussions.unity.com/t/796697 [/URL]
*
* ************************************************************************************/


public class MissingReferencesFinder : MonoBehaviour
{
    // CUSTOMISATION PATH
    private const string MENU_ROOT = "Alan/";
  
    // general variables
    private static bool thereAreReferences = false;
  
    // CUSTOMISATION FILTER
    static void ShowError (string context, GameObject go, string componentName, string propertyName)
    {
        var ERROR_TEMPLATE = "Missing Ref in: [{3}]{0}. Component: {1}, Property: {2}";

        // FILTERS: Hide not important elements on this list
        if (propertyName.Equals("Sprite") || propertyName.Equals("Highlighted Sprite") ||
            propertyName.Contains("Material") || propertyName.Contains("Prob") ||
            propertyName.Contains("Material") || propertyName.Contains("Lightmap") ||
            componentName.Contains("Slider") || componentName.Contains("InputField") ||
            componentName.Contains("Scrollbar") || propertyName.Contains("Select On") ||
            componentName.Contains("Image") || propertyName.Contains("Sprite") ||
            componentName.Contains("SmartChart") || propertyName.Contains("Labels") ||
            propertyName.Contains("Texture") || propertyName.Contains("First Selected") ||
            propertyName.Contains("Avatar") || propertyName.Contains("Horizontal Scrollbar") ||
            propertyName.Contains("Animation") || propertyName.Contains("Slider Controller Value"))
        {
                //do nothing. this prevent a bug using ! and "&&" in the if statement.
        }
        else
        {
            if (componentName.Contains("Button") || propertyName.Contains("Object Argument"))
            {
                // display the actual missing reference
                Debug.LogWarning(string.Format(ERROR_TEMPLATE, GetFullPath(go), componentName, propertyName, context), go);
                thereAreReferences = true;
            } else
            {
                // display the actual missing reference
                Debug.LogError(string.Format(ERROR_TEMPLATE, GetFullPath(go), componentName, propertyName, context), go);
                thereAreReferences = false;
            }
          
        }
    }


    /// <summary>
    /// Finds all missing references to objects in the currently loaded scene.
    /// </summary>
    [MenuItem(MENU_ROOT + "Missing References Console Search", false, 50)]
    public static void FindMissingReferencesInCurrentScene()
    {
        Utils.ClearLogConsole();
        var sceneObjects = GetSceneObjects();
        FindMissingReferences(EditorSceneManager.GetActiveScene().path, sceneObjects);
    }

  
  
    // PRIVATE

    private static void FindMissingReferences(string context, GameObject[] gameObjects)
    {
        if (gameObjects == null)
        {
            return;
        }

        thereAreReferences = false;

        foreach (var go in gameObjects)
        {
            var components = go.GetComponents<Component>();
          
            foreach (var component in components)
            {
                // Missing components will be null, we can't find their type, etc.
                if (!component)
                {
                    Debug.LogErrorFormat(go, $"Missing Component {0} in GameObject: {1}", component.GetType().FullName, GetFullPath(go));

                    continue;
                }
              
                SerializedObject so = new SerializedObject(component);
                var sp = so.GetIterator();

                var objRefValueMethod = typeof(SerializedProperty).GetProperty("objectReferenceStringValue",
                    BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);

                // Iterate over the components' properties.
                while (sp.NextVisible(true))
                {
                    if (sp.propertyType == SerializedPropertyType.ObjectReference)
                    {
                        string objectReferenceStringValue = string.Empty;
                      
                        if (objRefValueMethod != null)
                        {
                            objectReferenceStringValue = (string) objRefValueMethod.GetGetMethod(true).Invoke(sp, new object[] { });
                        }

                        if (sp.objectReferenceValue == null
                            && (sp.objectReferenceInstanceIDValue != 0 || objectReferenceStringValue.StartsWith("Missing") ||
                                objectReferenceStringValue.StartsWith("None (")))
                        {
                            ShowError(context, go, component.GetType().Name, ObjectNames.NicifyVariableName(sp.name));
                        }
                    }
                }
            }
        }

        if (thereAreReferences)
        {
            // display a message at the end of the process
            Debug.Log("There are References ----------------------------------- END.\n");
        }
        else
        {
            Debug.Log("There are no missing References ---------------------- END OF TASK!\n");
            EditorApplication.isPlaying = true;
        }
    }

    private static GameObject[] GetSceneObjects()
    {
        // Use this method since GameObject.FindObjectsOfType will not return disabled objects.
        return Resources.FindObjectsOfTypeAll<GameObject>()
            .Where(go => string.IsNullOrEmpty(AssetDatabase.GetAssetPath(go))
                   && go.hideFlags == HideFlags.None).ToArray();
    }

    private static string GetFullPath(GameObject go)
    {
        return go.transform.parent == null
            ? go.name
                : GetFullPath(go.transform.parent.gameObject) + "/" + go.name;
    }
}


public static class Utils
{
    static MethodInfo _clearConsoleMethod;
    static MethodInfo clearConsoleMethod
    {
        get
        {
            if (_clearConsoleMethod == null)
            {
                Assembly assembly = Assembly.GetAssembly(typeof(SceneView));
                Type logEntries = assembly.GetType("UnityEditor.LogEntries");
                _clearConsoleMethod = logEntries.GetMethod("Clear");
            }
            return _clearConsoleMethod;
        }
    }

    public static void ClearLogConsole()
    {
        clearConsoleMethod.Invoke(new object(), null);
    }
}

In Unity, Is missing a connection panel?

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.

1 Like

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.