Friday, July 31, 2015

Unity UI - Dynamic Buttons and Scroll View

When building a UI, it's sometimes helpful to be able to dynamically create buttons depending on a number of objects. Unity's UI is easy to work with, but can be tricky to use if you're trying to build it dynamically. In this tutorial, we'll create buttons for a list of names and add them to a scrollable window so that we can accommodate any number of buttons.





 

We will create a simple list of buttons that can be dragged up or down. This feature is a nice way to save space if you have a large list that needs to be displayed.  

 

Example scripts we'll be using:  Scroll View Scripts




Begin by creating a Canvas


Add a Button and an Image to the Canvas
Resize and reorganize your button and image to look something like this. I changed the button text to Scroll View

Now that we have the basic shape of our scroll view window, you will need to duplicate the Image Object and we will add the Scroll Rect and Mask components.


I renamed the Image to ScrollView also note that I deselected Horizontal in the Scroll Rect Component

The Mask component is used to hide objects that do not fit within the space covered by the Image component. We can change the size of the mask by changing the size of the image if we so desire. The Scroll Rect component allows us to click and drag UI objects, but in order to do this we will need to have a Rect Transform to populate the "Content" field.

Create an Empty GameObject and make it a child of the ScrollView. Here I named mine: ScrollContent
We will also need to create a button and make it a child of the ScrollContent. As you can see in the image above, I repositioned it to fit nicely underneath the Scroll View. Next we will add a Content Size Fitter and Grid Layout Group component to the ScrollContent object.

You may need to reposition your button to fit nicely underneath the Scroll View as the Grid Layout Group might reposition it. Be sure to change the settings as shown above.
The Content Size Fitter will alter the size of the ScrollContent GameObject as we add more buttons in the future. The Grid Layout Group will ensure that the new buttons we add on the fly will be placed in an ordered fashion.

Return to the ScrollView component and drag our ScrollContent object into the Content field

Now if you hit the Play button, you should see your Scroll View window with a single button, placed awkwardly in the middle of the panel.


You should see something like this when you hit play

Return to the ScrollContent object and change the pivot from 0.5 to 1

Now when you hit Play, you should see the ScrollContent is placing its child buttons, starting from the top instead of the middle
At this point we can test our functionality by creating some extra buttons. If you hit Play and go to the Button object in the inspector (Note: You will need to make sure you do not have Maximize on Play selected) you can make duplicates of the button and watch them get added to your scrollable window.

As I duplicate the Button in the Inspector, they are added to the ScrollView
The final step is understanding how this could be used inside a game, to do that we will need to use our two scripts: Tutorial_Button and Tutorial_ScrollView

First add the Tutorial_ScrollView script to the ScrollView GameObject, then add the Tutorial_Button script to the Button GameObject.

Add the Button to the Button_Template field
Add the Text object to the Button Text field and drag the ScrollView to the Scroll View field
Configure the Button Script on the Button GameObject, by dragging the Tutorial_Button script into the On Click() event. Then in the drop-down, choose the Tutorial_Button.Button_Click function as shown above. This will send a notice to our Tutorial Button script when the button is clicked.


Lastly, disable the Button GameObject in the Inspector
Now if we run it, we should see our Scroll View populated by a list of names. You should be able to drag the list up and down to view the entire list of names. Clicking on one of the names will create a Debug.Log text of the name that was clicked.

Clicking buttons will call a function in the Tutorial_Scroll View script which creates Debug text

using UnityEngine;
using UnityEngine.UI;
using System.Collections;

public class Tutorial_Button : MonoBehaviour {

 private string Name;
 public Text ButtonText;
 public Tutorial_ScrollView ScrollView;

 public void SetName(string name)
 {
  Name = name;
  ButtonText.text = name;
 }
 public void Button_Click()
 {
  ScrollView.ButtonClicked(Name);

 }
} 
The Tutorial_Button script is placed on our Button. The Button in our scene acts as a template and the Tutorial_Scrollview script will create multiple buttons based on it. They will be identical, except for the Name that they store. When each button is created, a unique name is given to it which it will display via its Text object. The buttons wait for a call to Button_Click() and then notify the ScollView, passing their name as an argument.



using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class Tutorial_ScrollView : MonoBehaviour {

 public GameObject Button_Template;
 private List<string> NameList = new List<string>();


 // Use this for initialization
 void Start () {
 
  NameList.Add("Alan");
  NameList.Add("Amy");
  NameList.Add("Brian");
  NameList.Add("Carrie");
  NameList.Add("David");
  NameList.Add("Joe");
  NameList.Add("Jason");
  NameList.Add("Michelle");
  NameList.Add("Stephanie");
  NameList.Add("Zoe");

  foreach(string str in NameList)
  {
   GameObject go = Instantiate(Button_Template) as GameObject;
   go.SetActive(true);
   Tutorial_Button TB = go.GetComponent<Tutorial_Button>();
   TB.SetName(str);
   go.transform.SetParent(Button_Template.transform.parent);

  }


 }
 
 public void ButtonClicked(string str)
 {
  Debug.Log(str + " button clicked.");

 }
} 
The Tutorial_Scroll View script creates a list of names (that I picked arbitrarily) and instantiates a button for each. Here we use the Button we created in our scene as a template for creating the other buttons dynamically. For each new button that's created, we enable it and grab the Tutorial_Button component off it so we can set its name. The script then waits until the ButtonClicked function is called and prints out the name it receives. Don't forget you can update the format:

