Sorting UI List by distance

Hello all,
A question I am not quite sure how to tackle. I am trying to head towards having a list of available targets on my UI. Currently I am collecting all objects with tag of Enemy and then on my UI Scroll Rect I am instantiating a button foreach item in my List. This works for the basics but then I hit a wall on my next step.

My end goal is to have the the list and the UI Scroll Rect list keep updated with object distance and sort themselves so that the closer objects are on the top of the list. For those who have played it, think Eve Online and how they have their list of selectable targets within a system.

I am able to keep track of distances and all that fun stuff but I am not sure, or if even possible, to do this with the Unity UI. Wanted to see if anyone had any ideas. Thanks!

we will need to see your “fun stuff” to see how you are keeping track of your enemies in the first place

In my current game I have a combat log which is a ScrollView that fills with a list of actions each character took combat:

  1. I have the ScrollView simply created from GameObject>UI>ScrollView.
  2. I have my Content object in the ScrollView contain the Content Size Fitter component which resizes the object based on the child objects under it.
  3. I have my Content object in the ScrollView contain the GridLayoutGroup component which resizes the child objects to the size set in the GridLayoutGroup.
  4. When combat is over, I simply instantiate my combat log prefabs into the Content GameObject of the ScrollView and those 2 components do the rest.

You will have to read up on those 2 components to see if they will fit your needs. They certainly fitted my needs, but I only have to output my combat log once, after the combat is over. I have no idea how this will work on timed intervals, or even every frame. Ideally you only change this log when you register a change.

Some additionals from what I have I can think of is the need to clear and reload the ScrollView constantly, and needing to sort the log prefabs based on distance of enemies.

As far as UI setup, its just a formatting piece. I have a ScrollRect with a Mask, as a child to that I have the formatter with a Vertical layout group and a Content Size fitter.

Code wise, im still working on it but just a quick gathering of space stations in the current system on start I have

targets.AddRange(GameObject.FindGameObjectsWithTag("SpaceStation"));

        foreach (GameObject obj in targets)
        {
            var newButton = Instantiate(stationTargetButton);
            newButton.transform.SetParent(targetFormatter.transform, false);
            Target_Space_Station_button buttonScript = newButton.GetComponent<Target_Space_Station_button>();
            buttonScript.myOwner = obj;
        }

But yeah, when I did a similar thing with a shipyard garage I had to edit the list, then quickly disable and enable the UI formatter element to get it to update which is why I think I am going in the wrong direction on this and wanted to see if anyone else tackled something like this

If you have all the objects you want to track added to a list, you can Make use of List.sort to arrange its elements in the way you want. You will have to learn how to use List.sort however, there are several overloads which may or may not fit your needs.

Most likely the way you’d have to do it: Sorting a List by Variable? - Questions & Answers - Unity Discussions

Another added work you will have to do with this method is to remove the element in the list when it is removed from the scene.

I just hit a Eureka moment when I discovered a SetSiblingIndex() function. Though not sure if its the cleanest way to go about it I managed to get a working bit of code. In Update i used list.sort to keep the list updated by position which then gave me an updated index value for each item in the list. I then used the SetSiblingIndex to match that index value with the same index value in the List. It currently goes a little crazy when two objects are about the same distance but so far closer to what I need.

    void Update()
    {
        targets.Sort(delegate(GameObject a, GameObject b)
            {
                return Vector3.Distance(player.position, a.transform.position).CompareTo
                    ((Vector3.Distance(player.position, b.transform.position)));
            });

        foreach (GameObject obj in targets)
        {
            int index = targets.IndexOf(obj);

            foreach (Transform child in targetFormatter.transform)
            {
                var objData = child.GetComponent<Target_Space_Station_button>().myOwner;
                if (objData == obj)
                {
                    child.SetSiblingIndex(index);
                }
            }
        }

    }