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\$
  • \$\begingroup\$ You might want to look into scriptable objects. \$\endgroup\$ – Ethan The Brave Jan 8 '18 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\$ – Raphael Schmitz Jan 9 '18 at 10:25
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\$
  • \$\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\$ – Raphael Schmitz Jan 9 '18 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 '18 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\$ – Raphael Schmitz Jan 10 '18 at 17:46

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.