I spent most of the last two days getting a working Combo Box (aka Drop Down List) working in uGUI. However, I had some specific requirements for it that made this a lot harder than simply building all the objects and hooking them up. I’ll go over these requirements and why I think they are important below, then post the code at the bottom of this message along with an example project. The code is still pretty rough, as I’d like to improve some of the abstractions, but will likely be useful if you want to wrap your controls this way. This post is going to be long, but hopefully worth your time.
My requirements:
- The user should be able to create a combo box as a single item in their UI, not as 20+ objects
- The coder should have a single point of access for everything about the combo box and not have to worry about the objects it’s made from.
- The entire system should be stylable by the UI artist, without coder intervention.
- The styling should update automatically when the style is changed where ever it is used.
- You should be able to prefab the UI which contains the combo box.
- Any type of item should be able to be used in the combo box - want a list if images with text, a list of textures, or something else? No problem.
Many of these requirements would be met by having a better encapsulation system for Unity, which I’ll discuss later.
Here is what a combo box looks like in Unity.
Notice that it’s made from a very large number of objects. This is problematic for a number of reasons. First, it’s very time consuming to make all of this by hand and get them all working well together. Second, any programmer who needs to work with data in these components will have to have some way of finding them all first, or a giant list of references for the artist to fill out. Both of these options are extremely brittle, and IMO, not scalable to a large production.
Lets put that aside for a second and talk about reuse. I should prefab this so I don’t have to create it again - and then users can just drop them into the scene and just use them. The problem with this is that Unity does not support nested prefabs - so if I have a panel that needs to appear on every screen and prefab it, then if I change the combo box prefab it will not update in that scene.
So this is what I set out to solve.
How it works for the UI Artist:
When they want to create a combo box in the game they go to GameObject->UI->Combo box and it will create a new combo box object under the currently selected uGui element. This component has three fields for the prefabs described below. As soon as you fill them out, it will create the combo box for you, hiding it’s guts so it looks like a single item in the hierarchy. The prefabs act as the styling for the combo box - note that they aren’t part of the scene in the classical sense (they are created on start, etc)
How it works for the coder:
To populate the combo box with items, you can do one of the following:
// populate the combo box with text
comboBox.AddItems("Test1", "Test2", "Test3", "Unity", "Needs", "A", "Better", "Encapsulation", "System", "Than", "Prefabs");
// or, populate it with textures
comboBox.AddItems(myTex1, myText2);
// or populate it with both
comboBox.AddItems(StyledComboBox(new StyledItemButtonImageText.Data("MyString", myTex1));
// finally, listen for changes:
comboBox.OnSelectionChanged += delegate(StyledItemitem)
{
Debug.Log (item.GetText() + "" + comboBox.SelectedIndex);
}
The coder essentially only works with the StyledComboBox component, setting it’s items, getting the result, etc. They don’t have to worry about the items being created/destroyed/placed/managed at all.
How the UI artist styles the control:
The UI artist sets up a prefab for the combo box and add’s the ComboBoxPrefab template to it (or just modifies mine). This template has properties to link several things the system wants to know about; what rect to place the items under, which panel to toggle alpha on when the user opens or closes the combo box, and where to put the version that shows up in the menu.
The UI artist then creates a second prefab for how an item looks in this menu, and adds a StyleItem component to it - StyleItem is a base class, and theirs currently one usable subclass (StyledItemButtonImageText), but it’s easy to add new ones - this acts as the abstraction layer so the combo box doesn’t really have to know about it’s contents. For instance, they could make a version which has a button, an image, and a text in it, and arrange them however they should look using the current control, or a code could create a new StyledItem subclass with any number of controls inside of it.
They can optionally create a third prefab if they want the version which appears as the main control (currently selected item) to look different than the regular items in the list, but in most cases they don’t need this.
How Unity could provide better encapsulation and scale their engine better…
One of my major issues with Unity is the lack of proper encapsulation. Even if nested prefabs were to work correctly, I don’t think the system provides enough encapsulation. The code posted below is a work around to provide the level of encapsulation I feel is necessary for a project to scale. It takes a complex system of interconnected objects, encapsulates them into a single object, and provides a single, unified API for the system.
What I would like to see unity provide is a way to have a “closed prefab”. This is essentially a prefab that hides all of it’s children and data from the world, while allowing the person who sets up that prefab to provide a single API to work with it.
The workflow would go something like this, using our combo box as an example, but could be equally applied to any reasonably complex game object network.
- The creator of the asset would create the combo box prefab in the same manner they do now.
- The creator would mark the prefab as closed, which would hide all the subobjects and show them an “edit” button. Pressing edit would show all of the hidden objects for them to make changes.
- While in the edit mode, the user would be able to right click on any field in any sub-object and select expose, typing in a new name for the field’s alias. This would then show up as a field on the top level prefab object for the user to edit, or the code to set.
With this, you can create a prefab, hide it’s guts, and expose only the data they want the user to be able to change, providing a single place to address the data, with a narrow aperture. More importantly, there’s no giant list of pointers to fill out, or Find(“someobject”) in the code which breaks every time someone renames something.
The Code
The code is attached to this thread. It can likely be improved considerably, as I’m currently doing manual placement because I couldn’t get the layout components to do what I expected to be able to do with them. I welcome suggestions to make the system better, or if people would like to work with me on wrapping some other common controls like this that would be wonderful. I hope this is useful for someone else, enjoy!
1746901–110363–uGUI_ComboBox.zip (17 KB)