I have a list that collects all TextMesh objects in the scene. I would like to sort it by hierarchy, but for some reason it seems to be sorting it by when it was created, ascending. I tried organizing them in the hierarchy but it doesn't seem to make any difference. I am using Code (CSharp): texts = FindObjectsOfType<TextMeshProUGUI>().ToList(); to populate my list. I tried using the .Orderby() Linq function, but I can't really figure out how it works. Even sorting by position y would work, as my project is pretty well top to bottom. Thanks in advance.
You can use Array.Sort with a comparison delegate. Untested, but this should work: Code (csharp): var texts = FindObjectsOfType<TextMeshProUGUI>(); Array.Sort<TextMeshProUGUI>(texts, (a, b) => { return a.transform.position.y.CompareTo(b.transform.position.y); });
ignore Linq whenever you can. It wasn't made for games. don't run away from it either, sometimes it's useful. you need to supply a lambda to your sorting function. StarManta was quicker with a working example of a lambda.
I can't get it to work, it says "Array does not exist in this context." It could be because it's a list? There seems to be a sort function if I type "texts.Sort" but it doesn't work with your code there. Also, how does this work exactly? what are "a" and "b"? Does this cycle through the whole list?
Make sure you have using System; in your file. And yes, it goes through the list and sorts it. "a" and "b" are two hypothetical elements of the list that need to be compared. The sort function will use that comparison function over and over again so it knows what order things are supposed to go in. The actual algorithm used to sort is not important, just know that sorting will happen.
@Mashimaro7 StarManta just forgot to end the first line with .ToList(); look at your code and do the same thing. Lists =/= arrays And the array is what you get normally. edit: he didn't forget, he used a slightly different approach that works with arrays instead, so go with his PraetorBlue's answer. also try learning more about lambdas in general. you need them only rarely, but it helps if you can understand them.
Hmm, I'm still running into an error. Code (CSharp): private void Start() { print(FindObjectsOfType<TextMeshProUGUI>()); texts = FindObjectsOfType<TextMeshProUGUI>().ToList(); texts.Sort<TextMeshProUGUI>(texts, (a, b) => { return a.transform.position.y.CompareTo(b.transform.position.y); }); } "The nongeneric method "List<TextMeshProUGUI>().Sort() cannot be used with type arguments"
No errors now, but it is still sorting by most recent it appears... I tried reordering in hierarchy, and renaming, but it's sorting by the most recently created ones. Which is pretty inconvenient because I have a script that changes the text based on the number in the List.
What are you basing this on? If you're basing it on your print() statement in your code, note that that is logging the unmodified result. Do you have another debug log somewhere that has the order after you sort it?
I have it public, I'm viewing it in the inspector. Here's a screenshot. "Titles" was "title (1)" before, I just renamed it to see if it would reposition it, but no luck.
Strange. I think my next step in your shoes would be to put in some logs to ensure that the code is being called when I expect it to be. So like: Code (csharp): texts.Sort<TextMeshProUGUI>(texts, (a, b) => { Debug.Log($"Comparing {a.gameObject.name} at {a.transform.position} to {b.gameObject.name} at {b.transform.position}"); return a.transform.position.y.CompareTo(b.transform.position.y); }); And you should see that log statement being outputted a bunch, covering a bunch of combinations of your text objects. This is just to make sure your sort code is being called when you expect it to. Another possibility to look into would be the possibility that your "texts" field is getting overwritten at some point. In VS you can right click on texts and "Find All References", and put a Debug.Log next to each line you see there that may be setting your array's values. One thing that's a bit suspicious to me: there are text objects there that are NOT included in your array, which means they must have been created after FindObjectsOfType was run. If that's the case, I wonder if there is more initialization that must be run before your sorting order is valid. Maybe try adding a one-frame delay to this searching and sorting thing to make sure that all the initialization is done running?
reposition it? You mean inside the array? Or inside the hierarchy? Have you checked the elements one by one as they appear in your array and compared the y coordinates of them? I'm still not sure what exact criteria you want to use to sort your elements. Are you actually interested to sort them based on their phyiscal y position in the scene?
I think (s)he means as ordered in the Transform itself, not by world coordinates @Mashimaro7 take a look at Transform.GetSiblingIndex()
And then Code (csharp): texts.Sort((a, b) => a.transform.GetSiblingIndex().CompareTo(b.transform.GetSiblingIndex()));
What are you trying to do that requires the List of TMP objects to be sorted by the order they appear in the hierarchy anyway? More often than not, sorting the collection isn't the only solution.
Well it's not entirely nonsensical. Maybe (s)he just wants to be able to author the order through hierarchy itself, which is a valid technique imo. Though your question is also valid, maybe there is a better solution, and the whole sorting ordeal is a conceptual mislead.
I'm trying to make a serializable list of editable text objects, that can be increased or decreased with the click of a button. The issue is, say I have 3 texts, it's sorting them in order of ,"3,2,1." so, if I make another one and add it to that list, it will be "3,2,1,4" then when I load the game again, it will load up the new one as 4,3,2,1, and the text will be all outta whack because i t will be loading the index "3" which will have been saved to number 4, into index 0 because number 4 is the top of the list now. Edit: Each text object has an attached script which serializes it to PlayerPrefs based on the index of the manager object
your problem is weird. I'm not convinced it's the right approach to solve serialization issues. you kind of messed something else up if your indices get shuffled every time you make a change.
The issue is for some reason it's sorting by time of creation. I'm not sure why? I really didn't do anything, the above script is the only one that populates the list. But the issue is, if I create and add to the list during playtime, then after saving and quitting and loading again, it will repopulate the list.
wait a minute, you mean FindObjectsOfType? you have a problem with FindObjectsOfType basically reversing your intended order? is that it? can you come up with a code that clearly reproduces an issue, so we can fix it, I'm sure a simpler and more logical solution is at your grasp.
there is frankly an issue with documentation on this one as well. the order isn't exactly specified, and well, maybe it's not specified by design, but knowing unity, maybe it's just not documented. but let's investigate this first.
are you by any chance building this list from a single transform (excluding children*)? because if you do, I think the best way would be to, in fact, implement your own FindObjectsOfType. not only you get more control, but it's also very easy, and it'll likely run faster, because I doubt Unity is using the C# 8 pattern matching to match the types. (* though it's certainly doable to do full tree traversal, I'm not sure if we really want to reinvent the wheels)
Here's my script, Code (CSharp): using System; using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; using TMPro; using UnityEngine.UIElements; public class GUIControl : MonoBehaviour { public GameObject panel; public List<TextMeshProUGUI> texts; public TextMeshProUGUI fieldo; string thisText; private int number; private void Start() { texts = FindObjectsOfType<TextMeshProUGUI>().ToList(); texts.Sort((a, b) => a.transform.position.y.CompareTo(b.transform.position.y)); } public void OpenPanel(int num) { number = num; panel.SetActive(true); } public void ClosePanel() { panel.SetActive(false); } private void Update() { if (Input.GetKeyDown(KeyCode.Return)) { texts[number].text = fieldo.text; thisText = texts[number].text; PlayerPrefs.SetString(number.ToString(), thisText); print(number); } } } I can't really understand how it finds the objects, to be honest. It just seems to be by order of creation.
The only fact is that the docs do not specify the order. So it might be anything really. You haven't answered any of my questions so that I can help you. Nvm, let's try this anyway Code (csharp): List<T> FindInTransform<T>(Transform xf) { var list = new List<T>(); for(int i = 0; i < xf.childCount; i++) { var found = xf.GetChild(i).GetComponent<T>(); if(found != null) { Debug.Log($"{found.gameObject.name} was found"); list.Add(found); } } return list; } Try this and show us the logged results (disable sorting for the time being) Code (csharp): texts = FindInTransform<TextMeshProUGUI>(this.transform); (Hopefully all your texts are in the same transform, so specify that transform as an argument here)
Sorry, I figured my script answered most your questions haha. I got an error with this script, "T does not contain a definition for 'gameObject' the intellisense doesn't let me put anything there except "found" and it just seems to print nothing to the console.
You've written a generic method with an unconstrained type parameter T. That means T can be literally any type possible. In your Debug.Log call you used found.gameObject. which is not possible since T can be anything. There is no gameObject in a float, Vector3 or any other type that is not a Component derived type. If you want to keep the type parameter unconstraint (so it would also work with interface types) you just have to remove your Debug.Log statement which also seems to just clutter the log. If you want to log the gameobjects name, use the actual Transform reference you have: Code (CSharp): for(int i = 0; i < xf.childCount; i++) { var child = xf.GetChild(i); var found = child.GetComponent<T>(); if(found != null) { Debug.Log($"{child.gameObject.name} was found"); list.Add(found); } } Another solution is to constrain the Type parameter T to Component. So you can only use types derived from Component as parameter. That way it's guaranteed that the type T is a Component and therefore has a gameObject property Code (CSharp): List<T> FindInTransform<T>(Transform xf) where T : Component {
True, I wanted him/her to test the premise, and it was 3 AM where I live. Yes it should be constrained to Component, obviously. I mean, wasn't this a dead giveaway that it's a typo? But thanks for fixing my mistake and explaining why it matters. Yes it's supposed to clutter the log, this is not a final solution, I want OP to log the results. No, it doesn't have to work in an unconstrained manner. This is only a half-baked quasi-replacement for FindObjectsOfType, where we try to establish some control over the order of the output, because this is what OP wanted. @Mashimaro7 And? Does it work? Sorry about the typo btw.
As Bunny83 pointed out, this is because found is of type T, which ends up being TextMeshProUGUI at the calling site. So I've mistakenly made it assume there was gameObject property available for this object, however the compiler cannot see this to be true in the general case. Because it's not. If we constrain the type T only to Component type (and TextMeshProUGUI derives from one), then it's fine, because gameObject does belong to this and any derived type. This is why Bunny83 offered an unconstrained solution, as a workaround for the general case, where we query the actual Transform instead of the component we get with GetComponent, which in an unconstrained case could've potentially be an interface as well. But no, I intended this to be constrained so just add where T : Component in the method declaration. Hopefully this serves as a simple generic method tutorial and exercise for you. Now you know why we sometimes use <> thingies to specify types (and how it can fail). Sorry for not replying sooner, I really had to sleep. We've done this because I'm curious to see the actual order you'll get from this.
Sorry for the late response, I've been busy with other projects. But I just picked it up again and was able to solve the issue. I just simply removed the text objects I had on scene(except for the title), and now made it texts.Add(intantiate) for every object, added the button that adds texts and had it essentially save the number of texts to player prefs, then instantiate the number of texts. I don't know why, but the weird ordering issue no longer occurs, even if I add new ones during gameplay. Thanks for the help guys. Sorry I wasn't able to test the code you provided haha.