C# - Dynamically Changing the Variable A Button Passes to The Method

I’m looking for a method that allows me to change this - essentially, I have a loop running that generates a number of buttons, and I’d like to be able to assign a variable to the button based the conditions under which the button was created.
Only method of doing this I’ve been able to find of doing this (which is also the only way I’ve found on the forums) is to attach a script to the button that has a variable there, which can then be changed at runtime, and pressing the button triggers first that script. It seems like a roundabout way of achieving things…

8486387--1128716--upload_2022-10-3_17-26-32.png

I’m convinced I’ve missed something, somewhere in the documentation. Any help would be greatly appreciated.

It’s actually kind of the correct way.

The button signals an intent from the user, nothing else.

“User has poked button XXX”

The decision as to what that intent means should probably not be on the button.

It can be, but it adds no value there except to lurk and confuse you in the future.

Instead, structure your code so that:

  • button generates an intent (simply calls a function)

  • that function decides what should / could happen

  • that function then does the appropriate thing

Otherwise your project ends up as this horrible frankenstein with some logic coded in the buttons and some logic coded in code, and when you want to go change it you have to do extra work to find out where that decision even happens.

I’m a coder, I put it all in code, as little as possible in the UI. My Datasacks package emphasizes that, and I use it for ALL my UI.

8687265--1171653--20180724_datasacks_overview.png

Datasacks is presently hosted at these locations:

https://bitbucket.org/kurtdekker/datasacks

Makes sense - My code is already pretty much structured exactly that way. I’ve been working with this flow for a while now, but this is the first time I’ve hit a point where I’m attempting to generate a unique button per Inventory item (essentially the icon), and I felt like I had one too many steps in the process.
Cheers for your help!

Editing: I hit post, and then your edit appeared. Makes complete sense now and I can see I have one or two refactors to do…

The enclosed example is how I like to structure my dynamic UI stuff.

It is a vanilla-Unity example that does NOT use Datasacks.

Basically it dynamically makes buttons according to the sprites it finds, fills in the image, fills in the name, and also a button intent.

The button intent is presently just outputted to Debug.Log()

I use strings ALWAYS for my button intents because it’s far easier to debug than “What on earth does button 37 mean?!”

8494565–1130474–DynamicUIDemo.unitypackage (55.7 KB)

1 Like

For future reference: adding listeners with parameters to the Button’s onClick event can be done using UnityEventTools.AddXPersistentListener in the editor.

In builds a lambda expression or a local function can be used to conveniently convert a parametered delegate into a parameterless one, so that it can be passed to Button.onClick.AddListener.

To make debugging easier, it can be beneficial to define an abstraction for adding and removing listeners which always uses the prior method in the editor (even in play mode) and switches to the latter in builds. This is because runtime callbacks added through the UnityEvent.AddListener method aren’t visualized in the Inspector.

Wrapper for a callback that is usable both in the editor and in builds:

#if UNITY_EDITOR
using UnityEditor.Events;
#endif
using UnityEngine;
using UnityEngine.Events;

public readonly struct EventListener<T>
{
#if UNITY_EDITOR
    private readonly UnityAction<T> action;
#else
    private readonly UnityAction action;
#endif

#if UNITY_EDITOR
    public static implicit operator UnityAction<T>(EventListener<T> eventListener) => eventListener.action;
#else
    public static implicit operator UnityAction(EventListener<T> eventListener) => eventListener.action;
#endif

    public static EventListener<int> Create(UnityEvent @event, UnityAction<int> action, int argument)
    {
#if UNITY_EDITOR
        var result = new EventListener<int>(action);
        UnityEventTools.AddIntPersistentListener(@event, action, argument);
#else
        var result = new EventListener<int>(action, argument);
        @event.AddListener(result);
#endif

        return result;
    }

    public static EventListener<string> Create(UnityEvent @event, UnityAction<string> action, string argument)
    {
#if UNITY_EDITOR
        var result = new EventListener<string>(action);
        UnityEventTools.AddStringPersistentListener(@event, action, argument);
#else
        var result = new EventListener<string>(action, argument);
        @event.AddListener(result);
#endif

        return result;
    }

    public static EventListener<Object> Create(UnityEvent @event, UnityAction<Object> action, Object argument)
    {
#if UNITY_EDITOR
        var result = new EventListener<Object>(action);
        UnityEventTools.AddObjectPersistentListener(@event, action, argument);
#else
        var result = new EventListener<Object>(action, argument);
        @event.AddListener(result);
#endif

        return result;
    }

#if UNITY_EDITOR
    public EventListener(UnityAction<T> action) => this.action = action;
#else
    public EventListener(UnityAction<T> action, T argument)
    {
        this.action = OnEventInvoked;

        void OnEventInvoked()
        {
            action(argument);
        }
    }
#endif
}

Extension methods that make use of the callback wrapper:

#if UNITY_EDITOR
using UnityEditor.Events;
#endif
using UnityEngine;
using UnityEngine.Events;

public static class UnityEventExtensions
{
    public static EventListener<int> AddListener(this UnityEvent @event, UnityAction<int> action, int argument) => EventListener<int>.Create(@event, action, argument);
    public static EventListener<string> AddListener(this UnityEvent @event, UnityAction<string> action, string argument) => EventListener<string>.Create(@event, action, argument);
    public static EventListener<Object> AddListener(this UnityEvent @event, UnityAction<Object> action, Object argument) => EventListener<Object>.Create(@event, action, argument);

#if UNITY_EDITOR
    public static void RemoveListener(this UnityEvent @event, EventListener<int> listener) => UnityEventTools.RemovePersistentListener<int>(@event, listener);
    public static void RemoveListener(this UnityEvent @event, EventListener<string> listener) => UnityEventTools.RemovePersistentListener<string>(@event, listener);
    public static void RemoveListener(this UnityEvent @event, EventListener<Object> listener) => UnityEventTools.RemovePersistentListener<Object>(@event, listener);
#endif
}

Usage example:

List<EventListener<int>> listeners = new List<EventListener<int>>();

void AddFiveListeners()
{
    for(int i = 0; i < 5; i++)
    {
        int argument = i;
        listeners.Add(button.onClick.AddListener(ButtonPress, argument));
    }
}

void RemoveAllListeners()
{
    foreach(var listener in listeners)
    {
        button.onClick.RemoveListener(listener);
    }

    listeners.Clear();
}

void ButtonPress(int argument) => Debug.Log($"ButtonPress({argument}");