-2

is it possible to write the function below in just one foreach loop? i have already try it with List<object> but i do not bring this to work.

private void SoundsOnButtons()
{
    Button[] buttons = Resources.FindObjectsOfTypeAll<Button>();
    Toggle[] toggles = Resources.FindObjectsOfTypeAll<Toggle>();

    foreach (Button obj in buttons)
    {
        obj.gameObject.AddComponent<EventTrigger>();

        EventTrigger.Entry eventEnter = new EventTrigger.Entry();
        eventEnter.eventID = EventTriggerType.PointerEnter;
        eventEnter.callback.AddListener((eventData) => {
            Sound.PlaySfxOneShot(Sound.GiveSfxSound(7));
        });

        obj.GetComponent<EventTrigger>().triggers.Add(eventEnter);
    }

    foreach (Toggle obj in toggles)
    {
        obj.gameObject.AddComponent<EventTrigger>();

        EventTrigger.Entry eventEnter = new EventTrigger.Entry();
        eventEnter.eventID = EventTriggerType.PointerEnter;
        eventEnter.callback.AddListener((eventData) => {
            Sound.PlaySfxOneShot(Sound.GiveSfxSound(7));
        });

        obj.GetComponent<EventTrigger>().triggers.Add(eventEnter);
    }

}

thx for help. pezezzle

---------------EDIT---------------

