2
\$\begingroup\$

I've a list of buttons that I create using XML stored data. To achieve it, I've created a Prefab. These buttons are created correctly, and now I want them to be sorted for the user.

public List<GameObject> myButtons;

As I said, now I want to sort them. I've found out that sorting the List itself, buttons are sorted in the UI.

myButtons.Sort((x, y) => ...);

The problem is, that I don't have the necessary data to sort them within them. This leaves the .Sort() method unavailable for use.

I've tried to sort them adding an script to each one, which contains the data that I need to use to sort them. However, it doesn't work correctly and I seem unable to find out why, so I'm wondering about other more direct methods to sort them.

Notice that I'm referring to simple buttons in a 2D game, and that they're all inside the same parent/gameobject.

enter image description here

Technical Details

Let's start by the basics: I want to order the buttons taking into account if the user has reached a point in which the item it represents should be available.

This is stored in an instance of a class that represents the items related to the buttons of the question.

public class myItemClass
    {
        # Here there are other properties
        public bool available;
    }

Therefore, we've a correspondence between two lists:

public List<myItemClass> myItems;
public List<GameObject> myButtons;

Where:

myItems[0] is related/represented by myButtons[0]

...

NOTICE that this relation exist at the initialization of the game, so i need to keep it at any moment (meaning, having to sort both lists).

What I tried

To be able to order my buttons depending on if the items are available or not, what I've done is:

  1. Creating an script myItemScript, which has a reference to my original items from the other list.
  2. Sorted the items using Sort function over them.
public class myItemScript: MonoBehaviour {
    public OtherClass.myItemClass dataObject;
}

And then sorting them like:

myButtons.Sort((x, y) => y.GetComponent<myItemScript>().dataObject.available.CompareTo(x.GetComponent<myItemScript>().dataObject.available));

As I said before, both list need to be following the same order, so just after it, I order the item list as well:

myItems.Sort((x, y) => y.available.CompareTo(x.available));

Checking/Debug

As result, I found that items were in fact sorting different. However:

  • Items inside myItems List were sorted as expected.
  • Buttons in UI (and hence, myButtons List were not sorted as expected. Mixed items (available = true/false) were mixed and not ordered in two blocks, one first and then the other one.

This was checked using breakpoints within VS2017 and pausing the game in Unity interface.

\$\endgroup\$
  • 1
    \$\begingroup\$ Though it can be done with Transform.SetSiblingIndex, the question remains: how to do it optimally? \$\endgroup\$ – trollingchar Aug 9 '19 at 16:32
  • 1
    \$\begingroup\$ "I don't have the necessary data to sort them" what data does define their sort order, and where can we find it? "I've tried to sort them adding an script to each one, which contains the data that I need to use to sort them. However, it doesn't work correctly" Show us the script you wrote if you want our help correcting it. Be sure to include a full description of what output it should produce versus what output you're observing instead. \$\endgroup\$ – DMGregory Aug 9 '19 at 16:33
  • \$\begingroup\$ @trollingchar isn't this function only to work with z-index drawing position? \$\endgroup\$ – Btc Sources Aug 9 '19 at 16:59
  • \$\begingroup\$ @BtcSources if you have a layout component, it should work. But it seems to me that it will lag \$\endgroup\$ – trollingchar Aug 9 '19 at 17:12
  • \$\begingroup\$ Hi again @trollingchar. After implementing Gregory solution, I found that sorting the list with this method (or any other swaping "manually" the items), did not solve my problem. However, using the function you suggested, effectively solved the problem. How to do it optimally is my problem, but could you please post it as an answer to accept it? Thanks \$\endgroup\$ – Btc Sources Aug 17 '19 at 16:31
0
\$\begingroup\$

To change the order of UI elements, you can use Transform.SetSiblingIndex. Be careful though, if Unity uses arrays to store child elements, changing their order will be an expensive operation.

| improve this answer | |
\$\endgroup\$
0
\$\begingroup\$

It looks like you're sorting using a boolean value as your sort key. That means many items in your list will compare as equal (true == true and false == false)

When two items are equal, an unstable sort like List.Sort(Comparison<T>) is allowed to put them in any order. And it's allowed to make different decisions from one run to another (this nondeterminism is important for getting good performance from algorithms like QuickSort)

So it could put all the true items and all the false items together in both the list of items and the list of buttons, but use a different order of items within the true partition or within the false partition.

It's also strictly more work than you need. You can sort all the true items to the start of your array in linear time, deterministically:

int SortAvailableFirst(List<Item> items, List<GameObject> buttons) {
    int firstFalseIndex = 0;
    for(int i = 0; i < items.Count; i++) {
        var item = items[i];
        var button = buttons[i];
        if(item.available == false)
            continue;

        items[i] = items[firstFalseIndex];
        buttons[i] = buttons[firstFalseIndex];
        items[firstFalseIndex] = item;
        buttons[firstFalseIndex] = button;
        firstFalseIndex++;
    }

    // Return the number of available items.
    return firstFalseIndex;
}

This maintains the order of the available items, putting them at the front of the list. It does scramble the order of the unavailable items, but it scrambles them the same way when given the same sequence of trues & falses — it makes no random choices.

Or, with a little extra effort you can maintain the order of both true and false items.

Or, another way to get the order consistent is to sort your items first, then spawn your buttons in order by walking over the list of already-sorted items.

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ Thanks for your answer. A few comments: Appreciated point about performance; appreciated point about deterministic output. Cons: doesn't solve my problem, gives the exact same output for me than the Sort function used before. This means that my error is elsewhere.. I'll have to further debug it. I'll accept your answer, but I think that you should modify the code to sort both lists in the same execution, so it does sort the Gameobject list and hence "answer" the question title. \$\endgroup\$ – Btc Sources Aug 10 '19 at 13:42
  • \$\begingroup\$ Hi again. Although it seemed to work initially, sorting the list with this "manual" method of swaping items, the result isn't reflecting in the UI. Thus, it doesn't solve the problem. Please see my new comment in the question, and thanks for your time! \$\endgroup\$ – Btc Sources Aug 17 '19 at 16:32
  • \$\begingroup\$ Presumably you tried re-ordering your transforms to match the list order, using eg. SetSiblingIndex? \$\endgroup\$ – DMGregory Aug 17 '19 at 17:19
  • \$\begingroup\$ I didn't. As I said in the question, I found that sorting the list using Sort function over the list, made the transforms to be sorted in order as well, without having to use these kind of functions (which is something that surprised me). Switching from this List function to your proposed idea or others to sort manually the lists, couldn't achieve this "automatic sort". Maybe my question wasn't clear enough, but I didn't know that I needed to sort the transforms, maybe this would fit more the kind of answer that I needed. \$\endgroup\$ – Btc Sources Aug 17 '19 at 17:23
  • 1
    \$\begingroup\$ Sounds like you should make an effort to order your transforms then. Your transforms don't know anything about your list, so they can't change their order to match the list. What you were seeing before was probably due to sorting the list then constructing your buttons by walking over the already-ordered list, which means they're created in the right order, but they don't change positions if the list order changes without re-building the buttons. \$\endgroup\$ – DMGregory Aug 17 '19 at 17:28

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.