• Hey! Guest! The 40th (!!!) GMC Jam will take place between February 25th, 12:00 UTC to March 1st 12:00 UTC. Why not join in this very special anniversary jam! Click here to find out more!

GMS 2.3+ Help with Settings Menu

BQubed

Member
I'm struggling with what should be easy but isn't for me. I have a real problem wrapping my head around arrays. Visualization issue I think. Currently I have everything functioning as it should with the exception of settings values. I can increment them but they ALL increment. I understand why this is happening but I simply don't know how to fix it. I'm going to link all my menu code because it's not much.

GML:
//Main Menu
menu[0,0] = "New Game";
menu[0,1] = "Continue";
menu[0,2] = "Settings";
menu[0,3] = "Quit";

//Settings Menu
menu[1,0] = "Sound FX";
menu[1,1] = "Music";
menu[1,2] = "Text Speed";
menu[1,3] = "Punctuation Pauses";
menu[1,4] = "Back";

//Settings Values
values[0] = ["10","20","30","40","50","60","70","80","90","100"];
values[1] = ["10","20","30","40","50","60","70","80","90","100"];
values[2] = ["Slow","Medium","Fast","Instant"];
values[3] = ["On","Off"];
values[4] = [];   

index = 0;
sub_menu = 0;
value = 0;

GML:
//Input
var _key_right = keyboard_check_pressed(ord("D"));
var _key_left = keyboard_check_pressed(ord("A"));
var _key_down = keyboard_check_pressed(ord("S"));
var _key_up = keyboard_check_pressed(ord("W"));
var _key_select = keyboard_check_pressed(ord("E"));

var _move = (_key_down - _key_up)
var _modify = (_key_right - _key_left)
    
if (_move !=0)
{
    index += _move;
    
    //Loop through menu
    var _size = array_length(menu[sub_menu])
    if (index < 0) index = _size -1;
    else if (index >= _size) index = 0;
}

if _key_select
{
    switch(sub_menu)
    {
        case 0: switch(index)
        {
            case 0: room_goto(rCaraBedroom); break; //new game
            case 1: show_message("Continue not yet coded"); break; //continue
            case 2: sub_menu = 1; index = 0; break; //settings
            case 3: game_end(); break; //quit
        }
                break;
    }
    switch(sub_menu)
    {
        case 1: switch(index)
        {
            case 4: sub_menu = 0; index = 0; break;//back
        }
        break;
    }
    
}
if _key_right or _key_left
{
    switch(sub_menu)
    {
        case 1: switch(index)
        {
            case 0: value += _modify; break; //sound
            case 1: break; //music
            case 2: break; //Text speed
            case 3: break; //punctuation     
        }
        break;
    }
    
}

GML:
DrawSetTextMainMenu();

var _gap = 40;
var c = c_white;

//Draw Menu/Settings
for (var i = 0; i < array_length(menu[sub_menu]); i++)
{
    if i == index { c = c_orange; }
    else { c = c_white }

    draw_text_color(145,70+(_gap * i),menu[sub_menu, i],c,c,c,c,1);
}
//Draw Values
if sub_menu == 1
{
    for (var i = 0; i < 4; i++)
    {
        if i == index { c = c_orange; }
        else { c = c_white }
        draw_text_color(350,70+(_gap * i),values[i,value],c,c,c,c,1);
    }
}

So I know that I all the setting values are changing because of case 0: value += _modify; break; //sound in the step event. I tried giving each setting it's own variable rather than making them all value[0] = etc... but I still can't get it working properly. I'm pretty sure I need to have a separate array for selections or something like that but I just can't seem to get it to work.
 
Menus are my thing, so I can chime in on this. And, no they are not "easy", they can be quite complicated and involved!
Arrays are nice for looping stuff, and storing lists, they are the go-to.
For GUI, tho, you'd bemuch better off with structs. Much more versatile, readable, easy to set/get with the $ accessor, you could define bounding boxes, label, color, shape and behavior of each thing.
This is a very good read.
With a little brain grease, you can adjust it to your needs. For example, here I made the boxes stretch to the label length, changed the color/alpha when hovering with the mouse, added user events, use inheritance of structs, etc...
One single object will handle all your menu needs, no problems, no dozens of buttons objects. Pretty simple, and reusable across every single one of your projects!
Textfields, checkboxes, drop down menus, you name it.
This is the code for all the thing
GML:
// create button
//arguments:  (_name, _x, _y, _user_event, _col_text, _col_box, _box_alpha_unhovered, _box_alpha_hovered, _col_text_hover, _col_box_hover, _hover_offset_box_x, _hover_offset_box_y, _hover_offset_text_x, _hover_offset_text_y, _text_size_to_box, _font)
button = new Button("This is a long ass button that stretches automatically", 16, 16, 10, c_white, c_black, 0.8, 1, c_white, c_black, 0, -4, 0, -4, true, fntInput);
button = new Button("This is a long ass button that stretches automatically, but the colors are different", 16, 92, 10, c_black, c_lime, 0.8, 1, c_black, c_green, 0, -4, 0, -4, true, fntInput);
button = new Button("OK", 16, 160, 10, c_black, c_white, 0.8, 1, c_white, c_black, 0, -4, 0, -4, true, fntInput);



// create checkboxes
//arguments: (_name, _x, _y, _checked, _user_event, _col_text, _col_outer_box, _col_inner_box, _box_alpha_unhovered, _box_alpha_hovered, _font, _space)
checkbox1 = new Checkbox("Some Checkbox", 16, 264, false, 0, c_white, c_black, c_lime, 0.7, 1, fntInput, 2);
checkbox2 = new Checkbox("Some Checkbox 2", 16, 312, true, 10, c_white, c_black, c_red, 0.7, 1, fntInput, 2);
checkbox3 = new Checkbox("Some Checkbox 2", 16, 360, true, 2, c_white, c_black, c_yellow, 0.7, 1, fntInput, 2);
 
Last edited:

Nidoking

Member
It appears that you're trying to track four different values in one variable, which you have called "value". One variable (other than an array or data structure) can store one value. If you want to store more than one value, then yes, you will need an array or data structure. But rather than using arrays, you might actually find it easier to use structs. You can give the elements names instead of numbers, which may help you keep track of what means which and where. At least, using a struct to store the values for the specific settings would make it easier to identify each setting, and then you don't have to worry about your confusion about arrays.
 

BQubed

Member
I looked through that link about structs and constructors for menus and I gotta admit, it's WAY over my head and overly complicated for my needs. I've never felt comfortable using code way above my head (with the exception of Scribble). I never learn from code this high over my head. My current menu has all that I need in it but the only issue I'm suffering is the inability to adjust the individual values of each setting. I know I'm on the right track, but just missing a piece I can't grasp. If you could help me figure out what's wrong, I'd appreciate it. My game is quite simple in terms of settings and doesn't really need much.
 

BQubed

Member
Yes, all you're missing is a separate array for what each setting's value is.
So, for example, I should have an array called sound_fx[0] and a corresponding array called values[0] and make sure I do that for every single option? So, example:
GML:
snd_fx[0] = 0;
music[0] = 0;
msg_spd[0] = 0;

values[0] = 0;
values[1] = 0;
values[2] = 0;
Something kind of like that?
 
Not exactly. Either you use individual variables for each option, or you use a singular array (with a better name than "values", I might add).
 

Slyddar

Member
I've been looking at revisiting that tutorial and showing how to add selections. Here's how I would add it.
Firstly I'd update the code with some macros to make reading it easier, and set some values that we can update. I've used your code to make it easier to follow. Note I've updated it for 2.3 which means no more 2d arrays, instead you can just chain an array into an array. There is a pinned/highlighted comment in the video that shows what needs to be changed to get that working. I'll add it to the bottom of this post too.
Code:
//CREATE
#macro MAIN            0
#macro NEW_GAME        1
#macro CONTINUE        2
#macro SETTINGS        3
#macro SOUND        4
global.sound_volume = 5;    //from 0 to 10
global.music_volume = 5;    //from 0 to 10
max_sound = 10;

