4

I asked this question in a few places, and still haven't figured it out completely yet, so maybe some smart people here will have an idea how to approach that.

What's the best way of maintaining a deep nested hierarchy of prefabs in a project? Say, we have a few GUI screens, made of smaller, generic components. For the sake of simplicity, let's call the former "views", and the latter - "components". Views deal with semantics (e.g. inventory view, store view); components are configurable, but have no business logic attached to them whatsoever (for instance, a button cares only about its OnTap callback/event handler).

Both views and components can be nested.

GUI views need to be reused across multiple scenes.

The main issue with this approach in Unity is the fact that any nested hierarchy, once turned into a prefab, loses references to its children prefabs, like in this (completely made up, yet still valid) example:

- storeView - UIViewHeader - UIHeaderLabel<Text> - UIList - UIListItem - UIThumbnail - UITitleLabel<Text> - UISubTitleLabel<Text> - UIPrimaryButton<Button> - ...

I'd like to keep all of these, small UI* components in separate prefabs, but also keep the storeView prefab so I can easily add it to different scenes. Unfortunately, as soon, as we create the storeView prefab, all of the UI* prefab references are lost. Given that, we could try a different approach, in which instead of having a storeView prefab with content, we keep it empty and pick one of these few options:

  1. attach a behaviour to storeView and load child prefabs during run-time
    • cons: makes designer workflow more complex, puts the complexity in the script, which might be more error prone from the dev perspective too
    • pros: makes it easier to reuse the storeView between scenes, component prefabs can be styled, modified globally
  2. keep the storeView as an empty prefab, and reassemble it in every scene that requires it
    • cons: components need to be wired up manually, it's still easy to save the entire storeView accidentally and lose the prefab references
    • pros: guarantees that component prefab references are maintained, allows for slight differences between views (imho that's an issue actually, since these should belong to the configuration layer)
  3. save the entire storeView as a prefab
    • cons: scales terribly, makes iterating on new features or small UX changes more time consuming (additional QA, acceptance tests, etc...)
    • pros: it's a quick and dirty solution that works well with small projects
  4. use Prefab Evolution or similar
    • cons: I assume the package will be rendered obsolete by Nested Prefabs, which are on the roadmap? Requires depending on 3rd party code and might not be flexible enough (any opinions here, guys?)
    • pros: From many (mixed) reviews I've seen, some have been quite positive. The more complex a project gets, the less positive these reviews are, however.
  5. write a bunch custom Editor scripts:
    • cons: time consuming, also - seems like something that should be provided by the platform, even if it deals mainly with games
    • pros: complete control over the behaviour, can be improved by developers with designers' feedback. One might argue that being disciplined about having implementation, tests and designer-friendly tools as feature requirements sounds like a good design practice (resulting in less technical debt, easier maintenance)

Here's my personal, v. idealistic, unrealistic solution to that*:

Use a componentised architecture, where child prefab references are stored in complex prefabs by default. Internally think of them as UIView and subviews on mobile (Cocoa), or component classes in React or better, functional components - React/Cycle/Elm/anything that goes well with FRP (yes, I know these approaches differ in so many ways, but the key is composability, achieved either by functional composition, decorators, et cetera, et cetera...).

  • cons: I assume that my difficulties here come from my lack of experience with Unity and there's a more obvious, perhaps idiomatic solution (which I'm kindly asking for:) )
  • pros: makes testing and iterating on new features way easier, makes prefabs way more powerful, whilst not losing any of their benefits (please correct me if I'm wrong, I'm still getting familiar with Unity)

  • please don't think I'm expecting all of that from Unity, but that's one of the possible directions even if 1% of that is true.

Just to make it clear, it's not a rant regarding Unity, as a developer working previously with mobile (native) and web, I find it impressing how many of my problems it solves, and how ridiculously simple some of these solutions are.

Rafal Pastuszak
  • 3,100
  • 2
  • 29
  • 31

1 Answers1

1

I'd probably maintain the composite views in separate Scenes. Simply consider these scenes as they were prefabs, containing only a single prototype object of the given class.

Using SceneManager.LoadScene with LoadSceneMode.Additive you could even create some static factory method, like:

public class UIStoreView
{
    public static UIStoreView Instance()
    {
        SceneManager.LoadScene("UIStoreView", LoadSceneMode.Additive);
        return GameObject.Find("UIStoreView");
    }
}

With some naming convetion you can achieve something as simple as storeView = UIStoreView.Instance();.

Not a universal / scalable stuff, but at least you have something lightweight / maintainable until they roll out nested prefabs (timeline uncertain as they say).

Geri Borbás
  • 15,810
  • 18
  • 109
  • 172
  • Yeah, I noticed that it's one of the few permanent residents of the "development" tap on their roadmap. – Rafal Pastuszak Dec 06 '16 at 12:20
  • (sorry, pressed ENTER accidentally and submitted just the first sentence) However, this approach seems to raise a few concerns: - seems non-idiomatic, hacky - involving possible performance issues (there's a price paid for loading prefabs vs. scenes) The first issue is not as important as usual, since I'm starting to think that an idiomatic solution might not exist at all, I'll wait a few days and if there's no other sensible answers I'll mark this one as the solution. Cheers. – Rafal Pastuszak Dec 06 '16 at 12:27
  • Yap, probably some performance is to be paid for some flexibility. However, a scene file is just really **a simple serialized YAML asset**, just as the prefabs. If you think of games like Subway Surfers, they are continously using `LoadSceneMode.Additive` to stream the map for the eternity. – Geri Borbás Dec 06 '16 at 15:17
  • It feels less hacky to me than other approaches, as the composite prototype scenes can be stored nicely, named after the component itself, while this also uses Unity's own natural mechanisms to keep prefabs updated. – Geri Borbás Dec 06 '16 at 15:21
  • Have done a lot of iOS development, so this issue was shocking to me as well when it raised in my Unity projects. :D My solution was to not use nested hierarchies in production, but maybe if this idea striked me back then, I'd gave it a try (with profiling performance cost). – Geri Borbás Dec 06 '16 at 15:22
  • 1
    Not using nested hierarchies sounds like pushing the complexity somewhere else instead of minimising it (I'm doing this too, but sometimes it results in having to compose complex views manually - using scenes as poor man's nested prefabs seems to mitigate this issue tho). btw, I think there's an issue with the factory method provided (assuming one wants to remove the GUI scene once its views are added). [Something along these lines](https://gist.github.com/paprikka/eca94da83f4f5fed42a2d317ac54f2ad) works in my case. – Rafal Pastuszak Dec 06 '16 at 17:17
  • `SceneManager.UnloadScene("UIStoreView");` won't work if called instantly after LoadScene (Unity 5.4.2). That's why I'm loading scenes asynchronously (UnloadAsync is available in 5.5 though) [docs](https://docs.unity3d.com/550/Documentation/ScriptReference/SceneManagement.SceneManager.UnloadSceneAsync.html) – Rafal Pastuszak Dec 06 '16 at 17:21
  • I don't get every detail without context, seems quiet promising anyway! Would be interested in some profiler details (memory / performance footprints). – Geri Borbás Dec 06 '16 at 20:52