UI/WaitForUIButtons

From Unify Community Wiki
Revision as of 15:15, 25 July 2019 by Bunny83 (Talk | contribs)

(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search

Contents

Description

This is a simple CustomYieldInstruction that allows you to wait in a coroutine for a click on one UI.Button out of a list of buttons.

This yield instruction is designed to be reused. Just call "Reset()" before you reuse it. You can pass one or multiple UI.Button instances to the constructor and the yield instruction will automatically registers an internal callback on all those buttons. It also takes care of removing those callbacks once a button was pressed. The yield instruction provides the PressedButton property which will hold the reference to the button that was actually pressed. The yield instruction will simply wait until PressedButton is assigned.

In addition it also has a callback that is directly called from the actual button click event listener. It provides the pressed button as parameter. This feature allows this class to be used even outside a coroutine just to accumulate multiple buttons into a single callback. Just keep in mind that after each button click the callbacks are unregistered and you have to call Reset again.

The class implements the IDisposable interface. This might help to get rid of unwanted hidden references which might keep objects from being garbage collected. Note that once disposed the instance can no longer be reused. It will unregister all callbacks and clears all internal data. There are rare cases where this might be necessary. Even when a coroutine is terminated from outside (StopCoroutine, destroying the object, ...) this shouldn't be a huge issue. As soon as one of the buttons is pressed the class will unregister all callbacks from the buttons anyways. So there should be no risk of piling up dead listeners.

Class Members

- PressedButton (read-only): holds the Button reference of the pressed button.
- Reset() : Re-registers the internal button callbacks and sets PressedButton back to null. This method can savely be called multiple times. It unregisters the callbacks before adding them again. This method is a chain method so for convenience it returns the class instance again so it can be chained. For more information on that, see the examples below.
- ReplaceCallback( callback ) : This method allows to replace the callback that is called when one of the buttons got pressed. This is also a chain method.

Examples

Here are a few example usages:

public Button yesButton;
public Button noButton;
 
IEnumerator Dialog()
{
    // ...
    var waitForButton = new WaitForUIButtons(yesButton, noButton);
    yield return waitForButton.Reset();
    if (waitForButton.PressedButton == yesButton)
    {
        // yes was pressed
    }
    else
    {
        // no was pressed
    }
    // ...
}

Here's an example to just wait and ignoring which of the two buttons was pressed. This could be checked by a manually registered callback in the inspector.

IEnumerator Dialog()
{
    // ...
    yield return new WaitForUIButtons(yesButton, noButton);
    // yes or no got pressed by the user
    // ...
}

You can also provide an array of buttons directly since it's a "params" parameter:

public Button[] buttons;
IEnumerator Dialog()
{
    // ...
    yield return new WaitForUIButtons(buttons);
    // any of the buttons in the buttons array got pressed by the user
    // ...
}

Here's an example with the use of a callback:

public Button[] buttons;
IEnumerator Dialog()
{
    // ...
    yield return new WaitForUIButtons(buttons).ReplaceCallback(b=>Debug.Log("Button with name " + b.name + " got pressed"));
    // ...
}

When you reuse the instance (which is recommended to avoid allocating unnecessary garbage) you have to always call Reset before yielding. Since Reset is a chain method this can be done inplace

public Button[] buttons;
private WaitForUIButtons buttonChoice;
void Start()
{
    buttonChoice = new WaitForUIButtons(buttons);
}
IEnumerator Dialog()
{
    // ...
    yield return buttonChoice.Reset();
    // ...
}

Since we need a seperate closure callback for each button this will allocate memory for each button, there's no way around that. However wne reusing the "WaitForUIButtons" instance, no additional memory should be allocated each time you use it.



WaitForUIButtons.cs

using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;
 
public class WaitForUIButtons : CustomYieldInstruction, System.IDisposable
{
    private struct ButtonCallback
    {
        public Button button;
        public UnityAction listener;
    }
    private List<ButtonCallback> m_Buttons = new List<ButtonCallback>();
    private System.Action<Button> m_Callback = null;
 
    public override bool keepWaiting { get { return PressedButton == null; }}
    public Button PressedButton { get; private set; } = null;
 
    public WaitForUIButtons(System.Action<Button> aCallback, params Button[] aButtons)
    {
        m_Callback = aCallback;
        m_Buttons.Capacity = aButtons.Length;
        foreach(var b in aButtons)
        {
            if (b == null)
                continue;
            var bc = new ButtonCallback { button = b };
            bc.listener = () => OnButtonPressed(bc.button);
            m_Buttons.Add(bc);
        }
        Reset();
    }
    public WaitForUIButtons(params Button[] aButtons) : this(null, aButtons) { }
 
    private void OnButtonPressed(Button button)
    {
        PressedButton = button;
        RemoveListeners();
        if (m_Callback != null)
            m_Callback(button);
    }
    private void InstallListeners()
    {
        foreach (var bc in m_Buttons)
            if (bc.button != null)
                bc.button.onClick.AddListener(bc.listener);
    }
    private void RemoveListeners()
    {
        foreach (var bc in m_Buttons)
            if (bc.button != null)
                bc.button.onClick.RemoveListener(bc.listener);
    }
    public new WaitForUIButtons Reset()
    {
        RemoveListeners();
        PressedButton = null;
        InstallListeners();
        base.Reset();
        return this;
    }
    public WaitForUIButtons ReplaceCallback(System.Action<Button> aCallback)
    {
        m_Callback = aCallback;
        return this;
    }
 
    public void Dispose()
    {
        RemoveListeners();
        m_Callback = null;
        m_Buttons.Clear();
    }
}
Personal tools
Namespaces

Variants
Actions
Navigation
Extras
Toolbox