Skip to content

Building a menu of delegates and enums

by AngryAnt on March 15th, 2010

I like delegates. I also like enums. Furthermore, I happen to be part of a small group of people who actually enjoy the immediate mode GUI system in Unity. And I want you to like it too :)

So – menus. After having observed quite a few very funky approaches, in the IRC channel, to building main menu functionality and giving advice in there, I decided to sow together those ideas into a short example.

The main idea here is to be able to build and manage a menu system quickly and easily. This line adds a new submenu to the system – designated MyMenuState and implemented in the OnMyMenuGUI method:

m_Menus [MenuState.MyMenuState] = OnMyMenuGUI;

Notice that since we’re using delegates, the GUI method needn’t be implemented in the same script and could even live on an entirely different GameObject. Why is this so clever (besides enums and delegates being cool)? Because managing and switching between menus set up like this is insanely easy. This is the central GUI method for the menu system:

public void OnGUI ()
{
    m_Menus [m_CurrentState] ();
}

And this is GUI code inside a menu GUI method for switching to a different submenu:

m_CurrentState = GUILayout.Button ("Credits") ? MenuState.Credits : m_CurrentState;

So there. Enough talk. Have some example code:

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class Menu : MonoBehaviour
{
    delegate void OnGUIImplementation();
    public enum MenuState {Main,Settings,Credits};
    public struct Settings
    {
        public bool m_ThisSetting, m_ThatSetting, m_TheOtherSetting;
    };
    static Vector2 m_StandardMenuSize = new Vector2 (200.0f, 300.0f);
    static string m_LogoURL = "http://download.unity3d.com/images/top-menu/mm_unity_icon.png";
    static Texture2D m_Logo = null;

    private MenuState m_CurrentState = MenuState.Main;
    private Dictionary<MenuState, OnGUIImplementation> m_Menus;
    private Settings m_Settings;

    void Awake ()
    {
        m_Menus = new Dictionary<MenuState, OnGUIImplementation> ();
        m_Menus [MenuState.Main] = OnMainMenuGUI;
        m_Menus [MenuState.Settings] = OnSettingsGUI;
        m_Menus [MenuState.Credits] = OnCreditsGUI;
        StartCoroutine (GetLogo ());
    }

    IEnumerator GetLogo ()
    {
        WWW www = new WWW (m_LogoURL);

        yield return www;

        if (www.error == null)
        {
            m_Logo = www.texture;
        }
    }

    public void OnGUI ()
    {
        m_Menus [m_CurrentState] ();
    }

    static void BeginStandardMenu (string title)
    {
        GUILayout.BeginArea (new Rect ((Screen.width - m_StandardMenuSize.x) * 0.5f, (Screen.height - m_StandardMenuSize.y) * 0.5f, m_StandardMenuSize.x, m_StandardMenuSize.y));
            GUILayout.BeginVertical (GUI.skin.GetStyle ("Box"));
                GUILayout.Label (title, GUI.skin.GetStyle ("Box"));
    }

    static void EndStandardMenu ()
    {
        Color color;

                GUILayout.BeginHorizontal ();
                    GUILayout.FlexibleSpace ();
                        color = GUI.color;
                        GUI.color = Color.grey;
                        GUILayout.Label ("My game version 2.4X");
                        GUI.color = color;
                    GUILayout.FlexibleSpace ();
                GUILayout.EndHorizontal ();
            GUILayout.EndVertical ();
        GUILayout.EndArea ();
    }

    void OnMainMenuGUI ()
    {
        BeginStandardMenu ("Main menu");
            if (m_Logo != null)
            {
                GUILayout.BeginHorizontal ();
                    GUILayout.FlexibleSpace ();
                        GUILayout.Label (m_Logo);
                    GUILayout.FlexibleSpace ();
                GUILayout.EndHorizontal ();
            }
            if (GUILayout.Button ("New game"))
            {
                Application.LoadLevel ("Level one");
            }
            m_CurrentState = GUILayout.Button ("Settings") ? MenuState.Settings : m_CurrentState;
            m_CurrentState = GUILayout.Button ("Credits") ? MenuState.Credits : m_CurrentState;
            GUILayout.FlexibleSpace ();
        EndStandardMenu ();
    }

    void OnSettingsGUI ()
    {
        BeginStandardMenu ("Settings");
            m_Settings.m_ThisSetting = GUILayout.Toggle (m_Settings.m_ThisSetting, "This setting");
            m_Settings.m_ThatSetting = GUILayout.Toggle (m_Settings.m_ThatSetting, "That setting");
            m_Settings.m_TheOtherSetting = GUILayout.Toggle (m_Settings.m_TheOtherSetting, "The other setting", GUI.skin.GetStyle ("Button"));
            GUILayout.FlexibleSpace ();
            m_CurrentState = GUILayout.Button ("Main menu") ? MenuState.Main : m_CurrentState;
        EndStandardMenu ();
    }

    void OnCreditsGUI ()
    {
        BeginStandardMenu ("Credits");
            GUILayout.Label ("This game was created by some of the most awesomestest people on the planet. Having failed in their mission to Pluto, they started making games, which - as you can see - resulted in much joy and success!");
                GUILayout.Space (20.0f);
                GUILayout.BeginHorizontal ();
                    GUILayout.FlexibleSpace ();
                        GUILayout.Label ("The awesomestest people inc.");
                GUILayout.EndHorizontal ();

            GUILayout.FlexibleSpace ();
            m_CurrentState = GUILayout.Button ("Main menu") ? MenuState.Main : m_CurrentState;
        EndStandardMenu ();
    }
}

