• Hey Guest! Ever feel like entering a Game Jam, but the time limit is always too much pressure? We get it... You lead a hectic life and dedicating 3 whole days to make a game just doesn't work for you! So, why not enter the GMC SLOW JAM? Take your time! Kick back and make your game over 4 months! Interested? Then just click here!

Script/Logic Organization

fawhxldawg

Member
Hey,

I'm working on a modular/all-purpose GUI/UI/UX that I'd use from menus, consoles, inventory, settings, prompts etc.
Containing things from arrays of strings(Input and output), numbers, GUI elements like buttons, toggles, sliders, etc.

Basicaqlly just my own GUI suite to meet all my demands in my games.

But as you'd guess, adding all these features into one object starts to make a big object.
(Big for me at least).

And I'm just wondering how would I go about, or how others would or do organize their code?
Because It is starting to feel overwhelming for me despite being easy (for me) to navigate.

I just wonder if being overwhelmed is a normal feeling when you start really fleshing out something like this?

Here is my draw/logic script for my GUI system.
(It iterates through a list of maps; each map being a unique GUI instance)

Note: Maybe skip looking at the code, unless youre really good at figuring out what vague and abridged variablenames mean lol. I guess the syntax wont highlight my enums or macros.

GML:
#region Macros, Structs, Enums oh my!
     
        // Ultra Basic
        #macro nn noone
        #macro t true
        #macro f false
     
        #region Window
 
            // Window Basics
            // W/H
            #macro ww global.window_width
            ww = 1024
            #macro wh global.window_height
            wh = 768
         
            // BG/FG
            #macro wbga global.window_background_alpha
            wbga = 1
            #macro wbgc global.window_background_color
            wbgc = c_black
            #macro wfga global.window_foreground_alpha
            wfga = 1
            #macro wfgc global.window_foreground_color
            wfgc = c_white
         
            #region Timer/Trigger
             
                // Window Iterators
                // Frame
                #macro wf global.window_frame
                wf = 0
                // Second
                #macro ws global.window_second
                ws = 0
                // Minute
                #macro wm global.window_minute
                wm = 0
             
            #endregion
         
            #region Debug
             
                #macro dbg global.debug
                dbg = true
             
            #endregion
         
        #endregion
     
        #region Mouse
         
            // X/Y + Olds
            #macro mx mouse_x
            #macro my mouse_y
            #macro mxo global.mouse_x_old
            mxo = mx
            #macro myo global.mouse_y_old
            myo = my
            #macro mxd global.mouse_x_delta
            mxd = mx-mxo
            #macro myd global.mouse_y_delta
            myd = my-myo
         
            // Left
            #macro mbl mouse_check_button(mb_left)
            #macro mblp mouse_check_button_pressed(mb_left)
            #macro mblr mouse_check_button_released(mb_left)
         
            // Right
            #macro mbr mouse_check_button(mb_right)
            #macro mbrp mouse_check_button_pressed(mb_right)
            #macro mbrr mouse_check_button_released(mb_right)
         
            // Wheels
            #macro mwu mouse_wheel_up()
            #macro mwd mouse_wheel_down()
         
        #endregion
     
        #region Keyboard
         
            #macro kbSHFT keyboard_check(vk_shift)
            #macro kbCTL keyboard_check(vk_control)
         
        #endregion
     
        #region Keys
         
            enum k {
             
                xx,
                yy,
                xy,
                w,
                h,
                bgc,
                fgc,
                bga,
                fga,
                bg,
                outline,
                uid,
                active,
                scroll,
                dialogue,
                prompt,
                drag,
                hilite,
                focus,
                resize,
                label,
                l_str, // label string
                lc, // label coordinates
                l_min_w, // label min width (for minimizing frame)
                lga,
                lgc,
                minimized,
                wo,
                ho,
                str_out,
                str_in,
                inc, // input coordinates
                in_time, // Timestamps?
                in_arr, // Input history array
                in_arri,
                str_arr,
                con_xx,
                con_yy,
                con_ha, // content horiz align
                con_va, // content verti align
                con_sep,
                con_w,
                con_offx,
                con_offy,
                pin,
                xbtn,
                pbtn,
                mbtn
             
            }
         
        #endregion
     
    #endregion
 
    #region Structs
     
        // Margins/Seperators
        sep = {
         
            // Basic
            sm:5,
            md:10,
            bg:20,
            // Window
            ww2:round(ww/2),
            ww4:round(ww/4),
            ww8:round(ww/8),
            ww16:round(ww/16),
            wh2:round(wh/2),
            wh4:round(wh/4),
            wh8:round(wh/8),
            wh16:round(wh/16),
            // Text
            txt:20
         
        }
     
    #endregion

