Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Question C# - Transfer Buttons to EventTriggers

Discussion in 'Scripting' started by SaariTech, Sep 18, 2023.

  1. SaariTech

    SaariTech

    Joined:
    Jan 6, 2014
    Posts:
    20
    I'm stuck on how to transfer all data from onClick from buttons (ButtonClickedEvent) into callback in event triggers (EventTrigger.EventTrigger). My problem is that I have over 500 buttons and I can transfer them manually but I want to do it automatically.

    My code looks like this now:

    Code (CSharp):
    1. Button[] buttons = GameObject.FindObjectsByType<Button>(FindObjectsInactive.Include, FindObjectsSortMode.None);
    2. for(int i = 0; i < buttons.Length; i++)
    3. {
    4.     EventTrigger trigger = buttons[i].gameObject.GetOrAddComponent<EventTrigger>();
    5.     EventTrigger.Entry entry = new EventTrigger.Entry();
    6.     entry.callback = (EventTrigger.TriggerEvent)buttons[i].onClick;
    7.     entry.eventID = EventTriggerType.PointerClick;
    8.     Destroy(buttons[i]);
    9. }
     
  2. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,563
    You can attach any code / data you want via the .onClick delegate on the Button.

    See attached example.

    Relevant code:

    Code (csharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. // @kurtdekker
    6.  
    7. public class DynamicUIDemo : MonoBehaviour
    8. {
    9.     public OneSingleTile ExemplarTile;
    10.  
    11.     OneSingleTile MakeFreshCopyOfExampleTile()
    12.     {
    13.         // create it and simultaneously parent it to the same place in the UI
    14.         var copy = Instantiate<OneSingleTile>( ExemplarTile, ExemplarTile.transform.parent);
    15.  
    16.         // make it visible
    17.         copy.gameObject.SetActive( true);
    18.  
    19.         return copy;
    20.     }
    21.  
    22.     void Start ()
    23.     {
    24.         // turn off the example first, so it doesn't interfere
    25.         ExemplarTile.gameObject.SetActive( false);
    26.  
    27.         // you choose how to create these, perhaps from a list
    28.         // of known levels, or a list of items, for instance.
    29.  
    30.         // This code searches Resources/DynamicUISprites and loads
    31.         // all the sprites it finds, sorts them by alphabetical
    32.         // name, and displays them in the grid.
    33.  
    34.         var sprites = Resources.LoadAll<Sprite>( "DynamicUISprites/");
    35.  
    36.         Debug.Log( System.String.Format( "I found {0} sprites.", sprites.Length));
    37.  
    38.         // alpha sort by name
    39.         System.Array.Sort( sprites, (a,b) => { return a.name.CompareTo( b.name); });
    40.  
    41.         // now make buttons out of each one
    42.         foreach( var sprite in sprites)
    43.         {
    44.             var entry = MakeFreshCopyOfExampleTile();
    45.  
    46.             entry.SetIconSprite( sprite);
    47.  
    48.             entry.SetCaptionText( sprite.name);
    49.  
    50.             // set up what each button does when pressed
    51.             {
    52.                 string textToDisplay = "You clicked on:" + sprite.name;
    53.  
    54.                 entry.SetButtonDelegate(
    55.                     () =>
    56.                     {
    57.                         Debug.Log( textToDisplay);
    58.  
    59.                         // TODO you can call whatever other code you like here...
    60.                     }
    61.                 );
    62.             }
    63.         }
    64.     }
    65. }
    and the individual button control script is:

    Code (csharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using UnityEngine.UI;
    5.  
    6. // @kurtdekker
    7.  
    8. public class OneSingleTile : MonoBehaviour
    9. {
    10.     [SerializeField]Button Button;
    11.     [SerializeField]Text Caption;
    12.     [SerializeField]Image Icon;
    13.  
    14.     public void SetCaptionText( string caption)
    15.     {
    16.         Caption.text = caption;
    17.     }
    18.  
    19.     public void SetIconSprite (Sprite sprite)
    20.     {
    21.         Icon.sprite = sprite;
    22.     }
    23.  
    24.     public void SetButtonDelegate( System.Action action)
    25.     {
    26.         Button.onClick.AddListener(
    27.             delegate {
    28.                 action();
    29.             }
    30.         );
    31.     }
    32. }
    Screenshot-KurtGame-133395395078024650.png
     

    Attached Files:

    Last edited: Sep 18, 2023
  3. SaariTech

    SaariTech

    Joined:
    Jan 6, 2014
    Posts:
    20
    This isn't helping.

    I was able to add or remove listener in Button or EventTrigger but I need to transfer the listeners from Button class to EventTrigger class, transfer the existing executing methods with parameters from the Button class. I found a way to call the method by string which can be used from button.onClick.GetPersistentMethodName(j). with this solution:

    https://stackoverflow.com/questions/540066/calling-a-function-from-a-string-in-c-sharp

    But the problem with this is that I don't know how to get the parameters and directly access the delegates from the Button class.
     
    Last edited: Sep 18, 2023
  4. SaariTech

    SaariTech

    Joined:
    Jan 6, 2014
    Posts:
    20
    This is what I need but it is inaccessible due to its protection level.

    Code (CSharp):
    1. UnityEngine.Events.PersistentCall call;

    And the another problem is I don't know how to get it from the Button class to give it to EventTrigger class.
     
  5. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,563
    Couldn't you just attach a delegate to one that simply reaches over to the other and invokes its call?

    You actually can invoke the .onClick delegates externally...

    I do it here in my datasacks package, mapping a keyboard "intent" into invoking a button's payload, whever that might be.

    https://github.com/kurtdekker/datas...Assets/Datasack/Input/DSUserIntentKeyboard.cs

    Look around line 199...

    Code (csharp):
    1.             if (InvokeButtonOnClick)
    2.             {
    3.                 var button = GetComponent<Button>();
    4.                 button.onClick.Invoke();
    5.                 return;
    6.             }
     
  6. SaariTech

    SaariTech

    Joined:
    Jan 6, 2014
    Posts:
    20
    This works but not the solution I was looking for. I made a tool to convert Buttons to EventTriggers and the Buttons will be removed in my project. That part isn't hard to solve but I couldn't make it. Felt like the problem is something only Unity can solve. If they made the classes accessible and editable then I would fix that.
     
  7. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,563
    Just disable the buttons, then they won't interfere, and just use their delegates by poking them the way I mentioned above.

    Yes, it's not OO, yes, it's dirty, but your entire scene and every class inside of it is public effectively, so it's not like we're breaking a lot of OO by reaching for a different Component on the same GameObject.

    ALSO... technically you can get at the source for
    UnityEngine.UI
    here:

    https://github.com/Unity-Technologies/uGUI

    But do read the license for what you are allowed to do with it!!
     
  8. SaariTech

    SaariTech

    Joined:
    Jan 6, 2014
    Posts:
    20
    I'll take a look into it and see if I'm able to solve it. Thank you for the link!
     
  9. SaariTech

    SaariTech

    Joined:
    Jan 6, 2014
    Posts:
    20
    The code in GitHub is old, there is also the same code under the Packages/ folder in the Unity project. What I need is access some features in
    UnityEngine.Events.UnityEvent
    and it can be found in
    UnityEngine.CoreModule.dll
    but the variables/functions are compiled as internal and it's outside of my control. I tried https://stackoverflow.com/questions/2067938/copy-a-class-to-another but it couldn't find the internal variables in
    UnityEvent
    to be cloned.
     
    Last edited: Sep 19, 2023
  10. SaariTech

    SaariTech

    Joined:
    Jan 6, 2014
    Posts:
    20
    Seems like I have no other choice than using dnSpy to change that
    internal
    to
    public
    in UnityEvent in UnityEngine.CoreModule.dll.
     
  11. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    5,769
    Can you not just use reflection rather than (I think illegally) modifying source code?
     
  12. SaariTech

    SaariTech

    Joined:
    Jan 6, 2014
    Posts:
    20
    I'll give reflection a try as it seems be a better alternative. Thank you!
     
  13. SaariTech

    SaariTech

    Joined:
    Jan 6, 2014
    Posts:
    20
    I still have problem. I tried this:

    Code (CSharp):
    1. static void FindObject()
    2. {
    3.     object m_Calls0 = GetPropValue(buttons[i].onClick, "m_Calls");
    4.     object m_Calls1 = GetFieldValue(buttons[i].onClick, "m_Calls");
    5. }
    6.  
    7. static object GetPropPublicValue(object src, string propName)
    8. {
    9.     return src.GetType().GetProperty(propName).GetValue(src, null);
    10. }
    11.  
    12. static object GetFieldPublicValue(object src, string fieldName)
    13. {
    14.     return src.GetType().GetField(fieldName).GetValue(src);
    15. }
    16.  
    17. static object GetPropValue(object src, string propName)
    18. {
    19.     return src.GetType().GetProperty(propName, System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).GetValue(src, null);
    20. }
    21.  
    22. static object GetFieldValue(object src, string fieldName)
    23. {
    24.     return src.GetType().GetField(fieldName, System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).GetValue(src);
    25. }
    I also tried get the list of calls from
    PrepareInvoke()
    but it returns 0 when I check
    Count
    as it returns
    List<BaseInvokableCall> list
    from
    PrepareInvoke()
    .

    My code looks like this:
    Code (CSharp):
    1. MethodInfo dynMethod = buttons[i].onClick.GetType().GetMethod("PrepareInvoke", BindingFlags.NonPublic | BindingFlags.Instance);
    2. object list = dynMethod.Invoke(buttons[i].onClick, new object[] { });
    3. Debug.Log(GetPropValue(list, "Count")); // Displays 0
     
  14. SaariTech

    SaariTech

    Joined:
    Jan 6, 2014
    Posts:
    20
    I'll go with this temporary solution for now even if it is not the best solution.

    I wish there is a public function like
    GetEvent(int index)
    in
    UnityEvent
    so we everyone can later assign them to AddListener in
    UnityEvent
    in the other classes like
    Button
    or
    EventTrigger
    , this would save a lot of time rather than moving all events from onClick (Button) to callback (EventTrigger) in Unity Editor one by one.
     
  15. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,563
    I'm no C# weenie but I suspect if you just copied it out you might be able to action it yourself...

    Code (csharp):
    1. var foo = myButton.onClick;
    2.  
    3. myTriggerThingy.AddListener( delegate { foo(); } );      // or .Invoke() or whatever...
    Haven't tried that, just spitballing here in this window.

    I always keep my user intent's as a "pure intent," containing nothing, and then write switchblock service code to handle it.

    This lets the UI have zero logic. I don't like logic in both UI and code. :)

    My Datasacks package works that way: each button signals a string event, by default the name of the GameObject it is on. Plus you can add your own whatever-whatever-whatever if you're doing N-number things.

    20180724_datasacks_overview.png

    Datasacks is presently hosted at these locations:

    https://bitbucket.org/kurtdekker/datasacks

    https://github.com/kurtdekker/datasacks

    https://gitlab.com/kurtdekker/datasacks

    https://sourceforge.net/projects/datasacks/
     
  16. SaariTech

    SaariTech

    Joined:
    Jan 6, 2014
    Posts:
    20
    To keep the paths short as possible is what I'm trying to do, the data already exists and are accessible but the compiled code are internal and private. It takes more time for the external Unity developers to perform their tasks when needed.
     
    Kurt-Dekker likes this.
  17. SaariTech

    SaariTech

    Joined:
    Jan 6, 2014
    Posts:
    20
    Will it be possible to access the methods from UnityEvent in the upcoming Unity version if it wasn't possible externally access for now?
     
    Last edited: Sep 24, 2023