Example project: Menu.zip – built with Unity 2.6.1

From → Tips and tricks

8 Comments
  1. Pelle johnsen permalink

    Yeah delegates are nice :)

    Not so sure about enums, it forces a central registry of menu states, maybe just a dictionary with string keys would be more flexible .. as long as you get the keys right :) I guess it’s a matter of taste, enum gives you type safety, a string dictionary removes the requirement for a central registry .. oh well :)

  2. Enums are cheaper too. Not really sure why you’d want to use strings unless you’re going for a run-time generated menu system, in which case you might want to re-evaluate the whole design :)

  3. Pelle johnsen permalink

    More a matter of not having that one central place you have to edit to add new menu items. Anyway I guess for most game menus this won’t be a problem.

    I suppose if you want a multilevel hierarchical menu you would need to enhance the system a bit anyway.

  4. If you’d want branching menus, I’d go with one class per branch – duplicating the script above – giving each branch its own state and list of submenus.

    I don’t see why editing menus in one central location is a bad thing though. If it becomes a real world problem for your menu system then maybe you need to sit down and rethink its design.

  5. Pelle johnsen permalink

    Just speaking out of some bad experiences with central registries in SW (menus, apps, messages, etc.). At least in projects with larger teams, it has always ended up being a problem. Anyway probably not a problem for normal game menus.

  6. WiseMoses permalink

    Good stuff AngryAnt… good stuff. After reading the comments, i don’t know why you wouldn’t want to centralize the menu(through a struct or enum), if one wanted the string thing, just use a struct and add values of string to each var:
    public struct MenuItems
    {
    string g_StartGame = “Start Game”;
    string g_Options = “Game Options”;
    }

  7. Subbu permalink

    hi i am using prime31 uitoolkit.in this toolkit doesn’t have to move another menu.if i use user code it will work but i need to use prime uitoolkit.in this does have any option to call the different GUI method.please help me.

  8. Unfortunately I am not familiar with that toolkit. I would suggest you ask the developer whether the method outlined here integrates with his system.

Leave a Reply

Note: XHTML is allowed. Your email address will never be published.

Subscribe to this comment feed via RSS

*

Spam protection by WP Captcha-Free