• 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+ Drawing GUI Elements to a Separate Surface

I’ve been changing the way my game is drawn to be manually drawn through surfaces. I have a global.GUI_surface variable that I draw to in Draw GUI events. While drawing to this surface works fine, the entire surface has a black screen beneath the GUI elements - which completely covers the application surface, where the “game world” is drawn (everything that’s not GUI).

I believe it has something to do with opacity, but I can’t seem to use gpu_set_colorwriteenable(true, true, true, false) to disable the alpha channel for the surface. Also, when a GUI element leaves the screen, it leaves a "smear" of sorts where you can see each frame it was visible. I’m not sure how to go about it.

application_surface_draw_enable is set to false at all times.
One object draws both surfaces in the post-draw event:

GML:
/// @description Draw Surfaces
// * res_width & res_height are macros, which are set to 1152 & 648.

if(!surface_exists(global.GUI_surface)){
    global.GUI_surface = surface_create(res_width, res_height);
}

// Prepare Vars
var c        = c_white;
var col_ar    = [c, c, c, c];

// Prepare Surface
draw_clear(c_black);
gpu_set_blendenable(false);
gpu_set_colorwriteenable(true, true, true, false);

// --- DRAW SURFACES ---
// Overworld / Game World Surface
if(surface_exists(application_surface))
draw_surface_general(application_surface, 0, 0, res_width, res_height,
0, 0, 1, 1, 0, col_ar[0], col_ar[1], col_ar[2], col_ar[3], 1);

// GUI Surface
if(surface_exists(global.GUI_surface))
draw_surface_general(global.GUI_surface, 0, 0, res_width, res_height,
0, 0, 1, 1, 0, col_ar[0], col_ar[1], col_ar[2], col_ar[3], 1);

// Reset Drawing Mode for All Other Instances
gpu_set_colorwriteenable(true, true, true, false);
gpu_set_blendenable(true);

scr_reset_draw_settings(); // just does things like draw_set_color(c_white) and draw_set_halign(fa_left);
 

Nocturne

Friendly Tyrant
Forum Staff
Admin
Moderator
Show the code for drawing tot he global GUI surface as it sounds like the issue is there.
 
Right now, I'm testing one object's draw GUI event (it draws out a menu). Here is its code:
(The functions scr_adjust_gui and scr_reset_draw_settings will come after this block of code)

GML:
scr_adjust_gui();

if(surface_exists(global.GUI_surface))
surface_set_target(global.GUI_surface);

// --- (The drawing takes place here)
#region MAIN MENU (Unselected)
#region MAIN MENU (Selected)
// ---

if(surface_exists(global.GUI_surface))
surface_reset_target();

scr_reset_draw_settings();
scr_adjust_gui() - this function sets the GUI size to draw depending on how the screen is being viewed. It can be viewed in windowed mode, fullscreen (with maintain aspect ratio), or fullscreen with pixel-perfect - which is like playing in windowed mode except there are black bars on all sides. It works across all modes.
GML:
function scr_adjust_gui() {
    
    // Pixel Perfect & Fullscreen
    if(global.st_fs_pixelperfect && window_get_fullscreen() == true){
        display_set_gui_maximize(1, 1, (global.x_axis / 2) - (res_width / 2), (global.y_axis / 2) - (res_height / 2));
    }
    
    // Normal Fullscreen
    else if(!global.st_fs_pixelperfect && window_get_fullscreen() == true){
        display_set_gui_maximize();
        display_set_gui_size(res_width, res_height);
    }
    
    // Windowed Mode
    else {
        display_set_gui_maximize();
        display_set_gui_size(res_width, res_height);
    }

}
And scr_reset_draw_settings() - the gpu_set_colorwriteenable is there to prevent sprites with lower opacities to get closer to black, as when I first began using surfaces that was the main issue. That fixed the problem, but I'm not sure if it could be affecting how the GUI is drawn.
Code:
function scr_reset_draw_settings() {
    // General
    draw_set_halign(fa_left);
    draw_set_valign(fa_top);
    draw_set_color(c_white);
    draw_set_alpha(1);
    draw_set_font(fnt_basic);
    
    // Drawing to Surface - Avoid shifting to black at lower opacities
    gpu_set_colorwriteenable(true, true, true, false);
}
 

Nocturne

