So, what I do is my UI elements are prefabs (for example, the dropdown). Those have the script below attached to them and the event onValueChanged of the Dropdown component has the below method OnValueChanged assigned. Whatever instantiates the button prefab will pass through the logic of the button as we will see in the code below.

public class OptionEnumController : MonoBehaviour {    // [1]
    public Text text;            // [2]
    public Dropdown dropdown;    

    private Action _action;      // [3]
    private bool _isInitialized; // [4]

    public void Init(string optionName, int initialValue, string[] dropdownOptions, Action action) {  // [5]
        text.text = optionName;
        _action = action;         // [6]

        dropdown.options = new List<Dropdown.OptionData>();         // [7]
        foreach(var item in dropdownOptions) {
            dropdown.options.Add(new Dropdown.OptionData(item));
        }
        dropdown.value = initialValue;    // [8]
        _isInitialized = true;
    }

    public void OnValueChanged() {
        if(!_isInitialized) return;       
        _action?.Invoke(dropdown.value);   // [9]
    }

    public void SetValueWithoutFiringAction(int value) {  // [10]
        Action<int> previousAction = _action;
        _action = null;
        dropdown.value = value;
        _action = previousAction;
    }
    public int GetValue() {
        return dropdown.value;
    }
}

[1] - Here a dropdown represents enum data field. It is a controller (as in MVC terminology) because it takes UI events and calls the OnValueChanged method, which will fire the event that does the work.

[2] - We assign the actual UI elements to the script, so that we can reason about their values. They are children of the current GO and are carried with the prefab.

[3] - The action that will do the work.

[4] - This prefab must be spawned by some other system that will wire it properly at setup time. We want to ensure that the element is fully initialized before it starts processing events.

[5] - This is the Initialization method. This sets all the specifics of this UI element (needed in case of my options UI element). It will also take the action that will be executed when the dropdown is changed.

[6] - It could be argued that we didn't need to set the action here and could avoid _isInitialized field. This is true, but it's clearer how it works this way.

[7] - Just setting the values to the dropdown and setting the initial value. In the UI it should be -1.

[8] - This is the problematic point that is the purpose of this article. When setting this value from code, OnValueChanged will always be called, so this will create some endless loops and excessive event calls when setting values (as seen here). Currently there is no way to set the value of the elements without firing the event that would be fired if the value was set from the UI. This can create endless loops of events if multiple things are relying on the same value. So, the event call method must be guarded. 

[9] - If the element is initialized, and the action is assigned then we will call the method that does the work. 

[10] - This is what does the trick when we want to set the value without firing the event. We simply put the event to null for the duration of the assignment and return it afterwards.


If you're finding this article helpful, consider our asset Dialogical on the Unity Asset store for your game dialogues.