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.
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.
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.
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.
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! ) and raising events like âI have arrived at my destinationâ.
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.
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.
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?
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:
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.
^ ^ ^ 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.