Friendly Tyrant
Forum Staff
Admin
Moderator
Okay, so... First I would have the adjust GUI script in the create event of the object or something, as you shouldn't be calling the maximise function every step, which is what it appears to be doing (and that may affect future drawing).

Second:
GML:
// --- (The drawing takes place here)
#region MAIN MENU (Unselected)
#region MAIN MENU (Selected)
// ---
It would have been rather nice to see what's in here too! However, make sure you're calling the draw_clear_alpha() function before anything else, and that alpha writing is enabled when you call it (you can disable it again afterwards).

Finally, talking of alpha writing... It costs virtually nothing to enable and disable alpha writing, so you MUST ensure that everytime you switch it off, you switch it back on again after you've drawn the things you need in that event. You don't want to disable alpha writing for an entire event, so normally you'd have a typical event like:
Code:
// START OF DRAW EVENT
draw stuff
disable alpha write
draw more stuff
enable alpha write
draw more stuff
disable alpha write
draw more stuff
draw more stuff
enable alpha write
// END OF DRAW EVENT
So, basically, for every disable you need an enable, and you should never end the event with the alpha write disabled.
 
Moving scr_adjust_gui has been noted; I'll add something in place to check for changes in the viewing-mode and run the script there.
I haven't been using draw_clear_alpha(), and so I tried that for the Draw GUI event in the menu test object (you can see the code for drawing the
menu, though I don't think it affects the issue).
GML:
if(surface_exists(global.GUI_surface))
surface_set_target(global.GUI_surface);

gpu_set_colorwriteenable(true, true, true, true);
draw_clear_alpha(c_black, 0);

#region Variables

    #region Main
    
    var scl        = 2;
    var c        = c_white;
    var c_sel    = make_color_rgb(94, 255, 50);
    
    #endregion
    #region Names

    var str_items            = "ITEMS";
    var str_equip            = "EQUIP";
    var str_status            = "STATUS";
    var str_collectibles    = "RELICS";
    var str_config            = "CONFIG";

    #endregion
    #region Cursor
    
    var cursor_spr    = spr_amulet_cursor;
    var c_y            = 40 - main_top_y;
    
    #endregion
    #region Positioning
    
    var w_top_bar = res_width - (122 * scl); // 85
    
    // Main Tabs
    var new_xb = 6 * scl;
    
    var main_tab_x_start    = (19 * scl) - new_xb;
    var main_tab_y_start    = (27 * scl) - main_top_y;
    var main_tab_y_dest        = (42 * scl) - main_top_y;
    var main_buffer            = (92 * scl); // 100 * scl
    
    // Main Options
    var main_opt_x            = (16 * scl) - new_xb;
    var main_opt_y            = (7 * scl) - main_top_y;
    
    // Statuses
    var status_y            = (261 * scl);
    
    switch(party_num){
    case 0: break;
    case 1:
        var status_x_1 = 227 * scl;
        break;
    case 2:
        var status_x_1 = 166 * scl;
        var status_x_2 = 290 * scl;
        break;
    case 3:
        
        break;
    case 4:
        
        break;
    }
    
    #endregion

#endregion

#region MAIN MENU (Unselected)

// Tabs
draw_sprite_ext(spr_inv_tabs, 0, main_tab_x_start, main_tab_y[0], scl, scl, 0, c, 1);
draw_sprite_ext(spr_inv_tabs, 0, main_tab_x_start + main_buffer, main_tab_y[1], scl, scl, 0, c, 1);
draw_sprite_ext(spr_inv_tabs, 0, main_tab_x_start + main_buffer * 2, main_tab_y[2], scl, scl, 0, c, 1);
draw_sprite_ext(spr_inv_tabs, 0, main_tab_x_start + main_buffer * 3, main_tab_y[3], scl, scl, 0, c, 1);
draw_sprite_ext(spr_inv_tabs, 0, main_tab_x_start + main_buffer * 4, main_tab_y[4], scl, scl, 0, c, 1);

// Tab Strings
draw_set_halign(fa_center);
draw_text(main_tab_x_start + 40 * scl, main_tab_y[0] + 18 * scl, str_items);
draw_text(main_tab_x_start + 40 * scl + main_buffer, main_tab_y[1] + 18 * scl, str_equip);
draw_text(main_tab_x_start + 40 * scl + main_buffer * 2, main_tab_y[2] + 18 * scl, str_status);
draw_text(main_tab_x_start + 40 * scl + main_buffer * 3, main_tab_y[3] + 18 * scl, str_collectibles);
draw_text(main_tab_x_start + 40 * scl + main_buffer * 4, main_tab_y[4] + 18 * scl, str_config);