#endregion

draw_gui_frames(){

    // 'gfml' being the list of maps holding the data for our gui instances
    for(var i = 0; i < ds_list_size(gfml); i++) {

        var e = gfml[|i]

        // Skip if frame Disabled
        if(!e[?k.active]) continue

        // Init
        var w2 = e[?k.w]/2
        var h2 = e[?k.h]/2

        #region BG; Drawn first so it is on bottom

            if(e[?k.bg]) { // Draw unless bg disabled

                if(gfi_focus == i or !e[?k.focus]) draw_set_alpha(e[?k.bga])
                else draw_set_alpha(e[?k.bga]/2)
                draw_set_color(e[?k.bgc])
                draw_rectangle(e[?k.xy][0],e[?k.xy][1],e[?k.xy][2],e[?k.xy][3],f)

            }

        #endregion

        #region Mouse inside frame?

            if(mx > e[?k.xy][0] and mx < e[?k.xy][2]
                and my > e[?k.xy][1] and my < e[?k.xy][3]) {

                // Focus Set
                if(e[?k.focus] and (mblr or mblp)) {
                    gfi_focus = i
                }

                #region Focused

                    if(gfi_focus == i or !e[?k.focus]) {

                        #region Drag and Resize

                            if(mbl and !e[?k.pin]) {

                                if(e[?k.resize] and !e[?k.minimized] and kbCTL) {

                                    var wo = e[?k.w]
                                    var ho = e[?k.h]

                                    // Ensure min frame width/height; if label, width is min label size + btns
                                    // Otherwise min is just 1/8th window size
                                    if(e[?k.label]) e[?k.w] = clamp(e[?k.w]+mxd,e[?k.l_min_w],ww)
                                    else e[?k.w] = clamp(e[?k.w]+mxd,sep.ww8,ww)
                                    e[?k.h] = clamp(e[?k.h]+myd,sep.wh8,wh)

                                    if(e[?k.w] != wo) e[?k.xx] += mxd/2
                                    if(e[?k.h] != ho) e[?k.yy] += myd/2
                                    e[?k.wo] = wo
                                    e[?k.ho] = ho
                                    e[?k.con_w] = e[?k.w]-(sep.sm*2)

                                } else if(e[?k.drag]) {

                                    // Add mouse X/Y Deltas to frame X/Y
                                    e[?k.xx] += mxd
                                    e[?k.yy] += myd

                                }

                            }

                        #endregion

                        #region Scroll

                            if(mwu) {

                                // Scroll wheel up; +Shift for faster
                                if(kbSHFT) e[?k.con_offy] -= 24
                                else e[?k.con_offy] -= 12

                            } else if(mwd) {

                                // Scroll wheel down; +Shift for faster
                                if(kbSHFT) e[?k.con_offy] += 24
                                else e[?k.con_offy] += 12

                            }

                        #endregion

                    }

                #endregion

                #region Unfocused

                    /* Not in use

                    if(gfi_focus != i) {



                    }

                    */

                #endregion

            } else if(gfi_focus == i and mblr) gfi_focus = nn // Reset focus if clicked off

        #endregion

        #region Additional Inits

            // Update Label Coordinates; Also used by content for adjustments
            e[?k.lc][0] = e[?k.xy][0]
            e[?k.lc][1] = e[?k.xy][1]
            e[?k.lc][2] = e[?k.xy][2]
            e[?k.lc][3] = e[?k.xy][3]+(sep.txt*1.5)

            // Update Input Coordinates;

            // Content Updates; Ensure content stays with frame
            e[?k.con_xx] = (e[?k.xy][0])+sep.sm
            e[?k.con_yy] = (e[?k.xy][1])+sep.sm

        #endregion

        #region Content

            // String Output;; Skip if minimized so offset y isn't changed
            if(e[?k.str_out] and !e[?k.minimized]) {

                if(gfi_focus == i or !e[?k.focus]) draw_set_alpha(e[?k.fga])
                else draw_set_alpha(e[?k.fga]/2)
                draw_set_color(e[?k.fgc])
                draw_set_halign(e[?k.con_ha])
                draw_set_valign(e[?k.con_va])
                var str_h = 0 // hold's total string height drew so far
                var arri_h1 = 0 // to hold the first string's height
                for(var arri = 0; arri < array_length_1d(e[?k.str_arr]); arri++) {

                    //  get height of current string
                    var arri_h = string_height_ext(e[?k.str_arr][arri],e[?k.con_sep],e[?k.con_w])
                    if(arri = 0) arri_h1 = arri_h // Save the height of first string in array for use later in clamping yoffset

                    // Draw a string from the array while it would still be inside the frame.
                    if(e[?k.con_yy]+e[?k.con_offy]+str_h > (e[?k.xy][1])
                        and e[?k.con_yy]+e[?k.con_offy]+str_h+arri_h < (e[?k.xy][3]))
                        draw_text_ext(e[?k.con_xx]+e[?k.con_offx],(e[?k.con_yy]+e[?k.con_offy])+str_h,
                            e[?k.str_arr][arri],e[?k.con_sep],e[?k.con_w])

                    // Add last string height to the total height
                    str_h += arri_h

                    // Scroll Clamping; using the strings total height, frame height, first and last string heights...
                    // We can keep the beginning of the text and end of the text with in the frame while scrolling
                    if(arri == array_length_1d(e[?k.str_arr])-1 and e[?k.scroll])
                        e[?k.con_offy] = clamp(e[?k.con_offy],-(str_h-(arri_h+sep.bg)),e[?k.h]-(arri_h1+sep.bg))

                }

            }

            // String input

        #endregion

        #region Label; Drawn last so it always on top

            if(e[?k.label]) {

                // Init Button coordinates
                // Exit Button
                var xbtn
                xbtn[0] = e[?k.lc][2]-sep.sm // Coordinates are backwards since button is right-aligned
                xbtn[1] = e[?k.lc][3]-sep.sm // i.e. Drawing Bottom-Right to Top-Left
                xbtn[2] = xbtn[0]-sep.txt
                xbtn[3] = e[?k.lc][1]+sep.sm
                var xbtnx = xbtn[2]+((xbtn[0]-xbtn[2])/2)
                var xbtny = xbtn[3]+((xbtn[1]-xbtn[3])/2)

                // Pin Button
                var pbtn
                pbtn[0] = xbtn[2]-sep.sm // Coordinates are backwards since button is right-aligned
                pbtn[1] = xbtn[1] // i.e. Drawing Bottom-Right to Top-Left
                pbtn[2] = pbtn[0]-sep.txt
                pbtn[3] = xbtn[3]
                var pbtnx = pbtn[2]+((pbtn[0]-pbtn[2])/2)
                var pbtny = pbtn[3]+((pbtn[1]-pbtn[3])/2)

                // Minimize Button
                var mbtn
                mbtn[0] = pbtn[2]-sep.sm // Coordinates are backwards since button is right-aligned
                mbtn[1] = pbtn[1] // i.e. Drawing Bottom-Right to Top-Left
                mbtn[2] = mbtn[0]-sep.txt
                mbtn[3] = pbtn[3]
                var mbtnx = mbtn[2]+((mbtn[0]-mbtn[2])/2)
                var mbtny = (mbtn[3]+((mbtn[1]-mbtn[3])/2))-sep.sm
                var btns_w = (xbtn[0]-mbtn[2])+(sep.sm*2) // Get total of buttons width to add to l_min_w
                e[?k.l_min_w] = string_width(e[?k.l_str])+btns_w+sep.sm // Update l_min_w

                // BG
                draw_set_alpha(e[?k.lga])
                draw_set_color(e[?k.lgc])
                draw_rectangle(e[?k.lc][0],e[?k.lc][1],e[?k.lc][2],e[?k.lc][3],f)

                // FG
                if(gfi_focus == i or !e[?k.focus]) draw_set_alpha(e[?k.lga])
                else draw_set_alpha(e[?k.lga]/2)
                draw_set_color(e[?k.fgc])
                draw_line(e[?k.lc][0],e[?k.lc][3],e[?k.lc][2],e[?k.lc][3])

                // Label
                var l_str_x = e[?k.lc][0]+sep.sm
                var l_str_y = e[?k.lc][1]+((e[?k.lc][3]-e[?k.lc][1])/2)
                draw_set_halign(fa_left)
                draw_set_valign(fa_center)
                if(gfi_focus == i or !e[?k.focus]) draw_set_alpha(e[?k.lga])
                else draw_set_alpha(e[?k.lga]/2)
                draw_set_color(e[?k.fgc])
                draw_text(l_str_x,l_str_y,e[?k.l_str])

                #region Label Buttons

                    #region X Button

                        if(mx > xbtn[2] and mx < xbtn[0]
                            and my > xbtn[3] and my < xbtn[1]
                            and (gfi_focus == i  or !e[?k.focus])) {

                            if(mbl) draw_set_alpha(e[?k.fga]*.5)
                            else draw_set_alpha(e[?k.fga]*.75)
                            draw_set_color(e[?k.fgc])
                            draw_rectangle(xbtn[0],xbtn[1],xbtn[2],xbtn[3],f)
                            if(mblr) e[?k.active] = f

                        } else {

                            if(gfi_focus == i or !e[?k.focus]) draw_set_alpha(e[?k.fga])
                            else draw_set_alpha(e[?k.fga]/2)
                            draw_set_color(e[?k.fgc])
                            draw_rectangle(xbtn[0],xbtn[1],xbtn[2],xbtn[3],f)

                        }

                        if(gfi_focus == i or !e[?k.focus]) draw_set_alpha(e[?k.fga])
                        else draw_set_alpha(e[?k.fga]/2)
                        draw_set_color(e[?k.bgc])
                        draw_set_halign(fa_middle)
                        draw_set_valign(fa_center)
                        draw_text(xbtnx,xbtny,"x")

                    #endregion

                    #region Pin Button

                        if(mx > pbtn[2] and mx < pbtn[0]
                            and my > pbtn[3] and my < pbtn[1]
                            and (gfi_focus == i or !e[?k.focus])) {

                            if(mbl) draw_set_alpha(e[?k.fga]*.5)
                            else draw_set_alpha(e[?k.fga]*.75)
                            draw_set_color(e[?k.fgc])
                            if(e[?k.pin]) draw_rectangle(pbtn[0],pbtn[1],pbtn[2],pbtn[3],f)
                            else draw_rectangle(pbtn[0],pbtn[1],pbtn[2],pbtn[3],t)
                            if(mblr) e[?k.pin] = !e[?k.pin]

                        } else {

                            if(gfi_focus == i) draw_set_alpha(e[?k.fga])
                            else draw_set_alpha(e[?k.fga]/2)
                            draw_set_color(e[?k.fgc])
                            if(e[?k.pin]) draw_rectangle(pbtn[0],pbtn[1],pbtn[2],pbtn[3],f)
                            else draw_rectangle(pbtn[0],pbtn[1],pbtn[2],pbtn[3],t)

                        }

                    #endregion

                    #region Min Button

                        if(mx > mbtn[2] and mx < mbtn[0]
                            and my > mbtn[3] and my < mbtn[1]
                            and gfi_focus == i) {

                            if(mbl) draw_set_alpha(e[?k.fga]*.5)
                            else draw_set_alpha(e[?k.fga]*.75)
                            draw_set_color(e[?k.fgc])
                            draw_rectangle(mbtn[0],mbtn[1],mbtn[2],mbtn[3],f)
                            if(mblr) frame_minimize(e)

                        } else {

                            if(gfi_focus == i) draw_set_alpha(e[?k.fga])
                            else draw_set_alpha(e[?k.fga]/2)
                            draw_set_color(e[?k.fgc])
                            draw_rectangle(mbtn[0],mbtn[1],mbtn[2],mbtn[3],f)

                        }

                        if(gfi_focus == i) draw_set_alpha(e[?k.fga])
                        else draw_set_alpha(e[?k.fga]/2)
                        draw_set_color(e[?k.bgc])
                        draw_set_halign(fa_middle)
                        draw_set_valign(fa_center)
                        draw_text(mbtnx,mbtny,"_")

                    #endregion

                #endregion

            }

        #endregion

        #region Stretch Indicator

            if((gfi_focus == i or !e[?k.focus]) and kbCTL and !e[?k.pin] and e[?k.resize]) {

                // Init Triangle X/Y; Bottom Right Corner
                var xx1 = (e[?k.xy][2])-sep.sm
                var yy1 = (e[?k.xy][3])-sep.sm
                var xx2 = xx1-sep.bg
                var yy2 = yy1
                var xx3 = xx1
                var yy3 = yy1-sep.bg

                draw_set_alpha(e[?k.fga])
                draw_set_color(e[?k.fgc])
                draw_triangle(xx1,yy1,xx2,yy2,xx3,yy3,f)

            }

        #endregion

        #region FG; Drawn Last so it is on top

            if(e[?k.outline]) { // Draw unless outline disabled

                if(gfi_focus == i or !e[?k.focus]) draw_set_alpha(e[?k.fga])
                else draw_set_alpha(e[?k.fga]/2)
                draw_set_color(e[?k.fgc])
                draw_rectangle(e[?k.xy][0],e[?k.xy][1],e[?k.xy][2],e[?k.xy][3],t)

            }

        #endregion

        #region General Frame Updates

            // XY
            e[?k.xy][0] = e[?k.xy][0]
            e[?k.xy][1] = e[?k.xy][1]
            e[?k.xy][2] = e[?k.xy][0]+e[?k.w]
            e[?k.xy][3] = e[?k.xy][1]+e[?k.h]

        #endregion

    }

    return nn

}
Thanks for any help in advance, I know it is a long script!

