Efficient way to reference objects in the scene

My biggest gripe with Unity from day one has been the lack of a simple, efficient way to reference objects in the scene. We currently have a few different options:

  • Search for the object using things like GameObject.Find: obviously terrible in terms of both performance and maintainability.

  • Instantiate the object from a prefab: less terrible, but now I have to go through the extra steps of making everything I want to reference a prefab.

I still also need a way of referencing the parent object that I want to instantiate my object inside of - which generally means making the parent object a prefab as well. Ultimately pretty much the entire scene ends up being a giant prefab that gets instantiated at runtime.

If you want to get references to objects nested inside of these prefabs, you have to search through the children - not as bad as GameObject.Find, but still prone to breaking when things change. This approach works, and is what I’ve been using so far, but prefabs are much clunkier and less intuitive to work with than the scene hierarchy.

  • Use static references: you can attach a mono script to an object in the scene which stores references to scene objects and then exposes them via static fields. This approach seems to simplest and cleanest and is what I plan to start using in the future for objects that don’t make sense as prefabs (e.g. UI elements).
    public class ReferenceHandler : MonoBehaviour
    {
        [SerializeField] private GameObject _sceneObject;
        public static GameObject SceneObject;

        private void Awake()
        {
            SceneObject = _sceneObject;
        }
    }

This is, however, still less clean and less encapsulated than being able to directly reference objects in the scene; as everything is globally exposed and you have to write extra fields and assignment code.

What I would like to be able to do is the simplest and most intuitive approach: simply drag the scene object into a serialized field to get a reference to it. Of course, this doesn’t work for whatever architectural/design reasons, but frankly I don’t see any good reasons why it shouldn’t work.

Do any of you use a different approach to the ones listed above? Any clever workarounds or tools to make this process less painful?

I assume your question is actually “why can’t assets reference objects in a scene” and the answer is scenes are transient. When a scene is loaded it’s basically akin to Instantiate()-ing everything in the scene. It’s a copy of the data in the scene asset in loaded into memory. So even if you could reference an object in a scene, when you load the scene the object that you get is a copy (thus a different object) and it all break downs.

You can also load a scene multiple times. So the idea of referencing scene objects breaks down even more with that in mind.

An old but straight-forward solution I came up with some time ago was this: https://discussions.unity.com/t/769595/9

Where you only need to make a scriptable object to be able to create a ‘link’ from assets to scenes. Then you just reference the links scriptable objects instead and use that to scoop up a reference to the relevant game object.

If I were to tackle this issue again today, I would probably still have the component, but instead of a scriptable object I’d use some custom inspector work to select a ‘key’ that I’ve outlined in a central configuration scriptable object. Then elsewhere I can reference the same key. The component, in Awake, registers itself to a central registry (aka a static class), and references with these keys can be used to look up said object in the registry. Though that’s a lot of extra work to save some scriptable object assets. I would only bother making this system if I needed to reference a lot of scene objects from assets.

1 Like

Yeah, I’m looking for a way to get direct references to my scene objects in my ScriptableObjects. Looks like there really isn’t any way to accomplish this without some sort of “middle man” to pass the reference along. I initially considered something similar to your approach, but I dislike having to attach a script to every scene object I want to reference.

Given how ubiquitous ScriptableObjects have become, maybe this should be a feature request? Seems likely to get shot down though since it debatably defies Unity’s design principles/intended way of doing things.

Maybe as an editor extension? It should definitely be possible to build something that does some magic behind the scenes to accomplish this functionality.

I have a bunch of different SO manager singletons that I need to pass references to. I think what I’ll do for now is simply:

    public class ReferenceHandler : MonoBehaviour
    {
        public GameObject SceneObject;

        private void Awake()
        {
            GameManager.SceneObject = SceneObject ;
        }
    }

To be fair, in a prior project I did also have scriptable object singletons that wanted a reference to a game object, but it turned out to be a symptom of an architectural problem that needed fixing, and not a solution to enable it.

There are of course situations where it is reasonable to want to reference a scene object - such as a dialogue system where you want the data to be in assets, but be able to select what characters or other points of focus for the camera to move to, etc. The system like I mentioned would make it pretty seamless.

1 Like

This project only has one scene (it shows and hides views as necessary). Therefore it seems logical to be getting references to the scene since those objects will never be destroyed by a scene change.

