1
\$\begingroup\$

I am working on my first project in Unity using C# and I am currently trying to design a menu system that does the following sequence of events when a user clicks a button:

  1. Button does some effect for some milliseconds (for example, flashes)
  2. Menu1 does its closing sequence for some milliseconds (for example, buttons fly off screen)
  3. Menu2 does its opening sequence for some milliseconds (for example, buttons fly on screen)

The exact animations of the UI elements don't matter for the purpose of my question.

So far, the only design pattern I've found to achieve this kind of sequence is by using coroutines. So I ended up with something like the following:

void OnStartButtonClick()
{
    StartCoroutine(_OnStartButtonClick());
}

IEnumerator _OnStartButtonClick()
{
    yield return start_button.GetComponent<ButtonController>().DoEffect();
    StartCoroutine(MenuTransition(main_menu, data_menu));
}

IEnumerator MenuTransition(GameObject menu1, GameObject menu2)
{
    // main menu to data select
    if (menu1 == main_menu && menu2 == data_menu)
    {
        yield return CloseMainMenu();
        yield return OpenDataMenu();
    }
}

public Coroutine OpenMainMenu()
{
    return StartCoroutine(_OpenMainMenu());
}

IEnumerator _OpenMainMenu()
{
    main_menu.SetActive(true);
    // main menu opening sequence here
    yield return null;
}

public Coroutine CloseMainMenu()
{
    return StartCoroutine(_CloseMainMenu());
}

IEnumerator _CloseMainMenu()
{
    // main menu closing sequence here
    main_menu.SetActive(false);
    yield return null;
}

public Coroutine OpenDataMenu()
{
    return StartCoroutine(_OpenDataMenu());
}

IEnumerator _OpenDataMenu()
{
    data_menu.SetActive(true);
    // open data menu sequence
    yield return null;
}

public Coroutine CloseDataMenu()
{
    return StartCoroutine(_CloseDataMenu());
}

IEnumerator _CloseDataMenu()
{
    // data menu closing sequence here
    data_menu.SetActive(false);
    yield return null;
}

And the code on my button likewise utilizes coroutines:

public Coroutine DoEffect()
{
    return StartCoroutine(_DoEffect());
}

IEnumerator _DoEffect()
{
    // imagine some code that does the effect
    yield return new WaitForSeconds(1f);
}

This seems to work well enough and allows me to completely control the effect-->close-->open sequence. But it's getting a bit unwieldy as I build it out for more menus and transitions between them, resulting in a ton of methods with Coroutine return type wrapping IEnumerators (which I've been prefixing with underscore). I can't help but feel like there's a more elegant way to do this, and that I am using coroutines as a crutch ...

So my question is whether there is a more suitable or efficient design pattern that can allow me to achieve the kind of sequencing of events that I want without the proliferation of Coroutines & IEnumerators? Or are Coroutines & IEnumerators exactly the devices to solve this kind of problem?

\$\endgroup\$
3
  • 1
    \$\begingroup\$ Coroutines are a pretty solid method. Honestly there are a lot of ways you could solve this. I normally use work with threads so I have classes that just store a queue of functional classes to call in specific orders (also unweildy but nessecary in my case). One alternative I've heard good things about are using promises (gist.github.com/cuppster/3612000). I can speak much on it since I haven't tried it but usually you execute your logic with code like: menuSystem.CloseMain().OpenMenu(inventoryMenu). \$\endgroup\$ Commented Sep 28, 2020 at 6:41
  • 1
    \$\begingroup\$ Honestly though I think your current method is probably fine but you might be able to simplify things with interfaces and abstract classes. If your menues implement a common interface you could probably just have a MenuManager class that handles all of the boiler plate stuff like flashing elements or closing old menus. \$\endgroup\$ Commented Sep 28, 2020 at 6:44
  • \$\begingroup\$ Just separate/abstract it to be reusable for every button/transition. I have done more or less this plus sending a delegate to menu2 that it can use as a back button handler reversing the transition back. \$\endgroup\$ Commented Sep 29, 2020 at 9:27

1 Answer 1

1
\$\begingroup\$

The main thing you should change is not hardcoding references to specific menus.

public class Menu : MonoBehaviour {
    public IEnumerator Open() {
        //animation code here
    }
    public IEnumerator Close() {
        //animation code here
    }
}

IEnumerator MenuTransition(Menu menu1, Menu menu2)
{
    yield return StartCoroutine(menu1.Close());
    yield return StartCoroutine(menu2.Open());
}

The above solution lets us open and close any combination of menus without needing to write separate functions for each menu.

\$\endgroup\$

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.