Building a menu of delegates and enums
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:
{
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:
public class Menu : MonoBehaviour
{
delegate void OnGUIImplementation();
{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;
{
m_Menus = new Dictionary<MenuState, OnGUIImplementation> ();
m_Menus [MenuState.Main] = OnMainMenuGUI;
m_Menus [MenuState.Settings] = OnSettingsGUI;
m_Menus [MenuState.Credits] = OnCreditsGUI;
StartCoroutine (GetLogo ());
}
{
WWW www = new WWW (m_LogoURL);
yield return www;
if (www.error == null)
{
m_Logo = www.texture;
}
}
{
m_Menus [m_CurrentState] ();
}
{
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"));
}
{
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 ();
}
{
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 ();
}
{
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 ();
}
{
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
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
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
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.
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.
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.
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”;
}
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.
Unfortunately I am not familiar with that toolkit. I would suggest you ask the developer whether the method outlined here integrates with his system.