Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Feature Request Idea: Scene-Bound Prefabs

Discussion in 'Editor & General Support' started by DragonCoder, Apr 15, 2023.

  1. DragonCoder

    DragonCoder

    Joined:
    Jul 3, 2015
    Posts:
    1,677
    This thread where somebody suggests a classic Dependency Injection framework made me think of how one could solve the pain points DI promises to solve, in a more "Unity style" way: Scene-Bound Prefabs.

    The Problem:
    Your game objects, including ones created through prefabs do need references to other objects in the scene. E.g. all enemies, missiles, bots etc. need to know how to aim for the player. The player itself needs references to various systems like the player manager in a multiplayer setup or even something as trivial as the weather system to know to open their umbrella :)

    The Common Solution:
    There are two typical ways to achieve that:
    A: Singletons which allow direct access to the systems. This is not very modular since the dependency is hardcoded to something that also has to be unique. It makes testing harder as well as keeping an overview of what game objects needs what other objects to exist.
    B: After every instantiation (or on scene initialization) you use GetComponents(InChildren)<> for all scripts that need references and pass them the ones you have. This is a pain to scale. Especially with prefabs where scripts that are not at the root need the references. Every time you add something to the prefab that needs a ref, you gotta change everywhere it gets instantiated.

    Personally I am using Singletons mostly to solve this problem. Am at least keeping it "flat" as in, there are only three global reference-holders (to allow splitting at least into three asmdef). This does allow testing by just populating the references in the Singleton accordingly. So far this worked for me, but it also has scaling issues and beware when the dev forgets what exactly needs to be populated correctly within the singletons for a specific test.

    The Proposed Solution:
    Just like we can make prefab-variants, let us have scene-bound variants of a prefab.

    By having the prefab window open alongside the hierarchy window of an open scene, we would be able to drag an object from the scene into the prefab. When that happens on a normal prefab, a popup shall ask/tell:
    "Do you want to create a prefab bound to "[SCENE_NAME]"?
    Note the reference "[NAME_OF_SET_VARIABLE]" remains Null until the scene is loaded for the first time."

    If you confirm that, a scene bound variant is created which works like what popup tells: The prefab effectively starts with null in its reference fields, however after the matching scene has been loaded, the reference is populated. That way you can use this prefab:
    - In the loaded scene
    - Also in future scenes if the referenced objects have been marked as "DoNotDestroyOnLoad" (otherwise the prefab does not keep the objects artificially alive).

    How is this better?
    This would allow for two new, more scalable workflows:

    1.
    A frequently seen approach at using "common solution A" is to have a "global" scene which is being loaded at game start and contains all objects that shall persist throughout the game. The objects are marked as "DoNotDestroyOnLoad" so they persist through regular scene loads and a singleton allows access to those instances.
    Scene-Bound prefabs enhance this approach by eliminating the singletons entirely. Bind your scene-bound prefabs to this start-up scene and the references are permanently ready.

    For maximum usability of this, it would be ideal to have "scene-bound prefab variants" so you can bind the global things to the start-up scene without taking the ability away to specify something for one specific scene.

    2.
    Whenever you have something highly scene specific in your prefab, you just link the instance within a corresponding scene-bound prefab. That solves most cases where "common solution B" was used.
    It is easily scalable because when you extend your prefab to need more references, you just drag them in and do not need to touch any instantiation scripts.


    What is your opinion on this concept?
    Have I overlooked any better solution than variants of A and B up there that already exists?

    Am curious for a discussion :)
     
    Last edited: Apr 15, 2023
  2. Saniell

    Saniell

    Joined:
    Oct 24, 2015
    Posts:
    191
    For GetComponent stuff I'd like to just have [AutoGet] attribute of sorts. Not only it allows to skip writing boilerplate, one could also just precache GetComponent calls for performance reasons, in fact, AutoGet should only work on serialized fields . Actually I already made such attribute myself, but it would be nice to have it built in

    Yeah wouldn't it be easier to just have cross scene references? Having GUIDReference<T> that has explicit Resolve() method is more obvious than prefab that can just spawn references out of seemingly nowhere at any time. Not to mention GUIDs wouldn't require rethinking serialization process, just add GUID to every Object instead, which unity does already.

    I also don't understand when does Prefab gets references. Should unity automatically check every referenced prefab or instead make Instantiate even slower by making prefabs check global references? In second case, non instantiated version will never have "scene" references then?
     
  3. DragonCoder

    DragonCoder

    Joined:
    Jul 3, 2015
    Posts:
    1,677
    Thank you for the feedback!

    That works within the current scene, I assume. What does it take as input for what to "get"? Or does it just find what fits the type? What if more than one thing fits?

    Why does this sound like seemingly nowhere? When you open the prefab you see clearly what objects you are referencing via their name. A tooltip could tell which scene it is exactly (or add it in brackets).

    No, it should happen on Scene loading. Every scene would have (internally) a list of all prefabs that need references from itself.
    Prefabs already need to be loaded somewhere in memory after all since otherwise you would not have a reference to them. So when the scene is loaded, Unity would go through the list of bound-prefabs, load them if necessary and then populate the fields accordingly.
    That would make scene loading a bit slower, yes, but only when you do actually have prefabs bound to that scene. That should not be a major impact compared to loading other resources on scene load.

    Hmm, effectively that would result in the same as my idea, except with less reliability/predictability of when the instance behind the GUID does actually exist.
    Not sure I understand how a GUID reference would be more obvious.
     
  4. Saniell

    Saniell

    Joined:
    Oct 24, 2015
    Posts:
    191
    It just calls GetComponent using field type, but at build time. By default at least, you could make user explicitly say GetType.SameObject or maybe rename attribute to AutoGetComponent? Dunno, my main reason to use it was to reduce spikes from Awake calls on scene load

    I see, that makes sense.

    Because GUID reference would still have to be resolved by you, instead of reference appearing on prefab by itself, its more explicit and I like it for it. If you want you can make a GUID->SceneName table at buildtime and log an error if user tries to resolve guid but required scene wasn't loaded.
    I agree that's very similar to your proposal, but GUID reference will work on anything and not just prefabs, which is nice!

    In my implementation, I don't allow setting GUID reference at runtime, which means one could also resolve all required GUIDs when building the game, avoiding having to assign id to every object there is

    Though to be honest I don't think Unity is going to make such a system because it breaks if you try to load same scene more than once.
     
  5. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    7,609
    If singletons are proving a problem then you may want to move up to a service locator pattern of sorts, so you can provide some more modularity for managers. Or in situations where 'everything needs to see everything else', implement a blackboard pattern system.

    I lean towards static classes for managers and anything 'global' first, so I can keep it outside of scenes, usually backed by a singleton scriptable object config asset.

    But the problem with the proposed idea, I think - which also applies to people wanting cross scene references - is that you can load the same scene multiple times. That alone often makes these kinds of ideas fall apart.

    In any case the easiest way to solve these problems is to just take it out of the scene. I like having less things to drop into a scene, and I never want to have to add a manager to a scene (which makes testing a lot easier). It all just lives in the pure C# world.
     
  6. DragonCoder

    DragonCoder

    Joined:
    Jul 3, 2015
    Posts:
    1,677
    Think that is sort of what I am meaning by a flat singleton system. Only having very few of them that provide the references to everything that's needed. That way those act like service locators and I use this mechanic in the unit tests where I replace the managers which are needed for the particular unit test.
    Only had to make sure that all unused references are set to null before every test so I do not happen to reuse one from a previous unit test.

    Hmm, interesting point. I mean my solution would not break down per se. Whenever you instantiate the prefab it will simply have the references from the last time the bound scene has been loaded.
    Think more often than not that would be what you want (if the scenes are for example levels) and it's predictable.
     
  7. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,614
    That was my thinking, exactly.

    The proposed idea is going to get super error prone in multi-scene scenarios. What happens if prefabs are instantiated when multiple scenes with bindings are loaded? Who thinks it'll be fun tracking down binding errors which are due to scene (un)load order? Is the complexity that will necessarily add any better than the problem it's meant to solve?

    On the UI front, how fiddly will it be to represent what value the field will have when other scenes are open? And when you have multiple scenes open how do you handle the various ambiguities in both displaying or assigning?

    All of this is just to solve just one problem, which in my experience hasn't really been a big one. It may mean a bit less code in that case, but overall is it less effort or work?

    Meanwhile, cross-scene references (and IDs) solve the same underlying problem as I understand it, without adding any ambiguity, with little complexity, and also help out in other areas (e.g. they're super handy for save game systems, or in multi-scene environments). They seem to be a simpler solution which gets more done with less.
     
  8. Saniell

    Saniell

    Joined:
    Oct 24, 2015
    Posts:
    191
    Well what if unity made it an official package? Those should be allowed to be not-so-general-purpose
    In fact Unity did make a prototype which worked for GameObjects only, it's currently on github
     
  9. DragonCoder

    DragonCoder

    Joined:
    Jul 3, 2015
    Posts:
    1,677
    Have replied to that above. Simply provide the reference of the instances within the last time the scene has been loaded.
    That works perfectly for the common usecase where your scenes are replayable levels.

    As far as I know unity GUIDs are not set in stone. You are not supposed to really rely on their numerical representation...
    Fixing that is probably a bigger problem than adding new functionality that works without them.

    Well, in my opinion yes since as a user of the system I can just learn it, even if it may be somewhat complex and do not have to implement my own boilerplate code etc.

    What are you referring to exactly?
     
  10. Saniell

    Saniell

    Joined:
    Oct 24, 2015
    Posts:
    191
    DragonCoder and angrypenguin like this.
  11. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,614
    They're not, you don't use that ID. The general approach is so common that Unity wrote a reference implementation, as @Saniell just linked.

    What about all the other use cases?

    I'd pick the boilerplate over managing the risk your solution introduces, any day. It could easily introduce hard to find bugs into any multi-scene setup.

    You can learn it, sure, but then everyone has ongoing work to make sure it doessn't break their stuff, even if they're not (intentionally) using it. And it'd make the Editor more cumbersome.
     
    Kurt-Dekker likes this.
  12. DragonCoder

    DragonCoder

    Joined:
    Jul 3, 2015
    Posts:
    1,677
    There you simply don't use it that way. Usability and flexibility (as in covering all usecases) is always a compromise.

    Am honestly not seeing that...

    Lol, with that mindset we could have remained using with Unity 3.0 (or whichever the first non-mac version was) ;)

    Oh well, if I had time I'd investigate whether my idea is doable without source code access to offer it as a plugin on the asset store...