marcus
marcus

Reputation: 333

Unity - Creating a UI list at runtime programmatically

I have a scene where I should be able to select a 3D model from a list of items, which will then be loaded into the next scene. I want to generate the list of items programmatically, based on what is already stored in the local database. But I have problems getting the list of items to be displayed in the scene ...

So I have a class for the items:

[System.Serializable] public class Item { 
     public string ObjectGuid;
     public string Name;
     public string Description;
     public Sprite Icon;
     public Button.ButtonClickedEvent SelectObject;
}

A script for my scene, that contains a list of items and a method that I call to load the objects from the database:

public class ObjectSelectionScript : MonoBehaviour {
public List<Item> itemList;
public Transform contentPanel;

void LoadObjects() {
     StreamReader str = new StreamReader (Application.persistentDataPath + "/" + DataManager.LOCATION_XML_PATH);
     string result = str.ReadToEnd ();
     str.Close();
     var jo = SimpleJSON.JSON.Parse (result);

     if (jo ["Objects"] != null) {
         for (int i = 0; i < jo["Objects"].Count; i++) {
             if (!string.IsNullOrEmpty(jo["Objects"][i]["FileGuid"])) {
                 Item newObject = new Item();
                 newObject.ObjectGuid = jo["Objects"][i]["ObjectGuid"];
                 newObject.Name = jo["Objects"][i]["Name"];
                 newObject.Description = jo["Objects"][i]["Description"];
                 //newObject.SelectObject = new Button.ButtonClickedEvent();
                 itemList.Add(newObject);
             }
         }
     } 
 }

A ModelObject class:

 public class ModelObject : MonoBehaviour {
     public Button button;
     public Text Name;
     public Text Description;
     public Image Icon;
 }

And a prefab ModelObjectPrefab that contains Name, Description, a Thumbnail, added to my assests folder.

The PopulateList() method is my problem. I can't get to instatiate the prefab and then create ModelObjects that I can throw in the contentPanel in the interface. This is my code:

void PopulateList()
{
     try {
         foreach (var item in itemList)
         {
             GameObject newModel = Instantiate(ModelObject) as GameObject;
             ModelObject myModelObject = newModel.GetComponent<ModelObject>();
             myModelObject.Name.text = item.Name;
             myModelObject.Description.text = item.IDescription;
             myModelObject.icon.sprite = item.Icon;
             myModelObject.button.onClick = item.selectObject;
             newModel.transform.SetParent(contentPanel);

         }

     } catch (System.Exception ex) {

     }
 }

I currently get null pointer on instantiate. I have tried Resources.Load, and variations of the Instatiate method, but I did not find a solution.

Can anyone give me a hint what is the problem? How should PopulateList() look like to get the items to the interface?

Upvotes: 2

Views: 6203

Answers (2)

marcus
marcus

Reputation: 333

Note for readers. "GUI" is no longer available in Unity. Simply use the new UI system. Example http://www.folio3.com/blog/creating-dynamic-scrollable-lists-with-new-unity-canvas-ui/


The only thing that did work was to create the whole GUI programmatically. I created a GUI.matrix and added everything inside it. It did not look very good, so I spent a lot of time trying to make it look better and to be responsive on mobile devices, but in the end this was the only solution that worked.

Just as a quick reference for who ever might have the same issue, the solution has this structure:

public void OnGUI ()
{
var savedMatrix = GUI.matrix;
scrollPosition = GUI.BeginScrollView (new Rect (10f, 10f, Screen.width - 10f, Screen.height - 10f), scrollPosition, new Rect (10f, 10f, Screen.width - 10f, (_files.Count + 2) * 200f));
        if (Input.touchCount == 1) {
            Vector2 touchDelta2 = Input.GetTouch (0).deltaPosition;
            scrollPosition.y += touchDelta2.y;
        }
        var selected = GUILayout.SelectionGrid (gridInt, _files.ToArray (), 1, MyStyle);
        if (selected >= 0) {
            if (selected == 0) {
                if (selectedItem != null )
                    if (selectedItem.Parent != null)
                        selectedItem = selectedItem.Parent;
                else 
                    selectedItem = null;
            } else {
                SelectFolder (selected);
            }

            RefreshViewModel ();
        }
        GUI.EndScrollView ();
        GUI.matrix = savedMatrix;
}

Upvotes: 0

Serlite
Serlite

Reputation: 12258

Note that the Instantiate() method should not be given a type (eg. ModelObject) as an argument. Rather, you should be passing it an object instance (in this case, your prefab GameObject "ModelObjectPrefab"). The documentation specifically indicates that it requires:

An existing object that you want to make a copy of

Otherwise, you're probably going to get unexpected behaviour like this. In your code, we need to pass a reference to "ModelObjectPrefab" as an argument of the Instantiate() call. I would suggest modifying your class containing PopulateList() to look like:

// New variable to hold prefab object
public GameObject modelObjectPrefab;

// ...

void PopulateList()
{
     try {
         foreach (var item in itemList)
         {
             // Reference prefab variable instead of class type
             GameObject newModel = Instantiate(modelObjectPrefab) as GameObject;
             ModelObject myModelObject = newModel.GetComponent<ModelObject>();
             myModelObject.Name.text = item.Name;
             myModelObject.Description.text = item.IDescription;
             myModelObject.icon.sprite = item.Icon;
             myModelObject.button.onClick = item.selectObject;
             newModel.transform.SetParent(contentPanel);

         }

     } catch (System.Exception ex) {

     }
}

Then, in the Editor window, drag your prefab "ModelObjectPrefab" from the assets panel onto the new Model Object Prefab field in the script properties, to form the connection between the script and the prefab.

Hope this helps! Let me know if you have any questions.

Upvotes: 1

Related Questions