Exaud Blog
Blog
Introducing the Reference Finder: Simplifying Unity Development (Part II)
Welcome to part II of our article on introducing Reference Finder! In this part, we’ll delve deeper into its features and benefits. Posted onby Francisco LeitesReference Finder
Welcome to part II of our article on introducing Reference Finder! In this part, we’ll delve deeper into its features and benefits. Let’s get started!
Internally, the tool is divided into two parts: the view that generates the UI and displays information to the user, and the presenter that runs all the logic required to find references based on user parameters. I will be focusing on the presenter side of the tool, as it has more substance and shows what the tool does under the hood.
After the user selects the type of search and the object to have its references found, the tool will go through each of the open scenes, assets, and prefabs and retrieve all their GameObjects (sometimes through their Transform, as objects can be disabled, and other times through the available objects directly).
private void GetReferencesInPrefabStage(Dictionary objectsWithReferencesPerLocationDictionary) { Stage currentStage = StageUtility.GetCurrentStage(); if (!IsPrefabPreviewOpen()) { return; } Object[] objectsWithReferences = GetReferences(currentStage.FindGameObjectsInStage()); objectsWithReferencesPerLocationDictionary.Add("Prefab preview stage", objectsWithReferences); }
For each of the found objects, the tool runs a reference checker method that uses the SerializedObject version of a Unity object and its SerializedProperty iterator to go through each reference and compare it to the object we are looking for (including its components in the case of a GameObject).
public static bool HasReferenceTo(T unityObject, Object to) where T : Object { SerializedObject serializedComponent = new SerializedObject(unityObject); SerializedProperty iterator = serializedComponent.GetIterator(); while (iterator.NextVisible(true)) { if (iterator.propertyType == SerializedPropertyType.ObjectReference || iterator.propertyType == SerializedPropertyType.ManagedReference) { if (iterator.objectReferenceValue == to) { return true; } } } return false; }
Any references that are found are stored in a dictionary that holds a list of references categorized by area name, and then displayed to the user.
public void FindReferences() { _view.ClearRootAtIndex((int)ReferenceFinderUIOrder.ReferencesLists); Dictionary objectsPerSceneDictionary = new Dictionary(); GetReferencesInPrefabStage(objectsPerSceneDictionary); GetReferencesPerScene(objectsPerSceneDictionary, GetGameObjectsPerScene()); GetReferencesInAssets(objectsPerSceneDictionary); if (objectsPerSceneDictionary.Count == 0) { return; } _view.ShowReferencesPerScene(objectsPerSceneDictionary); }
Gimmicks and Quirks
As previously mentioned, some of the problems I encountered could only be solved by circumventing or using certain gimmicks. Although some of these issues have been resolved or have better documentation on later versions of Unity, I feel it’s still important to document them here, just in case someone is looking for a solution to any of these problems.
Starting with the ObjectField, I had a hard time figuring out what I needed to disable so that the editor wouldn’t throw a million errors when I clicked on the Object. I also wanted to prevent the user from altering the results, while still allowing me to use the built-in feature of showing and highlighting the object in the scene. To disable the drag and drop feature, you need to use the PreventDefault and StopImmediatePropagation methods within the object’s event. To achieve this, I have created the following method, which is a non-static variation.
the following method, which is a non-static variation. private void PreventEvent(EventBase eventBase) { eventBase.PreventDefault(); eventBase.StopImmediatePropagation(); }
This can be used as a callback for the action or event that you are trying to prevent.
objectField.RegisterCallback(PreventEvent); objectField.RegisterCallback(PreventEvent); objectField.RegisterCallback(PreventEvent); objectField.RegisterCallback(PreventEvent); objectField.RegisterCallback(PreventEvent);
In the ObjectField, you can disable the button on the right that allows for opening the context by following these steps.
objectField.Children().Last().Children().Last().SetEnabled(false);
Next up, stages! Unity provides scene editing contexts known as stages. Stages are divided into main stages, where normal scenes reside, and preview stages, where the prefab stage exists within a preview scene. To obtain the references from this scene, I would first need to check which stage is currently open. Then, the stage provides a FindComponentsOfType method that we can use to retrieve every Transform, and subsequently obtain their respective GameObjects.
The issue was that, in the version I had been using, there was no way to determine whether the open stage was a preview stage or not. My solution was to check whether the stage name was empty, as it seemed that preview stages do not have names.
private bool IsPrefabPreviewOpen() { Stage currentStage = StageUtility.GetCurrentStage(); return currentStage.name == string.Empty; }
At last, the version of the UI elements that I had been using (or, at the very least, the only version available to me) had a bug that prevented a tooltip from displaying on a disabled button. To work around this, I created an empty object that served as a shell for the button. This object was what I added the tooltip to.
public void CreateFindReferencesElements(string tooltip = "", bool buttonState = false) { VisualElement tooltipElement = new VisualElement(); tooltipElement.tooltip = tooltip; Button findReferencesButton = VisualElementUtils.CreateButton(OnFindReferencesClicked, "Find references", string.Empty); findReferencesButton.SetEnabled(buttonState); findReferencesButton.AddToClassList("find-references-element"); tooltipElement.Add(findReferencesButton); rootVisualElement.Insert((int)ReferenceFinderUIOrder.FindReferences, tooltipElement); }
Conclusion
Working on this tool has given me a new perspective on development. Because I want to release it to the public, I needed to focus on maintainability and readability from the beginning, which forced me to create cleaner code and add documentation to everything.
Furthermore, the rather “tight” work schedule required me to analyze the week’s objectives and break them down into smaller chunks that could be tackled during the week. This tool took me several months to develop, and there were many different iterations of the idea (at one point, I was using the meta files created by Unity), but this was the final result, and I couldn’t be happier.
The next steps would be to add unit testing (which had to be postponed as I was having trouble creating them without causing massive disruption to my code and its readability), filters for searches, to refactor some rather ugly code, and to take advantage of the UI Elements updates over the year to update the codebase and make it compatible with later versions of Unity. If you’d like to use the tool or check out the code, you can go to its Github repository through here.
Check out Part I of this article here.