0
\$\begingroup\$

Task

Display a UI list


Assumptions

  • Most UI parts only get their data during runtime
  • UI parts need to be styled in the editor (= before runtime)
  • UI editing should be done with real time preview in the editor
  • The items in UI list should look the same

Current solution

  • Save a list item prototype as a prefab (drag&drop in the editor)
  • Set that prefab as the reference in the script managing the list
  • During runtime, instantiate the referenced prefab as needed

However, this needs a prefab file, which seems like an unnecessary additional point of failure. Just one annoying example: More often than not I forget to click "Apply" after editing such a prototype - which only becomes apparent at runtime.


Suggested solution

  • In the script managing the list, set the list item prototype as a reference
  • On Awake(), create a prefab and store it in a class field
  • During runtime, instantiate the prefab from the class field

Question

I got the suggested solution working withPrefabUtility.CreatePrefab(). The only issue with that is that it still saves a prefab file. I currently just save those to a temp directory, but that is a workaround.
Is it possible to have a prefab which does not exist as a file, but still allows copying with Instantiate()?

\$\endgroup\$
2
  • \$\begingroup\$ You might want to look into scriptable objects. \$\endgroup\$ Jan 8, 2018 at 20:34
  • \$\begingroup\$ I never really worked with scriptable objects. I thought I knew what they do, but I have no idea how that would allow me to instantiate prefabs without using a prefab file. \$\endgroup\$ Jan 9, 2018 at 10:25

1 Answer 1

1
\$\begingroup\$

You can Instantiate any old GameObject you want (or more broadly, anything deriving from UnityEngine.Object) — it doesn't have to be a prefab for this to work.

So one possibility would be to have a source GameObject in your scene that you set to inactive, using SetActive(false), so it doesn't render or update, and spawn copies of it with Instantiate, remembering to re-enable the copies with SetActive(true)

Here's a way you can keep track of this to minimize any risk of misuse:

public class PseudoPrefab : MonoBehaviour {
    // You can provide overrides for the various flavours of Instantiate,
    // taking position & rotation or world transform flags.
    public GameObject Instantiate(Transform parent) {
        // Clone ourselves, including the GameObject we're attached to.
        var clone = Instantiate<PseudoPrefab>(this, parent);

        var go = clone.gameObject;
        go.SetActive(true);

        // Strip off the PseudoPrefab script from the spawned copy.
        Destroy(clone);

        // Return a ready-to-use instance.
        return go;
    }

    void Start() {
        // Escape to the root, so we're not accidentally found
        // when traversing a particular container's children.
        transform.parent = null;
        gameObject.SetActive(false);
    }
}

Now your script that uses these items in a list can look something like this:

public class ListManager : MonoBehaviour {

    public PseudoPrefab listItemTemplate;
    public Transform listContainer;

    List<GameObject> _activeItems = new List<GameObject>();

    void AddItemToList() {
         _activeItems.Add(listItemTemplate.Instantiate(listContainer));
    }
    // ...
}

Your pseudo-prefab never lives in the same collection as your actual spawned instances, so you don't need to worry about accidentally mutating/deleting it when traversing the active copies.

\$\endgroup\$
3
  • \$\begingroup\$ Most list updates are destroying every list item and respawning them. I normally loop through the transform children and destroy them. The way you described, I would always need to make sure not to delete the template somehow. Which is possible with a reference, but then I have more points of failure than with a prefab. That is one of the reasons why I'd prefer a field-only GameObject blueprint. \$\endgroup\$ Jan 9, 2018 at 10:58
  • \$\begingroup\$ I've included an example above that shows one way to avoid this type of misuse structurally. You could even eject your pseudo-prefab to a completely separate scene at startup, so that no stray iterations anywhere in your scene could catch it by accident. \$\endgroup\$
    – DMGregory
    Jan 9, 2018 at 16:03
  • \$\begingroup\$ This is not what I wanted to hear, but it seems like the currently best way to achieve the target, so I'm marking this as the answer. \$\endgroup\$ Jan 10, 2018 at 17:46

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .