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

Pass a variety of parameters / types to a generic function

Discussion in 'Scripting' started by keystagefun, Jun 30, 2020.

  1. keystagefun

    keystagefun

    Joined:
    Feb 19, 2020
    Posts:
    33
    Hi,

    Using C#, every time I want a UI button in my app, I create it using code.

    I pass through a bunch of info to my button class and construct the button piece by piece. All works well.

    I also pass through the function I want to run when my button is clicked. This also works fine.

    What I can't figure out, is the best way to pass the parameters to that function, when they may be different for each button.

    In the class that handles all my UI, I'm using the following currently:

    Code (CSharp):
    1. public delegate void ClickDelegate(string val);
    2. public static ClickDelegate clickFunction;
    I then pass the function name as type ClickDelegate into my button constructor.

    Instead of the parameter "val" being of type "string", what I really want is for "val" to be whatever type it needs to be for whatever function is being called, which ideally would be an object, containing multiple data types that won't always be the same.

    I'm new to Unity, so I'm probably missing something obvious, but in the package I used to work with, you could pass an object that contained a bunch of data of different types and then unpack it in whichever function was being called. So my button creation call would contain something like this:

    Code (CSharp):
    1. // First button, with 4 parameters (a string, an integer, a custom class and a game object) passed through
    2. params = {msg: "I was clicked", time: 1000, script: myScript, go: myGameObject};
    3. this.createButton( px: 100, py: 100, txt: "My Button 1", onClickFunction: clickButton1, onClickParams: params);
    4.  
    5. // Second button, with 3 different parameters (an audio file, a string and a colour) passed through to its onClick function
    6. params2 = {soundFx: "mySound.wav", title: "Hello!", color: myColor};
    7. this.createButton( px: 300, py: 100, txt: "My Button 2", onClickFunction: clickButton2, onClickParams: params2);
    Hope that makes sense... I can't find an easy way to achieve this...

    Thanks.
     
  2. StarManta

    StarManta

    Joined:
    Oct 23, 2006
    Posts:
    8,741
    I would resolve this by using a delegate and a lambda function like this:
    Code (csharp):
    1. void createButton(int px, int py, string txt, Action onClickFunction) {
    2. // store onClickFunction somewhere, etc
    3. }
    4.  
    5. this.createButton(300, 100, "My button 2", () => { PlaySound("mySound.wav", "Hello!", myColor); });
    Basically you store a callback function with no parameters, and that callback function just calls some other function that does have the parameters you need. You can define that as an inline lambda function (the () => { } bit).

    If you do want it to have a parameter (say you want to use the same concept for when someone has entered text into a text field), the syntax looks like:
    Code (csharp):
    1.  
    2. void createTextField(int px, int py, string txt, Action<string> onEditFunction) {
    3. // store onEditFunction somewhere, etc
    4. }
    5.  
    6. this.createTextField(300, 100, "My text field", (str) => { PlaySound("mySound.wav", str, myColor);});
    In this example, "str" is the parameter that is sent to onEditFunction.
     
  3. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,380
    This is why Microsoft when designing the 'event' keyword designated System.EventHandler as the default shape of an event handler:
    Code (csharp):
    1. void EventHandler(object sender, EventArgs e)
    https://docs.microsoft.com/en-us/dotnet/api/system.eventhandler?view=netcore-3.1

    And is why any events related to the og .net/mono framework also have a similar shape. For example System.IO.FileSystemWatcher can dispatch events when the filesystem changes:
    https://docs.microsoft.com/en-us/dotnet/api/system.io.filesystemwatcher.changed?view=netcore-3.1

    I don't use UnityEvent, but rather my own custom SPEvent that predates UnityEvent. But they're effectively the same thing. And with it too I follow this same <object, EventArgs> shape:
    https://github.com/lordofduct/space...pacepuppyUnityFramework/Events/SPEvent.cs#L20

    And you could do the same with UnityEvent:

    Code (csharp):
    1.  
    2. [System.Serializeable]
    3. public class UnityEventWithArgs : UnityEvent<object, System.EventArgs> {}
    4.  
    Only downside is that eventargs if they house values need to be instantiated (empty ones can just use EventArgs.Empty). Which can create garbage. I use a resource pool for mine personally.

    Or in your example in your original post. You create the 'args' as part of the button creation process (it's basically your object with all those values attached) and pass it along to be dispatched with the event. You'd have to create the contract class for the variety of properties you want on the EventArgs for all your values you pass along.

    OR, you could take advantage of 'dynamic' and do this instead:

    Code (csharp):
    1.  
    2. [System.Serializeable]
    3. public class DynamoUnityEvent: UnityEvent<object, dynamic> {}
    4.  
    Then when you create your button use anonymous objects:
    Code (csharp):
    1.  
    2. CreateButton(new { px = 300, py = 100, txt = "My Button 2" });
    3.  
    Your button class could just look like:
    Code (csharp):
    1.  
    2. public class DynamoButton : MonoBehaviour
    3. {
    4.  
    5.     public DynamoUnityEvent OnClick;
    6.     public dynamic EventArgs;
    7.  
    8.     public void SignalClick()
    9.     {
    10.         OnClick.Invoke(this, EventArgs);
    11.     }
    12.  
    13. }
    And of course if you wanted it in the form of a delegate:
    Code (csharp):
    1. public delegate DynamicCallback(object sender, dynamic args)
    or just:
    Code (csharp):
    1. public delegate DynamicCallback(dynamic args)
    or simply:
    Code (csharp):
    1. public delegate ObjectCallback(object args)
    And just cast the object to dynamic or a known type if you knew the type.

    ...

    Though I would say that the shape of the 'CreateButton' method doesn't remind me of args being passed with the event, and more being passed to some javascript method for creating buttons and those are the value you want to set on the button.

    You could use an anonymous object for that as well.

    Or the shorthanded constructor:
    Code (csharp):
    1.  
    2. var obj = new SomeType()
    3. {
    4.     SomeProp = 1,
    5.     SomeOtherProp = 2,
    6.     SomeOtherOtherProp = "hello"
    7. };
    8.  
    Also... @StarManta's post.
     
    Last edited: Jun 30, 2020
  4. keystagefun

    keystagefun

    Joined:
    Feb 19, 2020
    Posts:
    33
    Thanks both of you - really helpful and a couple of options to look at.
    Tried this quickly and it works perfectly. Thank you.