Rightclick context menu with submenu

danerican

Member
Hi. I want to rightclick my character and change it's clothes and hair from a text based context menu.
I have searched for this alot but found nothing matching what I need, and I do not have required knowledge to piece together myself from different tutorials, , so I'm sorry if this has indeed been solved here already.
I have followed tutorials for a rightclick menu like this one
and submenus like one
but can't find anything specific to what I want, combining the two.

I can now right click my character, and a menu with different clothing "red jacket" "blue jacket" and so on, shows up and when I click one, it changes. Great.
But I need submenus. So when I rightclick I need it to say "Clothes", "Hair" and so on, and when I click one, then change the buttons to say things like "Red jacket" "blue jacket" and so on.

I have used the methods from this video, so perhaps I can expand on that to include submenus??

I am brand new at this, so please explain like I'm 5
THANK YOU VERY MUCH FOR ANY HELP
 
You're brand new to Gamemaker? Or new to making menus?
How much coding experience do you have with GM?
To achieve what you're after will involve ds lists and arrays for something reasonable, there's also a bunch of other factors too depending on how in-depth this menu system is going to be.

If you're finding video tutorials helpful I can suggest Friendly Cosmonaut to give you a professional example of a menu system which includes submenus

If you can understand that, you'll find it easy to alter the code to work with mouse button presses and your own custom GUI layer.

Probably not the help you're expecting but menus in general aren't as straightforward as 'mix these ingredients in a bowl then chuck in the oven for an hour and you'll have made 12 cupcakes'

...unless it's a simple pause menu
 
Last edited:

danerican

Member
I'm new to both Gamemaker, making menus, and coding in general, so I have none coding experience with GM or anything else, other than some tutorials. Thanks, I will check out Friendly Cosmonaut. But like I said, I got a simple menu to work, which changes clothes, I just need sublevels for this menu. Thank you for reply
 
I'm new to both Gamemaker, making menus, and coding in general, so I have none coding experience with GM or anything else, other than some tutorials. Thanks, I will check out Friendly Cosmonaut. But like I said, I got a simple menu to work, which changes clothes, I just need sublevels for this menu. Thank you for reply
I'm no expert myself just trying to fill in for the pros to help them save repeating the same replies.

It's great you got something to work. For a submenu you essentially want to place your menu withing your menu. Like I said, a ds list and arrays will help you with that.

If you require actual assistance I'd advise showing code to your menu setup followed by any code showing how you've attempted making a submenu. Then you'll likely get better guidance.