Your system requires adding a script and then creating an SO asset for each scene reference, correct?

I second the architectural issue. This problem stems from turning the concept upside down: referncing scene objects in a ScriptableObject or prefab asset.

The best solution here is to: reference the ScriptableObject / prefab asset in the scene. :wink:

And think long and hard about why that SO needs a reference to an in-scene object.

Not sure what you’re doing here but this feels totally unnecessary making everything a prefab.

Let’s say you have a root “generic manager” object in the scene. You assign the prefab you want to instantiate to it. You drag an in-scene parent (container) object to it as well that’s also in the scene which also may have components on it and totally doesn’t need to be a prefab.

Then you instantiate the prefabs and use the in-scene transform as the parent.
If the prefab instance needs a reference to the parent, you just use GetComponentInParent

You don’t have to search children in a prefab at all.

Again, the prefab has a “prefab references” component on it. While editing the prefab you assign all the references that you need outside the prefab. The above “generic manager” then can instantiate the prefab and use GetComponent and so has access to all the components or objects within the prefab without having to know exactly where they are or how to “find” them.

You want to encapsulate prefabs as much as possible and only expose to the outside what the outside needs.

More often than not, your “another external generic manager” will want to call some method on the prefab instance which is in a component deep in the hierarchy of the prefab. If you have this situation, it’s a flaw in the architecture.

The prefab itself should have at least (and in many cases: at most) one “API” component that outside scripts call into. Say your prefab is a “chess piece” then you don’t want to have an outside in-scene “ChessPieceManager” that moves these pieces. Instead it has access to the “ChessPieceAPI” component and calls ChessPiece.MoveTo() on it which inside it performs all actions that are within the scope of the prefab - game logic, animation playback, spawning explosion vfx (we’re making chess cool again! :smile: ) and raising events like “I have arrived at my destination”.

2 Likes

Guid Based Reference solves this. It’s an official (as in made by Unity) repository. The idea is pretty simple - objects gets a GUID, you store this guid when you store a reference to an object. Objects register themselves in Awake to a global registry, dereferences themselves in OnDestroy. When you hold a guidReference, it’s null if the object isn’t in the global registry, and non-null if it is.

For debugging, it stores the scene name and object name, but only in editor.

We used a fork of it for a bunch of stuff in Teslagrad 2, and it was surprisingly many spots where it turned out to be usefull to have an automatic global identifier for an object in a scene that could be used even if that object wasn’t loaded. The original use case was for making sure that there was only one copy of an object if you carried it out of a scene and then carried it back into the scene, but it also found uses for save IDs and other fun things.

The main cross scene use was to be able to show things in specific levels on the in-game map, as the map was in it’s own scene, and there was too many objects that should be shown on the map that it’d have been practical to hand-write unique identifiers for all of them.

2 Likes

I just remembered this weird nugget in the scripting API: Unity - Scripting API: ExposedReference<T0>

Never used it but it looks akin to what I described, but looks like you pass an object that has the implementation for locating the ‘referenced’ object. So a form of indirect reference I guess. Need to play around with it I guess.

ExposedReference was made for Timeline. It’s a pretty simple concept - an asset (like a Timeline-based cutscene) needs to interact with objects in the scene. Those objects are supplied by an object in the scene (Like the PlayableDirectior that plays the timeline), which contains a dictionary for names-to-targets for the asset.

The neat thing about that is that you can swap them at runtime - so you can play the same Timeline on different playable characters by swapping the target, or reuse the same cutscene in different levels with different skinnings for objects. So I think it’s solving a slightly different problem?

I haven’t ever seen anyone use ExposedReference outside of the context of Timeline, but I assume that it’s possible to use it for other things since it’s exposed in the way that it is.

1 Like

Thanks, I’ll take a look at this.

I remember trying this out a while back and it didn’t work out some reason… can’t recall what that reason was though.

I guess my question is: why is it wrong to turn the concept upside down? Just because that’s not how the workflow is currently designed? It makes sense to me and fits my workflow well, so I’d like to find a way to make it work.

Yeah, that’s what I’m currently doing in a lot of places; and it makes sense for GameObjects that do one and only one thing (the quintessential “Monobehavior”). But this stops making sense to me when dealing with things like UI elements that don’t necessarily have any scripts attached to them and just hold other UI elements and/or get moved around or shown/hidden.