// Top Bar
draw_sprite_part_ext(spr_inv_top_bar, 0, 0, 0, 14, 63, 0, 0 - main_top_y, scl, scl, c, 1);
draw_sprite_part_ext(spr_inv_top_bar, 0, 14, 0, 1, 63, 14 * scl, 0 - main_top_y, w_top_bar, scl, c, 1);
draw_sprite_part_ext(spr_inv_top_bar, 0, 15, 0, 14, 63, w_top_bar + 8, 0 - main_top_y, scl, scl, c, 1);

// Options
draw_sprite_ext(spr_inv_options, 0, main_opt_x, main_opt_y, scl, scl, 0, c, 1);
draw_sprite_ext(spr_inv_options, 1, main_opt_x + main_buffer, main_opt_y, scl, scl, 0, c, 1);
draw_sprite_ext(spr_inv_options, 2, main_opt_x + main_buffer * 2, main_opt_y, scl, scl, 0, c, 1);
draw_sprite_ext(spr_inv_options, 3, main_opt_x + main_buffer * 3, main_opt_y, scl, scl, 0, c, 1);
draw_sprite_ext(spr_inv_options, 4, main_opt_x + main_buffer * 4, main_opt_y, scl, scl, 0, c, 1);

#endregion
#region MAIN MENU (Selected)

// Highlight Option
draw_sprite_ext(spr_inv_options_sel, main_option_index, main_opt_x + main_buffer * main_option_index,
                main_opt_y, scl, scl, 0, c_white, 1);

// Draw Cursor
if(cur_menu == inv_menus.main)
draw_sprite_ext(cursor_spr, frame, cursor_x, c_y, scl, scl, 0, c_white, 1);

#endregion

gpu_set_colorwriteenable(true, true, true, false);

if(surface_exists(global.GUI_surface))
surface_reset_target();

// The function scr_reset_draw_settings NOW runs
// gpu_set_colorwriteenable(true, true, true, true)
// instead of gpu_set_colorwriteenable(true, true, true, false)
// to ensure that it's enabled at the end of the draw event.
scr_reset_draw_settings();
Here's scr_reset_draw_settings():
GML:
///@description reset_draw_settings
function scr_reset_draw_settings() {

    // General
    draw_set_halign(fa_left);
    draw_set_valign(fa_top);
    draw_set_color(c_white);
    draw_set_alpha(1);
    draw_set_font(fnt_fs_room);
    
    // Enable Alpha at end of Event
    gpu_set_colorwriteenable(true, true, true, true);

}
Although a black screen still covers the application surface, the GUI surface does not leave screen smears anymore when leaving the screen. I think the problem might now have to due with how the actual surface is being drawn, as opposed to how the GUI elements are being drawn. Here's the post-draw event for the object that draws the surfaces:
GML:
/// @description Draw Surfaces
if(!surface_exists(global.GUI_surface)){
    global.GUI_surface = surface_create(res_width, res_height);
    
}

// Prepare Vars
var c        = c_white;
var col_ar    = [c, c, c, c];

// Prepare Surface
gpu_set_colorwriteenable(true, true, true, true);
draw_clear_alpha(c_black, 0);
gpu_set_blendenable(false);
gpu_set_colorwriteenable(true, true, true, false);

// --- DRAW SURFACES ---
// Overworld
if(surface_exists(application_surface))
draw_surface_general(application_surface, 0, 0, res_width, res_height,
0, 0, 1, 1, 0, col_ar[0], col_ar[1], col_ar[2], col_ar[3], 1);

// GUI Layer
if(surface_exists(global.GUI_surface))
draw_surface_general(global.GUI_surface, 0, 0, res_width, res_height,
0, 0, 1, 1, 0, col_ar[0], col_ar[1], col_ar[2], col_ar[3], 1);

// Reset Blendenable
gpu_set_blendenable(true);

scr_reset_draw_settings();
Despite trying draw_clear_alpha(c_black, 0) here, the GUI surface still draws black completely over the application surface. The smears stopped happening from the GUI elements so I think it's a step in the right direction, but I'm not sure where to go from here
 
Top