Lastly but above all, the first thing you should look at is the software Manual because there's so much time and frustration you can save yourself just by looking it up. And if you don't understand some of the stuff in it ( some of the examples used aren't the greatest) use the search function on this forum, type in the function or variable or whatever in there and dive into the many threads.

Then if you're still struggling, come back here and say so. Best if luck and welcome to the community
 

TailBit

Member
I was unsure how you meant, did you want to have a submenu open beside it, going from the option you selected..

I would have changed it around a bit, create the menu by doing it a bit different .. have a script split a string into objects that is its own menu button

lets say you have the enum MENU {first,hair,body} somewhere

GML:
// menu_create(x,y,array,action,submenu index)
var xx = argument0,yy =argument1,array = argument2,action = argument3,sub = argument4; // sub should always be 0 the first time you create it

with(obj_menu) if id.sub >= sub instance_destroy(); // if there exist a sub menu with the same submenu index then destroy them all

for(var i=0;i<array_lenght_1d(array)){
    var ins = instance_create_depth(mouse_x,mouse_y,-10,obj_menu);
    ins.text= array[i]; // give it a array list like this
    ins.action = action;
    ins.index = i;
    ins.sub = sub;
}
in the draw event of obj_menu just draw_self() and draw_text(x,y,text)

make the menu with
menu_create(x,y,["Hair","Body"],MENU.first,0)
when you click on something

so .. then .. the step event
GML:
//step begin event event:
on_menu = position_meeting(mouse_x, mouse_y, object_index);
GML:
if mouse_check_button_pressed(mb_left){
    swith(action){
        case MENU.first:
            switch(text){
                case "hair": menu_create(x+sprite_width,y,["yellow","brown","red"],MENU.hair,1); break;
                case "body": menu_create(x+sprite_width,y,["sweater","tshirt","Vneck"],MENU.body,1); break;
            }
            break;

        case MENU.hair:
            var hairtypes = [spr_hair_yellow,spr_hair_brown,spr_hair_red];
            obj_player.hair = hairtypes[index]; // we gave all a index based on what order they were created in
            instance_destroy(obj_menu); // destroy all menus
            break;

        case MENU.body:
            var cloths = [spr_sweater,spr_tshirt,spr_vneck];
            obj_player.body = cloths[index]; // we gave all a index based on what order they were created in
            instance_destroy(obj_menu); // destroy all menus
            break;
    }
    if !on_menu instance_destroy();
}
with that you should be able to create even deeper menus

when doing a menu like that, then have one array that tells what menu to display, when going one deeper into the sub menu, then store what selection you did for that menu, and then set what menu to display on the next

what I do is drawing the options using a loop .. for the current submenu ..

then adjust pos so that the next draw cycle will start at the height of the last selection we did

if there is another submenu open then start drawing through it,

create
GML:
cur_sub = 0; // the list with "hair" and "body"
select = 0; // above option 0 in that list .. "hair"

sub = 0;
submenu[sub] = 0; // submenu 0 should draw the content in menu[0]
selected[sub] = 0;

meny[0] = ds_list_create();
meny[1] = ds_list_create();
meny[2] = ds_list_create();

ds_list_add(meny[0],"Hair","Body")
ds_list_add(menu[1],"hair1","hair2","hair3");
ds_list_add(menu[1],"t-shirt","sweater","V-neck");
step
GML:
if mouse_check_pressed(mb_left)
if cur_sub!=noone
switch(submenu[cur_sub]){
    case 0:
        switch(select){
            case 0: submenu[1] = 1; break;
            case 1: submenu[1] = 2; break;
        }
       
        selected[0] = select;
        sub=1
        selected[sub] = 0; // just to give the new menu a default value
       
        exit; // to avoid reaching instance_destroy()
    case 1:
        var hair = [spr_hair1,spr_hair2,spr_hair3];
        obj_character.hair = hair[select];
        break;
    case 1:
        var cloth = [spr_tshirt,spr_sweater,spr_vneck];
        obj_character.body = cloth[select];
        break;
}
instance_destroy()
GML:
cur_sub = noone;
select = noone

var s,i,list,xx,yy,ww=80,hh=14,pos = 0; // adjust size here
for(s = 0;s<sub;s++){
    list = menu[submenu[sub]];
    for(i=0;i<ds_list_size(list);i++){
        xx = x+s*ww;
        yy = y+i*hh + pos;
       
        if point_in_rectangle(mouse_x,mouse_y,xx,yy,xx+ww,yy+hh){
            cur_sub = s;
            select = i;
        }
       
        draw_sprite(sprite_index,0,xx,yy) // need to change to a 80x14 sprite?
        draw_text(xx,yy,list[| i]);
    }
   
    pos += selected[s] * hh;
}
cleanup event:
GML:
for(var i=0;i<array_lenght_1d(menu);i++) ds_list_destroy(meny[i]);
this post got long xD
 

danerican

Member
Wow thank you so much for the in depth reply Tailbit, much appreciated - I will try it out now and get back.
I want the submenu to appear instead of the first one, at the same location, so if I click "hair" the button options should just change instead of a new menu appearing beside it
 

TailBit

Member
Well, then it's less difficult, but if you still use the menu_create script I made here, then you can simply:
- find the top menu button (should have index 0)
- store its x and y temporarily
- delete all menu buttons
- and use menu_create on the x and y you stored

Inside the step event switch code when you to replace the menu with a new one
GML:
var yy=y;
with(obj_menu) if index == 0 { yy=y; break; }
instance_destroy(obj_menu)
menu_create(x,yy,["yellow","brown","red"],MENU.hair,0) // the sub part don't matter then
.. or ..

Could change the menu_create script, so it will destroy all the menus that got a smaller sub, still find the obj_menu with index 0 and create menu from it, and use a +1 higher sub number

In the script:
Code:
with(obj_menu) if id.sub < sub instance_destroy();
Somewhere in switch code in step event
Code:
with(obj_menu) if (index == 0) menu_create(x,y,["yellow","brown","red"],MENU.hair,sub+1)
 

danerican

Member
Here is what I have working:
I right click my character and a small context menu pops up with a few choices. So when I click one, I want the options to change.

GML:
Player Character Object:

Name:    oChar
Events:

Create:   
    buttons = ds_list_create();
    ds_list_add(buttons,cMenu.char_ChangeClothes,cMenu.char_ChangeHair);
    
Mouse Right Pressed:
    cMenu_showbuttons(buttons,id);
Code:
Menu Init Object:

Name:    obj_cMenuInit
Events:

Create:
    enum cMenu
    {
    char_ChangeHair = 0,
    char_ChangeClothes = 1,
    }

    global.cMenu_grid = ds_map_create();

    //Populate our ds_map
    global.cMenu_grid[? cMenu.char_ChangeHair] = "Change Hair";
    global.cMenu_grid[? cMenu.char_ChangeClothes] = "Change Clothes";
Code:
Menu Button Object:

Name:    obj_cMenuButton
Events:

Create:
    image_speed = 0;
    
Step:
    if point_in_rectangle(mouse_x,mouse_y,x,y,x+sprite_width,y+sprite_height-1)
    
    {
    image_index = 1;
    draw_set_color(c_black);
    }
    else
    {
    image_index = 0;
    draw_set_color(c_white);
    }
    
Draw GUI:
    draw_self()
    if point_in_rectangle(mouse_x,mouse_y,x,y,x+sprite_width,y+sprite_height-1)
    {
    draw_set_color(c_white);
    }
    else
    {
    draw_set_color(c_black);
    }
    draw_set_font(font0);
    draw_text(x+5,y,global.cMenu_grid[? action])


Mouse Left Pressed:

    switch(action)
    {
    case cMenu.char_ChangeHair:
    oChar.image_index = 1;
    break;
    
    case cMenu.char_ChangeClothes:
    oChar.image_index = 2;
    break;
    }

    with(obj_cMenuButton)
    {
    instance_destroy();   
    }
I have attached a screnshot of what it looks like now. Oh and the character is just a placeholder xD
How would I go about changing my above code to allow submenu? THANK YOU VERY MUCH
 

Attachments

TailBit

Member
I trashed the Init object, just have a script create all the buttons right away

But yeah, in this case then creating submenu is basically just destroying the old menu and then create a new one where it was .. since we are using a button method here .. it works .. but there are many ways around this

I'm trying to give you a version without ds_ functions and using arrays instead, as arrays don't cause memory leaks if you forget to get rid of them ..

oChar right pressed:
Code:
menu_create(mouse_x,mouse_y,["hair","body"],cMenu.char_Choices)
menu_create script:
Code:
// menu_create(x,y,array with strings,action)
var array = argument2;
with(obj_cMenuButton) instance_destroy();

for(var i = 0;i<array_length_1d(array);i++){
    var ins = instance_create_depth(argumen0,argument1 + i*16,-100,obj_cMenuButton) // I do not know the height of it .. so change 16 to what you need
ins.xstart = argument0; // I store the position you told the script to create the menu in their x/ystart variable, that way you can use it when creating a list in the same position
    ins.ystart = argument1;
    ins.text = array[i];
    ins.index=i;
    ins.action = argument3;
}

obj_cMenuButton create:
GML:
enum cMenu { char_Choices,char_ChangeHair,char_ChangeClothes }

image_speed=0;
text = "";
index = 0;
step event:
// point_in_rectangle returns true 1 or false 0
image_index = point_in_rectangle(mouse_x,mouse_y,x,y,x+sprite_width,y+sprite_height-1);

draw gui:
Code:
draw_self()
draw_set_color( c_white * image_index );
// if multiplied with 1 then it is the same number, if multiplied with 0 then it becomes 0, which is the value for black
draw_text(x+5,y,text)

mouse_left pressed:
Code:
with(obj_cMenuButton) instance_destroy();

switch(action){
    case cMenu.char_Choices:
        switch(index){
            case 0: menu_create(xstart,ystart,["hair0","hair1","hair2"],cMenu.char_ChangeHair); break;
            case 1: menu_create(xstart,ystart,["cloth0","cloth1","cloth2"],cMenu.char_ChangeHair); break;
        }
    break;

    case cMenu.char_ChangeHair:
        oChar.image_index = index; // I would give a different variable to hold the index for his hair and cloth
    break;

    case cMenu.char_ChangeClothes:
        oChar.image_index = index;
    break;
}

Hope some of this makes sense ..
 
Last edited:

danerican

Member
I wrote "char" instead of "case" .. edited the post .. anyway, going now :3 GL
Hey Tailbit, this worked perfectly, exactly what I wanted. Thank you so much for the effort!
But how do I make the menu go deeper than the current 2 levels? So for example if I click hair, it should say "length" "color" and so on and then give me more options after I click one, and then in Length it should for example say "short" "med" "long", so I go deeper in the menu before actually choosing/applying a sprite, and maybe different depths into the menu, depending on whether I choose hair, body, or whatever. Hope that description made sense. I tried a many different things but I can't seem to get it right? Thanks
 
Last edited:

TailBit

Member
Well, at this point the code in "case cMenu.char_Choices:" checks what index the button you press is and opens a new submenu depending on it, so you could pretty much copy that into one of the other cases and just edit it to how you want it to look, add more enums so you can filter their actions better ..

so it start by char_Choices .. its case takes you to char_ChangeHair .. then you have char_ChangeHair's case send you to new menu that you make a enum for

if all of the menus should have pre determined content, then maybe an menu like the tutorial showed would have been better:
(note that this code is incomplete, no menu destruction if you press outside, only destroys old menus when going deeper)

I actually recommend using the 2nd tutorial you posted and just edit it to make menu buttons instead, because I went overboard with the complexity here.

so let's bring obj_cMenuInit back, create event:
Code:
enum cMenu {char_Choices,char_ChangeHair,char_ChangeHairLength}

submenu = noone;
old_submenu = submenu;

menu[cMenu.char_Choices] = [1,"Hair",cMenu.char_ChangeHair,"Body",cMenu.char_ChangeBody] // the 1 in the beginning should tell that the menu contains pairs of strings and which menu they should take you to
menu[cMenu.char_ChangeHair] = [1,"length",cMenu.char_ChangeHairLength,"color",cMenu.char_ChangeHairColor]
menu[cMenu.char_ChangeHairLength] = [0, "1","2","3"] // a menu with 0 in the beginning got only strings and you have to use a switch event to choose what to do with the result
step event:
Code:
if submenu != old_submenu {
    var array = menu[submenu];
    var inc = 1+menu[0];
    var row = 0;
    for(var i=1;i+inc-1<array_length_1d(array);i+=inc){
        var ins = instance_create_depth(x,y + i*16,-100,obj_cMenuButton) // I do not know the height of it .. so change 16 to what you need
        ins.text = array[i];
        ins.index = i;
        ins.row=row;
        ins.submenu = submenu;
        row++;
    }
}
obj_cMenuButton press event:
Code:
var array =obj_cMenuInit.menu[submenu];

if array[0]{ // if the first position in the array is true, then change menu automatically on press
    obj_cMenuInit.submenu = array[index+1];
}else // if not then we have to resort to the switch statement and deal with it
switch(submenu){
    case cMenu.char_ChangeHairLength:
        oChar.hairLength = index + 1;
obj_cMenuInit.submenu = cMenu.char_ChangeHair; // you could have it send you back to the previous menu
       // instance_destroy(obj_cMenuInit) // or destroy the menu .. let menu destroy code destroy buttons
    break;
}
then there is the script to start the menu:
menu_create(mouse_x,mouse_y,cMenu.char_Choices)

it is simply:
Code:
var ins = instance_create_depth(argument0,argument1,-100,obj_cMenuInit);
ins.submenu = argument2;
 

danerican

Member
Thank you Tailbit.
how do I add submenus to the code you gave me? Your code works perfectly with 1 submenu but I cannot figure how to add more. I tried many things, including the attached screenshot. As you can see, when I click 'case 1' I want to enter a submenu for each of the clothing items. Right now, all buttons under 'case 1' calls the same thing under cMenu.Char_ChangeOutfit, I can't figure how to differentiate these. Thanks
Again, sorry I’m still very much learning
 

Attachments

Last edited:

TailBit

Member
case cMenu.char_ChangeOutfit:
You are not checking index here, each button that is made from the array will have a index where the first string will be "Torso" = 0 .. "Glasses" = 1 and so on, but I guess you could check the text of them instead:

so you need to add a whole switch event into that:
GML:
switch(index){
    case 0: // "Torso"

    break;
// ... and all the way to ...
    case 4: // which would be "Randomize"

    break;
}

// or by checking the text of the button instead of the index .. maybe it makes more sense .. be aware that they are case sensitive

switch(text){
    case "Torso":

    break;
// ... all the way to ...
    case "Randomize":

    break;
}
and atm you are using the same enum that the menu it is in, making all the buttons just recreate the buttons if you press them .. so you might need some more enums

And please stop posting code in images, I could save a lot of time if you just did some copypasta magic, and then you would make the forum guidelines happy too.
 

danerican

Member
It worked perfectly.
Thank you so much Tailbit, you've been most helpful and patient, I must say I did not expect this much effort and patience, with all the specific code and comments inside and everything, very much appreciated!
Oh and thanks for the heads up regarding posting code in images:)
 
Top