Also: Please don't tell me I need to rewrite everything from scratch because I didn't use a certain function that saves 2 bits worth of resources and that my for loops suck, I find criticism like that offensive.
I'm not doing everything the most efficient way certainly but it makes sense to me and I'm comfortable with it and I learn new tricks at my own pace.
I'm just trying to find how people go about making things "feel less big" or easier to navigate.
Or maybe how people break code up into smaller more digestable pieces?
 
Last edited:

kburkhart84

Firehammer Games
Yes, it is certainly overwhelming sometimes to do these things, that feeling is certainly normal. Many of these things aren't easy in the first place to do well, but then making them generic where you can use them all over instead of just the one project makes it even harder(having written a couple systems like that I 100% get that feeling).

I didn't look too hard at your code, but I see you mention using a single object for all the GUI stuff....I'm not certain that is a good idea. I personally use a button object for buttons, and then customize those in the room editor for each instance as needed, so they all get the same behavior but do their own thing when clicked. However, I would never do something like having a single object for ALL the different GUI things. You could certainly have a parent/child hierarchy, for example have a parent for all things that can have the "focus" so it's easy to find them all by referencing the parent. But I couldn't ever see a check box being the same object as a button, for example.

The only other thing I see right away is that I personally don't like some of those macros. t = true, f = false?!?! It's a personal choice I guess, but I would find those few letters saved typing not worth the readability loss. The same could be said about many of those macros you have defined. If it works for you, then so be it, it's your code.
 

