spacebruce.netlify.app

/notes/gm_menu/

Notes
← Previous Mahou Shoujo game design doc
→ NextKNIFEMAN VCS Game
Tagsnotes gamemaker gamedev lazy
Posted2022-08-29
Updated-

Lazy gamemaker placeholder menu


I needed a quick debug menu for a game project, threw this together in an hour or so, no warranty

preview image
looks like this.

Has sliders, toggles, simple buttons, dividers and text labels. Holler at me on twitter if you need any help with it.

Sample usage #

Make an object, embed this in an already existing object, whatever.

Create Event #

	MainMenu = new MenuBody();	//Create the menu like this

	PageMain = MainMenu.AddPage();	//Add your pages
	PageContinue = MainMenu.AddPage();
	PageLoad = MainMenu.AddPage();
	PageNew = MainMenu.AddPage();
	PageOptions = MainMenu.AddPage();
	PageQuit = MainMenu.AddPage();

	//Front page
	PageMain.Add(new MenuButton("Continue", noone));		//Page dot Add every element
	PageMain.Add(new MenuButton(
	"Load", 									//Name/Label of element
	function() { MainMenu.Goto(PageLoad); })	//Function to call when pressed can be inline or defined in a script elsewhere, you decide
	);											//Menu has a .Goto function to assist in switching between submenus
	PageMain.Add(new MenuButton("New Game", function() { MainMenu.Goto(PageNew); }));
	PageMain.Add(new MenuDivider());			//Dividers just draw a line, for breaking up lists I guess.
	PageMain.Add(new MenuButton("Options", function() { MainMenu.Goto(PageOptions); }));
	PageMain.Add(new MenuButton("Quit", function() { MainMenu.Goto(PageQuit); }));
	//Load game
	PageLoad.Add(new MenuButton("Back", function() { MainMenu.Goto(PageMain); }));
	//New Game
	PageNew.Add(new MenuButton("Back", function() { MainMenu.Goto(PageMain); }));
	//Options
	PageOptions.Add(new MenuText("Video"));
	PageOptions.Add(new MenuToggle("Fullscreen",						// Toggle item
		function(Active)	{ 		window_set_fullscreen(Active);	}, 	// Function on toggle (state passed via bool argument)
		window_get_fullscreen())										// Default argument, stored and flipped when pressed
		);
	PageOptions.Add(new MenuDivider());
	PageOptions.Add(new MenuText("Sound"));
	PageOptions.Add(new MenuSlider("Volume",										// Slider item
		0,100,50,																	// Min,Max, Default value
		function(Volume) { show_debug_message("Volume : " + string(Volume)); })		//Function called on change, value passed via a float argument
	);
	PageOptions.Add(new MenuDivider());
	PageOptions.Add(new MenuButton("Back", function() { MainMenu.Goto(PageMain); }));
	//Quit
	PageQuit.Add(new MenuButton("Back", function() { MainMenu.Goto(PageMain); }));
	PageQuit.Add(new MenuButton("Close game", function() { game_end(); }));

Step event #

	//Link menu actions to user input like this, all behaviour is self contained
	if(keyboard_check_pressed(vk_up))
		MainMenu.Up();
	if(keyboard_check_pressed(vk_down))
		MainMenu.Down();
	if(keyboard_check(vk_left))
		MainMenu.Left();
	if(keyboard_check(vk_right))
		MainMenu.Right();
	if(keyboard_check_pressed(vk_enter))
		MainMenu.Use();
	if(keyboard_check_pressed(vk_backspace))
		MainMenu.Back();

Destroy (or cleanup?) event #

	//Is there a better way of doing this? I don't think you can delete self;
	MainMenu.Destroy();
	delete MainMenu;

Draw event #

	//				x, y, width
	MainMenu.Draw(64,64,128);

Source listing #