It also doesn’t make sense to me in general to make specific UI elements like this prefabs, since they’re only instantiated once and in only one scene. It’s just a lot of extra steps and clutter to accomplish the same thing as a direct reference in my UIMananager (a ScriptableObject singleton) to the object in the scene.

I actually didn’t know prefabs could reference non-prefab objects in the scene. That is good to know. I admittedly have a lot to learn about prefabs as I’ve been using them to the bare minimum due to how much I dislike the prefab workflow.

I had no idea this PrefabReferences component existed. Searching for it yields almost no results. Is there a documentation page for this?

I agree on these points.

It’s a hypothetical component they’re using to explain a concept. It doesn’t actually exist.

Basically they’re talking about using references on components to model your data structure.

As for the architectural issue, I found that trying to put my game state into scriptable objects was fraught with issues. Namely because they don’t reset like scenes do, it became a book keeping affair to try and ensure that no state carried over into the next session.

The was solved by keeping game state in scenes, on monobehaviours where it ought to belong, and the odd static class where I could hook into EditorApplciation.playmodeStateChanged to reset itself upon leaving play mode, when I needed some form of global accessibility.

Oh… so just using GetComponentInChildren, GetComponentInParent, etc… This still requires searching through the children though. With UI elements I often end up doing something like:

List<TMP_Text> texts = prefab.GetComponentsInChildren<TMP_Text>().ToList();
                Title = texts.Where(t => t.name == "Title Text").FirstOrDefault();
// etc...

Is there a way to reference these things directly? Or would you have to have a script on the prefab that passes those references around?

I guess this mostly comes down to UI elements. I should have been more specific in my original post - what I’m primarily looking for is a good way to reference UI elements in my SO singleton managers that doesn’t involve prefabs or adding a “reference handler” type script to every object I want to reference.

I guess you could just not use ScriptableObjects and have monobehaviour singleton managers that live in the scene, but then you miss out on all the benefits of SOs and the ability to persist data if you want to.

All they’re talking about is just having a component that references all the other component that you care about. Then you don’t need a direct reference to everything in the scene, just that one component.

It’s nothing complicated, just a component with serialized fields and some read-only properties. Nothing more than:

public sealed class SomeUserInterface : MonoBehaviour
{
    [SerializeField]
    private TMP_Text _titleText;
  
    public TMP_Text TitleText => _titleText;

    // etc etc
}

This is usually how I structure my UI, in either UI Toolkit or uGUI. I’ll have a [Name]Interface component/visual element that holds references to everything important (along with primary API methods). Larger/more complicated interfaces may have multiple of these reference components.

Really it’s just about giving yourself the appropriate API to make your life easier.

But personally, even though I have my own scriptable object singleton system, I only use it for configuration data. Global values, important prefabs, etc. I’m not sure why a singleton monobehaviour can’t serve the same role in place of the scriptable object, completely nixing this issue.

1 Like

^ ^ ^ This. Don’t write code just to write code. Write code encapsulate value and business logic that goes together. Write code to make your program BETTER, not more complicated.

I call what Spiney is describing above an adaptor, because it usually doesn’t have any controlling logic, it’s just a way to get at stuff. Enclosed is my reference “how to do dynamic UI with arbitrary things.”

It lets you write code to make your stuff but still “Have Nice Things” in the Unity editor.

Note the adaptor called OneSingleTile:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

// @kurtdekker

public class OneSingleTile : MonoBehaviour
{
    [SerializeField]Button Button;
    [SerializeField]Text Caption;
    [SerializeField]Image Icon;

    public void SetCaptionText( string caption)
    {
        Caption.text = caption;
    }

    public void SetIconSprite (Sprite sprite)
    {
        Icon.sprite = sprite;
    }

    public void SetButtonDelegate( System.Action action)
    {
        Button.onClick.AddListener(
            delegate {
                action();
            }
        );
    }
}

Its only purpose is to centralize all the bits that go into one of these things: text, button, delegate, icon, etc.

9557998–1351300–DynamicUIDemo.unitypackage (94 KB)

1 Like

OK, I get it. I generally don’t refer to custom scripts as components, so I was very confused. This is just another reference passing script then.

You’re probably right - just using monobehaviour singletons would make my life easier. I think I’ll restructure my project around that.

Thanks for the advice everyone.

1 Like