fawhxldawg

Member
Yes, it is certainly overwhelming sometimes to do these things, that feeling is certainly normal. Many of these things aren't easy in the first place to do well, but then making them generic where you can use them all over instead of just the one project makes it even harder(having written a couple systems like that I 100% get that feeling).

I didn't look too hard at your code, but I see you mention using a single object for all the GUI stuff....I'm not certain that is a good idea. I personally use a button object for buttons, and then customize those in the room editor for each instance as needed, so they all get the same behavior but do their own thing when clicked. However, I would never do something like having a single object for ALL the different GUI things. You could certainly have a parent/child hierarchy, for example have a parent for all things that can have the "focus" so it's easy to find them all by referencing the parent. But I couldn't ever see a check box being the same object as a button, for example.

The only other thing I see right away is that I personally don't like some of those macros. t = true, f = false?!?! It's a personal choice I guess, but I would find those few letters saved typing not worth the readability loss. The same could be said about many of those macros you have defined. If it works for you, then so be it, it's your code.
Thanks for the reply! Yeah I dont really expect anyone to look through it all, it really is too much lol and the syntax highlighting is very minimal on here. (That and also im pretty naughty about adhereing to standard programming conventions)

So this GUI part is largely just the "frame" or container for things, (though they do have built in print areas, and string input fields) other GUI elements like buttons, sliders, checkboxes etc. Would be seperate objects that get drawn inside these "frames/containers" and would share some variables with the containers like if the container isnt active then neither would the objects assigned to that container be. Havent gotten that far yet though.

And yeah I knew my petty true false macros would rustle some jimmies but I'm just used to it haha and I quite like save those few characters.

But thanks for letting me know im not alome with that overwhelming feeling it is very reassuring to hear that I'm not just crazy or just doing something wrong!

Perhaps it means you're making good progress when you start feeling overwhelmed!
 
Last edited:
Top