Deposit the following in it's entirety into a script file

	//Elements
	function MenuElement(Name = "", Action = noone) constructor
	{
		String = Name;
		Callback = Action;
		Selectable = true;
		Draw = function(dx,dy,width) { return 0; };
		Use = function() {};
		Left = function() {};
		Right = function() {};
		LineHeight = string_height("X");
	}
	function MenuButton(Name, Action) : MenuElement(Name, Action) constructor
	{
		Use = function()
		{
			if(Callback != noone)
				Callback();
		}
		Draw = function(dx,dy,width)
		{
			draw_text_ext(dx,dy,String,LineHeight,width);
			return string_height(String);
		}
	}
	function MenuToggle(Name, Action, DefValue) : MenuElement(Name, Action)  constructor
	{
		Active = DefValue;
		Use = function()
		{
			Active = !Active;
			if(Callback != noone)
				Callback(Active);
		}
		Draw = function(dx,dy, width)
		{
			draw_rectangle(dx,dy,dx + (LineHeight - 2), dy + LineHeight, true);
			if(Active)
				draw_rectangle(dx + 2,dy + 2,(dx + (LineHeight - 2)) - 2, (dy + LineHeight) - 2, false);
			draw_text(dx + LineHeight, dy, String);
			return string_height(String);
		}
	}
	function MenuSlider(Name, Min, Max, DefValue, Action) : MenuElement(Name, Action)  constructor
	{
		Value = DefValue;
		MinValue = min(Min,Max);	MaxValue = max(Min,Max);
		Left = function(Speed = 1)
		{ 
			Value = max(Value - Speed,MinValue);	
			if(Callback != noone)
				Callback(Value);
		};
		Right = function(Speed = 1)
		{	
			Value = min(Value + Speed, MaxValue);	
			if(Callback != noone)
				Callback(Value);
		};
		Draw = function(dx,dy,Width)
		{
			draw_text(dx,dy,String);
			draw_rectangle(dx,dy+LineHeight,dx+Width,dy+(LineHeight*2),true);
			var percent = (Value - MinValue) / (MaxValue - MinValue);
			var position = dx + 2 + ((Width - 4) * percent);
			draw_rectangle(position-2,dy+LineHeight,position+2,dy+(LineHeight*2),true);
			draw_text(dx,dy+(LineHeight), string(Value));
			return LineHeight*2;
		}
	}
	function MenuDivider() : MenuElement()  constructor
	{
		Selectable = false;
		Draw = function(dx,dy,width)
		{
			draw_line(dx,dy + 5, dx + width, dy + 5);
			return 10;
		}
	}
	function MenuText(Name) : MenuElement(Name)  constructor
	{
		Selectable = false;
		Draw = function(dx, dy, width)
		{
			draw_text_color(dx,dy,String,c_red,c_red,c_red,c_red,1.0);
			return string_height(String);
		}
	}
	function MenuPage() constructor
	{
		Elements = ds_list_create();
		Index = -1;
		Selected = -1;
		static Left = function()
		{
			Elements[| Selected].Left();
		}
		static Right = function()
		{
			Elements[| Selected].Right();
		}
		static Up = function()
		{
			var lastgood = Selected;
			do
			{
				--Selected;
			} until ((Selected == -1) || Elements[| Selected].Selectable)
			if(Selected == -1)
			{
				Selected = lastgood;
			}
		}
		static Down = function()
		{
			var length = ds_list_size(Elements);
			var lastgood = Selected;
			do
			{
				++Selected;
			} until ((Selected == length) || Elements[| Selected].Selectable)
			if(Selected == length)
			{
				Selected = lastgood;
			}
		}
		static Add = function(Element)
		{
			ds_list_add(Elements, Element);
			if(Selected == -1 && Element.Selectable)	//If select pointer not preset to a good target, use this
				Selected = ds_list_size(Elements)-1;
		}
		static Use = function()
		{
			if(Elements[| Selected].Use != noone)
				Elements[| Selected].Use();
		}
		static Destroy = function()
		{
			for(var i = 0; i < ds_list_size(Elements); ++i)
			{
				delete Elements[| i];
			}
			ds_list_destroy(Elements);
		}
		static Draw = function(dx,dy,width)
		{
			var _y = dy;
			for(var i = 0; i < ds_list_size(Elements); ++i)
			{
				var starty = _y;
				_y += Elements[| i].Draw(dx,_y,width);
				if(Selected == i)
					draw_rectangle(dx-2,starty-2,dx + width + 2, _y + 2, true);
			}
		}
	};
	function MenuBody() constructor
	{
		Pages = ds_list_create();
		PageOn = -1;
		PageHistory = ds_queue_create();	//Keep track of previously visited pages
		static Up = function()	//Interaction
		{
			if(PageOn == -1)
				return;
			Pages[| PageOn].Up();
		}
		static Down = function()
		{
			if(PageOn == -1)
				return;
			Pages[| PageOn].Down();
		}
		static Left = function()
		{
			if(PageOn == -1)
				return;
			Pages[| PageOn].Left();
		}
		static Right = function()
		{
			if(PageOn == -1)
				return;
			Pages[| PageOn].Right();
		}
		static Use = function()
		{
			if(PageOn == -1)
				return;
			var PageChange = PageOn;
			Pages[| PageOn].Use();
			if(PageChange != PageOn)	//if page changed
			{
				if(ds_queue_tail(PageHistory) != PageChange)	//if returning to prevous page, add to history
					ds_queue_enqueue(PageHistory, PageChange);
				else
					ds_queue_dequeue(PageHistory);	//else remove item from history
			}
		}
		static Back = function()
		{
			if(ds_queue_size(PageHistory) > 0)
				PageOn = ds_queue_dequeue(PageHistory);
		}
		static Destroy = function()
		{
			ds_queue_destroy(PageHistory);
			for(var i = 0; i < ds_list_size(Pages); ++i)
			{
				Pages[| i].Destroy();
				delete Pages[| i];
			}
			ds_list_destroy(Pages);
		}
		static AddPage = function()		//Interface
		{
			var Page = new MenuPage();
			ds_list_add(Pages, Page);
			var size = ds_list_size(Pages);
			Page.Index = (size - 1);
			if(size == 1)
				PageOn = 0;
			return Page;
		}
		static Draw = function(dx,dy,width)
		{
			if(PageOn == -1)
				return;
			Pages[| PageOn].Draw(dx,dy,width);
		}
		static Goto = function(Location)
		{
			PageOn = Location.Index;
		}
	}