Thanky you @derHugo this was exactly what i looking for but why is this not possible?:

    private void AddToList<T>() where T : Selectable
{
    List<T> list = new List<T>();

    Button[] buttons = Resources.FindObjectsOfTypeAll<Button>();
    Toggle[] toggles = Resources.FindObjectsOfTypeAll<Toggle>();

    list.AddRange(buttons); // Here i get a error cannot convert !
    list.AddRange(toggles); // Here i get a error cannot convert !

    foreach (var obj in list)
    {
        obj.gameObject.AddComponent<EventTrigger>();

        EventTrigger.Entry eventEnter = new EventTrigger.Entry();
        eventEnter.eventID = EventTriggerType.PointerEnter;
        eventEnter.callback.AddListener((eventData) => {
            Sound.PlaySfxOneShot(Sound.GiveSfxSound(7));
        });

        obj.GetComponent<EventTrigger>().triggers.Add(eventEnter);
    }
}
Ronny Bigler
  • 654
  • 6
  • 12
  • You can use `List` and `AddRange` all buttons and all toggles – Charles Dec 06 '21 at 13:23
  • This is a job for _discriminated unions_! \*runs off to a nearby phonebooth\* – Dai Dec 06 '21 at 13:26
  • "but i do not bring this to work." - **how** did `List` not work, exactly? – Dai Dec 06 '21 at 13:27
  • `obj.gameObject.AddComponent();` <-- I checked the Unity docs, and `Button` does not have any inherited property or member `gameObject` - so where are you getting it from? – Dai Dec 06 '21 at 13:42
  • thx all for answear. i try to put all (Toggle and Buttons) with addRange, as @Charles says, in a list and then `foreach(object obj mylist){ obj.gameObject.AddComponent(); }` but this does not work i cant access gameObject this way. I try to: (Toggle)obj.gameObject... cast it, but it also not work. – Ronny Bigler Dec 06 '21 at 13:50
  • 1
    You can, but why is one loop? If you just want to simplify the code, wrapping things in the for loop into a new method is a better choice. – shingo Dec 06 '21 at 14:03
  • @RonnyBigler Unity's documentation does not state that `gameObject` is a member of `Toggle` nor `Button` - so I don't know how your original code runs at all. – Dai Dec 06 '21 at 14:23
  • @Dai without further information I think it is always legit to assume the "normal" case. Under this assumption OP is referring to [`Button`](https://docs.unity3d.com/Packages/com.unity.ugui@1.0/api/UnityEngine.UI.Button.html) and [`Toggle`](https://docs.unity3d.com/Packages/com.unity.ugui@1.0/api/UnityEngine.UI.Toggle.html) which both inherit finally from [`Component`](https://docs.unity3d.com/ScriptReference/Component.html) -the mother of all components in Unity which has the property [`gameObject`](https://docs.unity3d.com/ScriptReference/Component-gameObject.html) – derHugo Dec 06 '21 at 14:40
  • @derHugo That's _Unity UI 1.0_. In more recent versions of Unity that doesn't seem to be true as [Button](https://docs.unity3d.com/ScriptReference/UIElements.Button.html) now is `Button > TextElement > BindableElement > VisualElement > Focusable > CallbackEventHandler > System.Object`. – Dai Dec 06 '21 at 14:44
  • @Dai no ... if you take a look at [Comparison of UI Systems](https://docs.unity3d.com/2020.2/Documentation/Manual/UI-system-compare.html) the namespace `UIElements` belongs to the `UI Toolkit` which is still in **preview**. OP couldn't use [`FindObjectsOfType`](https://docs.unity3d.com/ScriptReference/Object.FindObjectsOfType.html) on `UIElement` classes in the first place ;) But why would you assume that current code doesn't compile rather than just assume the more obvious case that OP doesn't use `UnityEngine.UIElements` but rather `UnityEngine.UI` (uGUI) which comes installed per default ;) – derHugo Dec 06 '21 at 14:57
  • @Dai i have no idea why, but it works. I think it is so how derHugo dos explain. – Ronny Bigler Dec 06 '21 at 15:46

1 Answers1

4

You could, yes.

I know this is not exactly what your title is asking for but in my opinion your code would be already way better just extracting the inner block into a method and do

private void SoundsOnButtons()
{
    var buttons = Resources.FindObjectsOfTypeAll<Button>();
    
    foreach (var obj in buttons)
    {
        AddTriggerEvent(obj.gameObject);
    }

    var toggles = Resources.FindObjectsOfTypeAll<Toggle>();

    foreach (var obj in toggles)
    {
        AddTriggerEvent(obj.gameObject);
    }
}

private void AddTriggerEvent(GameObject target)
{
    var eventTrigger = target.AddComponent<EventTrigger>();

    var eventEnter = new EventTrigger.Entry();
    eventEnter.eventID = EventTriggerType.PointerEnter;
    eventEnter.callback.AddListener((eventData) => {
        Sound.PlaySfxOneShot(Sound.GiveSfxSound(7));
    });

    eventTrigger.triggers.Add(eventEnter);
}

This way you stay fully flexible and maintainable while it is as performant as doing all in a single loop - maybe even better since you don't waste any implementation or execution performance on uniting the two collections into one first.


You could even go one step further if you really want to and make your own generic wrapper method again like e.g.

// "Selectable" is the most specific common base class of UI.Button and UI.Toggle
// You could also use "Component" in order to just allow any component as type parameter
private AddEventTriggersToAll<T>() where T : Selectable
{
    var instances = Resources.FindObjectsOfTypeAll<T>();
        
    foreach (var obj in instances)
    {
        AddTriggerEvent(obj.gameObject);
    }
}

and then just do

private void SoundsOnButtons()
{
    AddEventTriggersToAll<Button>();
    AddEventTriggersToAll<Toggle>();
}

If you really want to go for a single loop you could use e.g. Linq Concat

foreach(var obj in Enumerable.Empty<Selectable>().Concat(buttons).Concat(toggles)
{
    AddTriggerEvent(obj.gameObject);
}

which basically equals more or less doing

var list = new List<Selectable>(buttons.Length + toggles.Length);
list.AddRange(buttons);
list.AddRange(toggles);

foreach(var obj in list)
{
    AddTriggerEvent(obj.gameObject);
}

As a very general note though: Are you sure you want to use Resources.FindObjectsOfTypeAll and not maybe rather simply Object.FindObjectsOfType?


Update - Answer to your edit

While you can add instances of Button or Toggle to a List<Selectable> you can not do so on a generic List<T> where T : Selectable because here you only limit the type downwards.

In other words since Button is a Selectable it would theoretically be totally valid to call AddToList<Button> BUT what you then would try to do is basically

List<Button> list = new List<Button>();

Toggle[] toggles = Resources.FindObjectsOfTypeAll<Toggle>();
list.AddRange(toggles);

=> can't work of course!

However, I don't see a need for your method to be generic at all ... why not simply have

private void AddToList()
{
    var list = new List<Selectable>();

    var buttons = Resources.FindObjectsOfTypeAll<Button>();
    var toggles = Resources.FindObjectsOfTypeAll<Toggle>();

    list.AddRange(buttons);
    list.AddRange(toggles);

    foreach (var obj in list)
    {
        obj.gameObject.AddComponent<EventTrigger>();

        EventTrigger.Entry eventEnter = new EventTrigger.Entry();
        eventEnter.eventID = EventTriggerType.PointerEnter;
        eventEnter.callback.AddListener((eventData) => {
            Sound.PlaySfxOneShot(Sound.GiveSfxSound(7));
        });

        obj.GetComponent<EventTrigger>().triggers.Add(eventEnter);
    }
}

BUT note that AddRange and working with List<T> in general is pretty expensive if you don't assign the expected capacity beforehand since while the list grows and internally exceeds the current maximum capacity it all the time has to reallocate and move around the underlying array!

You really gain nothing by "merging" both arrays together just to have a single loop. IF you want to go that way despite everything I said you should rather do

var buttons = Resources.FindObjectsOfTypeAll<Button>();
var toggles = Resources.FindObjectsOfTypeAll<Toggle>();

var list = new List<Selectable>(buttons.Length + toggles.Length);
list.AddRange(buttons);
list.AddRange(toggles);

as this way you at least allocate the list only once with the correct capacity right away

derHugo
  • 83,094
  • 9
  • 75
  • 115