menu[MAIN][0] = "New Game";
menu[MAIN][1] = "Continue";
menu[MAIN][2] = "Settings";
menu[MAIN][3] = "Quit";

//Continue submenu 2
menu[CONTINUE][0] = "Not done yet";
menu[CONTINUE][1] = "Back";

//start submenu 3
menu[SETTINGS][0] = ["Sound FX ", global.sound_volume];
menu[SETTINGS][1] = ["Music ", global.music_volume];
menu[SETTINGS][2] = "Text Speed ";
menu[SETTINGS][3] = "Punctuation Pauses ";
menu[SETTINGS][4] = "Back";

index = 0;        //menu index position
sub_menu = 0;    //current sub menu

You'll notice the Sound FX and Music sub menus look different, as they need new additions to show the current value. When you have a menu item that you want the player to change, we store it as an array, with the text and the current value. I'll just demo Sound and Music menus, and you should be able to work the rest out from the example.

It's probably easier to just give you the step event rather than explain too much. Basically we can use the one switch statement to allow changes, as well as selections, but using this line, since menu entries that we can move left and right on have to be arrays as well now.
Code:
if _select or (is_array(menu[sub_menu][index]) and (_left or _right)) {
Step Event
Code:
/// @description
//get input
var _up = keyboard_check_pressed(vk_up);
var _down = keyboard_check_pressed(vk_down);
var _left = keyboard_check_pressed(vk_left);
var _right = keyboard_check_pressed(vk_right);
var _select = keyboard_check_pressed(vk_enter) or keyboard_check_pressed(vk_space);


var _move = _down - _up;
if _move != 0 {
    //move the index
    index += _move;

    //clamp values
    var _size = array_length(menu[sub_menu]);
    //old : var _size = array_length_2d(menu, sub_menu);
    if index < 0 index = _size - 1;            //at start, so loop to menu end
    else if index >= _size index = 0;        //at end, so loop to menu start
}

//if we select, or if we are on a menu item that is an array and are moving left or right
if _select or (is_array(menu[sub_menu][index]) and (_left or _right)) {
    switch(sub_menu) {
        case MAIN:
            switch(index) {
                case 0:
                    //New Game
                    //start game
                break;
                case 1:
                    //Conitnue
                    sub_menu = CONTINUE;
                    index = 0;
                break;
                case 2:
                    //Settings
                    sub_menu = SETTINGS;
                    index = 0;
                break;
                case 3:
                    //Quit
                    game_end();
                break;
            }
        break;
        case CONTINUE:
            switch(index) {
                case 0:
                    //Continue
               
                break;
                case 1:
                    sub_menu = MAIN;
                    index = 1;
                break;
            }
        break;
        case SETTINGS:
            switch(index) {
                case 0:
                    //Sound
                    var _move = _right - _left;
                    global.sound_volume = clamp(_move + global.sound_volume, 0, max_sound);
               
                    //Update the Settings sub menu (SETTINGS), this Sound entry (0), and array position (1)
                    menu[SETTINGS][0][1] = global.sound_volume;
                break;
                case 1:
                    //Music
                    var _move = _right - _left;
                    global.music_volume = clamp(_move + global.music_volume, 0, max_sound);
               
                    //Update the Settings sub menu (SETTINGS), this Music entry (1), and array position (1)
                    menu[SETTINGS][1][1] = global.music_volume;
                break;
                case 2:
                    //Text Speed
                break;
                case 3:
                    //Punctuation Pauses
                break;
                case 4:
                    //Back
                    sub_menu = MAIN;
                    index = 2;
                break;
            }
        break;
    }
}

The selection looks like this. We process the left and right and ensure it is not beyond our limits, then we update the array entry so the new value is displayed.
Code:
                    //Sound
                    var _move = _right - _left;
                    global.sound_volume = clamp(_move + global.sound_volume, 0, max_sound);
               
                    //Update the Settings sub menu (SETTINGS), this Sound entry (0), and array position (1)
                    menu[SETTINGS][0][1] = global.sound_volume;
Now the last change is if we have an array entry, we need to draw the txt field, and the value, so we need the change in the Draw event as below to do that. This is not your draw event, so you'll need to extract what you need.
Code:
draw_set_halign(fa_center);
draw_set_font(fnt_menu);

//line spacing
var _gap = 40;

//draw items
for (var i = 0; i < array_length(menu[sub_menu]); ++i) {
    draw_set_color(c_white);
    if i == index draw_set_color(c_teal);

    if is_array(menu[sub_menu][i]) {
        //draw value
        var _arr = menu[sub_menu][i];
        draw_text(room_width/2, room_height * .4 + _gap * i, string(menu[sub_menu][i][0]) + string(menu[sub_menu][i][1]));
    } else {
        //just draw text
        draw_text(room_width/2, room_height * .4 + _gap * i, menu[sub_menu][i]);
    }
}


Updating Menu code to 2.3, taken from the video comment.
So in the create event, the old structure of menu[0, 0] = "Start"; becomes menu[0][0] = "Start";

This line in the step event, and also where we get the size in the draw event "var _size = array_length_2d(menu, sub_menu);" becomes "var _size = array_length(menu[sub_menu]);"

And lastly the draw event line "menu[sub_menu, i])" becomes "menu[sub_menu])"

For others reference, the video this is all from is this :
 
Last edited:

Slyddar

Member
Actually I went ahead and did the Text Speed menu as well, which shows how you can use this system to display and update text fields too. I'm using a values array (loved the name so much I had to use it hehe) to store the values for the menus, so that it correlates to the same entry, which should make it easy to manage.

This is the addition to the Create event. In menu[SETTINGS][2] we are storing the "Text Speed" text, then retrieving the actual text for global.text_speed, from the values array ("Slow", etc), and lastly storing the actual index value so we can easily increment it. Now we could just store the global.text_speed value, and retrieve the text each time we draw it, but that requires extra steps in the draw event. Storing the values like this means we don't have any more processing to do, we simply draw array positions 0 and 1 for all array menu items.
Code:
values[SETTINGS][2] = ["Slow","Medium","Fast","Instant"];
global.text_speed = 1;
//start submenu 3
//integer storage uses 2 array positions [menu, value], text storage uses 3 array positions [menu, text, value]
//that way drawing the values always works by drawing array positions 0 and 1, so no change to the draw event is needed
menu[SETTINGS][0] = ["Sound FX ", global.sound_volume];
menu[SETTINGS][1] = ["Music ", global.music_volume];
menu[SETTINGS][2] = ["Text Speed ", values[SETTINGS][2][global.text_speed], global.text_speed];
menu[SETTINGS][3] = "Punctuation Pauses ";
menu[SETTINGS][4] = "Back";

Then the text speed entry in the step event looks like this :
Code:
                    //Text Speed
                    var _move = _right - _left;
                    global.text_speed = clamp(_move + global.text_speed, 0, array_length(values[SETTINGS][2]) - 1);
            
                    //Update the Settings sub menu (SETTINGS), this text entry (2), and array position (1), and (2)
                    menu[SETTINGS][2][1] = values[SETTINGS][2][global.text_speed];
                    menu[SETTINGS][2][2] = global.text_speed;
 
Last edited:

BQubed

Member
Thanks for the help Slyddar. Actually I think it was your tutorial I followed to make the initial menu. Your tutorials are good! Very clear and easy to understand.

EDIT: Wow, I just tested it and it works right outta the box! I feel kinda bad just copy pasting code but I never move on from a piece of code till I study it and learn it completely so at least I'll get an education. Thanks again, man.
 
Top