Green and black looks very Sci-Fi


I've found this type of approach to be very useful, especially if you're working with a large list of data. I used names here as a simple example, but the approach could be extended to work for things like Items, Quests, Abilities, Attributes, etc. For example, in the project I'm working on, Codenamed: Story Strategy, I created the character select screen using this type of Scroll View.

Because there are a lot of Characters to choose from, having a scrolling list makes sense to save space

Well that's all for this tutorial, leave a message if you liked it or have any questions!


11 comments:

  1. This seems to work great, and I'm glad you added in how to use this dynamically at run time. A+

    ReplyDelete
  2. I am doing training where my script for loops instruction text instead of buttons.
    I am new so may need some help.
    I'll spend time trying and post results or questions...

    ReplyDelete
  3. I loaded your project and the buttons being added are not visible.
    They are there: clicking where they should be prints the debug info,
    and the scrolling works.
    Only things I saw was the dialog about this being created in a different
    Unity Editor version.
    I'm using 5.4.1f1

    ReplyDelete
  4. Great~~~!! Thanks to you, the problem is solved.

    ReplyDelete
  5. Fantastic! Thank you so much for this. If people knew this existed, then you would probably get a lot more traffic here. Great tutorial!

    ReplyDelete
  6. This comment has been removed by the author.

    ReplyDelete
  7. what if i want to scroll with buttons instead of drag?

    ReplyDelete
  8. This comment has been removed by the author.

    ReplyDelete
  9. PLZ HELP ME!

    I making the editor, but something is wrong:


    ScrollLister.cs:
    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    using System.Threading;

    public class ScrollLister : MonoBehaviour {

    public GameObject content;
    public GameObject itemSample;
    public List objectPool;
    // Use this for initialization
    void Start () {
    itemSample.SetActive (false);
    }

    /*
    // Update is called once per frame
    void Update () {

    }*/

    public GameObject cloneSample(){
    GameObject sample = (GameObject)Instantiate (itemSample);
    sample.SetActive (true);
    sample.transform.SetParent (itemSample.transform.parent);
    Debug.Log ("clone was return");
    return sample;
    }

    public void addItem(GameObject addedItem){
    objectPool.Add (addedItem);
    reloadList ();
    Debug.Log (objectPool);
    /*
    float height = 0f;
    float width = 0f;

    foreach (GameObject item in objectPool) {
    ((RectTransform)item.transform).offsetMax = new Vector2(((RectTransform)item.transform).offsetMax.x, height);
    if(((RectTransform)item.transform).rect.width>width)
    width = ((RectTransform)item.transform).rect.width;
    height += ((RectTransform)item.transform).rect.height;
    }
    Rect rect = ((RectTransform)content.transform).rect;
    rect.width = width;
    rect.height = height;
    //(GameObject)Instantiate (addedItem);*/
    }

    public void reloadList(){
    float height = 0f;
    float width = 0f;
    foreach (GameObject item in objectPool) {

    if(((RectTransform)item.transform).rect.width>width)
    width = ((RectTransform)item.transform).rect.width;
    //((RectTransform)item.transform).offsetMax = new Vector2(width,((RectTransform)item.transform).offsetMax.y);//((RectTransform)item.transform).offsetMax.y

    height += ((RectTransform)item.transform).rect.height;
    }
    Rect rect = ((RectTransform)content.transform).rect;
    rect.width = width;
    rect.height = height;
    Debug.Log ("List was reload completed!");
    }
    }

    And in the main script I clone and add a object into the content
    void Start () {
    blocksMenuLister = blocksMenu.GetComponent ();
    GameObject onStart = blocksMenuLister.cloneSample ();
    onStart.transform.Find("Text").gameObject.GetComponent().text = "onStart";
    onStart.SetActive (true);
    blocksMenuLister.addItem (onStart);
    blocksMenuLister = blocksMenu.GetComponent ();
    GameObject update = blocksMenuLister.cloneSample ();
    update.transform.Find("Text").gameObject.GetComponent().text = "Update";
    update.SetActive (true);
    blocksMenuLister.addItem (update);
    //blocksMenuLister.addItem ();
    /*
    blocksMenuLister.itemSample = blocksMenuSample;
    blocksMenuLister.content = blocksMenuContent;


    GameObject onStart = blocksMenuLister.cloneSample ();
    blocksMenuLister.addItem ();*/
    }

    then it doesn't works!, the button's position is out from the content,so when the game is running, I can;t see the button in scrollList.Plz HELP ME!

    ReplyDelete
  10. Hi
    I used this.thanks it was very useful for me.

    ReplyDelete