Introducción al Desarrollo de Videojuegos con GameMaker · Module 8: HUDs, Interfaces, Menus, and Pause Systems

Using the Draw GUI Event for UI Elements

Lesson 36 of 60 · 50 min

Also available in: ES

Introduction to UI Rendering and the Camera Problem

Throughout the previous modules, we built the core foundation of our platformer: a character that jumps and responds to solid collisions, patrolling enemies, and collectibles scattered across the level. However, for players to understand their status in the game, they need immediate visual feedback. This is where the HUD (Heads-Up Display) comes in: indicators for health points, remaining lives, coin counts, and elapsed time.

A common mistake when starting out with GameMaker is drawing score text or life icons inside the standard Draw event (the world drawing event). When a level is static and lacks a moving camera, this approach appears to work fine. However, as soon as the player moves and the camera tracks across the room, the UI elements lag behind or scroll completely off-screen, remaining pinned to spatial world coordinates instead of floating over the screen's glass.

To solve this architectural issue, GameMaker includes an independent rendering layer called the GUI (Graphical User Interface) layer, along with a dedicated event to manage it: the Draw GUI event. In this lesson, you will master this event, gain a thorough understanding of the mathematical separation between Room space and screen space, and establish the technical groundwork for our project's entire interface system.

Room Coordinates vs. GUI Coordinates

To program interfaces predictably and professionally, you must clearly distinguish between the two coordinate systems operating simultaneously within GameMaker's rendering pipeline: Room Space (world coordinates) and GUI Space (screen coordinates).

Room Space is the two-dimensional plane where game instances physically exist. If a level is 3840 pixels wide by 1080 pixels high, a block placed at position x = 2000, y = 800 will always reside at that exact geographical point on the map. When the camera focuses on position x = 1800, that block is visible on-screen; if the camera pans to x = 0, the block is hidden from view. Room coordinates are directly subject to camera position, viewport rotation angle, and zoom factors.

Conversely, GUI Space represents a fixed two-dimensional plane overlaid on top of whatever the camera is viewing—much like drawing directly onto your monitor's glass with a permanent marker. In this system, position (0, 0) always maps to the top-left visible corner of the game window, regardless of where the player character is located in a massive level or where the camera is looking.

Schematic diagram illustrating the difference between dynamic world coordinates tracked by the camera and the fixed GUI layer drawn over the screen.
Figure 1: Spatial comparison between the Room coordinate system (dynamic world) and the Draw GUI layer (static screen). · AI-generated image
FeatureStandard Draw Event (Room Space)Draw GUI Event (GUI Space)
Origin point (0, 0)Top-left corner of the entire room map.Top-left corner of the visible screen or window.
Affected by cameraYes. Panning, rotating, or zooming moves the rendered elements.No. The plane remains completely stationary regardless of camera state.
Execution orderRendered during world drawing (instance and tile layers).Rendered at the end of the draw pipeline, on top of all world content.
Primary purposeCharacters, terrain platforms, environmental effects, enemies.Life trackers, score text, minimaps, pop-up menus, dialogue boxes.
Scaling and resolutionInherits room pixel scale and viewport dimensions.Controlled independently via display_set_gui_size().

The Render Cycle and Draw Event Execution Order

GameMaker runs its game loop through strictly ordered stages. In every frame, after processing inputs and physics updates across the Step events (Begin Step, Step, End Step), the engine enters the graphics pipeline. Understanding the chronological order in which these draw events fire prevents unexpected visual layering bugs.

During the draw phase, GameMaker steps through the following sequence:

1. **Pre Draw:** Executes before any surface processing begins; used for advanced shader techniques and low-level render targets. 2. **Draw Begin:** Marks the start of world rendering; useful for custom background rendering before instances are processed. 3. **Draw (Standard Event):** Renders all visible instances in the room based on layer depth order. If an object has an assigned sprite and no custom code in its Draw event, GameMaker calls `draw_self()` by default. 4. **Draw End:** Fires after all room instances and tile layers have finished drawing to the room target surface. 5. **Post Draw:** Executes on the final backbuffer surface before the frame is presented to the display. 6. **Draw GUI Begin:** Initiates the user interface rendering pass. 7. **Draw GUI:** The core event where interface elements are rendered. Everything drawn here is composited directly over all world content produced by earlier Draw events. 8. **Draw GUI End:** The final rendering event, ideal for fullscreen fade transitions, post-processing overlays, or custom hardware mouse cursors.

💡 Golden Rule: Never place movement logic, collision checks, or health modifications inside any Draw or Draw GUI event. Draw events exist purely for visual output. Game state logic must stay strictly within the Step events.

Configuring GUI Resolution and Scaling

By default, GameMaker sizes the GUI layer to match either the initial Room dimensions or the game window size. However, in retro-styled pixel art games running at low internal resolutions (such as 480x270 or 640x360) displayed on modern 1080p monitors, this can lead to two different presentation styles depending on your art direction:

First scenario: Pixel-Perfect HUD. In this approach, text and HUD icons use the exact same pixel scale as your character sprites and stage tiles. To achieve this, you lock the GUI canvas size to match the camera's internal resolution exactly.

Second scenario: High-Definition HUD (Hi-Res UI over pixel art). Developers often prefer crisp, high-resolution fonts and vector-style icons rendered at 1080p while the world gameplay underneath remains pixel art. This is achieved by setting the GUI resolution significantly higher than the internal camera view.

To govern this behavior with mathematical precision, GameMaker provides three essential GML functions:

// Set the virtual resolution of the GUI canvas
display_set_gui_size(640, 360);

// Get the current width of the GUI canvas
var _gui_ancho = display_get_gui_width();

// Get the current height of the GUI canvas
var _gui_alto = display_get_gui_height();

By explicitly calling `display_set_gui_size(640, 360)`, you guarantee that whether the game runs on an older laptop or a 4K display, screen coordinates always operate on a known virtual grid: position `x = 640` is always the rightmost edge of the screen, and `y = 360` is always the bottom.

Creating the UI Controller Object (obj_hud)

To maintain clean, modular project architecture, avoid assigning your player character (`obj_jugador`) the responsibility of drawing both itself and the on-screen health bar, score counters, and decorative frames. If the player dies and its instance is temporarily destroyed during a respawn, the entire HUD would vanish along with it.

The industry standard pattern is to use a dedicated controller object. We will create a spriteless object named `obj_hud` (or `obj_ui_controller`) whose sole responsibility is reading global or instance variables and rendering the interface.

Step 1: Set Up obj_hud's Create Event

Open the Asset Browser, right-click the **Objects** folder, select **Create -> Object**, and name it `obj_hud`. In the Object Properties window, add a **Create** event and enter the following GML code:

/// obj_hud Create Event

// Set the virtual resolution for the GUI canvas
// Using 640x360 maintains retro pixel-perfect consistency with our camera
display_set_gui_size(640, 360);

// Screen margins to offset elements from display edges
margen_horizontal = 16;
margen_vertical   = 16;

// Visual smoothing variable for future bar transitions or animations
alpha_interfaz = 1.0;

Step 2: Implement the Draw GUI Event

In the same `obj_hud`, click **Add Event**, expand the **Draw** category, and select **Draw GUI** (be careful not to pick the standard Draw event). Add the following documented code block:

/// obj_hud Draw GUI Event

// Verify that the player instance exists to prevent runtime errors
if (!instance_exists(obj_jugador))
{
    exit; // Exit event if player is absent (e.g., during room transitions or respawns)
}

// Configure text drawing properties
// Resetting draw states is an essential defensive programming practice
draw_set_font(-1); // Use default engine font
draw_set_halign(fa_left);   // Align text to the left
draw_set_valign(fa_top);    // Align text to the top

// 1. Draw text drop shadow to maintain readability against any background
draw_set_color(c_black);
draw_text(margen_horizontal + 1, margen_vertical + 1, "SCORE: " + string(global.puntos));
draw_text(margen_horizontal + 1, margen_vertical + 21, "COINS: " + string(global.monedas));

// 2. Draw primary text in white and yellow
draw_set_color(c_white);
draw_text(margen_horizontal, margen_vertical, "SCORE: " + string(global.puntos));

draw_set_color(c_yellow);
draw_text(margen_horizontal, margen_vertical + 20, "COINS: " + string(global.monedas));

// 3. Draw lives indicator in top-right screen corner
var _gui_ancho = display_get_gui_width();
var _x_vidas = _gui_ancho - margen_horizontal;
var _y_vidas = margen_vertical;

// Right-align text for top-right placement
draw_set_halign(fa_right);
draw_set_color(c_white);
draw_text(_x_vidas, _y_vidas, "LIVES: " + string(global.vidas));

// 4. Mandatory reset of global draw properties back to engine defaults
draw_set_color(c_white);
draw_set_halign(fa_left);
draw_set_valign(fa_top);

Font Management and Text Alignment in the UI

One of the most common pitfalls for beginners in GameMaker is misunderstanding the global Draw State. When you call functions such as `draw_set_color(c_red)` or `draw_set_halign(fa_center)`, you are not applying that property solely to the next line of code: you are mutating a global state machine shared across all instances in the game.

If you set horizontal alignment to `fa_center` inside `obj_hud` and do not reset it back to `fa_left` before the script finishes, every other instance in the room calling `draw_text()` in its own Draw event will suddenly center its text, creating hard-to-trace bugs across dialogue boxes, enemy nameplates, and menus.

Text Alignment Functions

GameMaker provides clear built-in constants to control how text aligns relative to the (x, y) coordinates passed into `draw_text()`:

- **Horizontal Alignment (`draw_set_halign()`):** - `fa_left`: The x coordinate marks the left edge of the text string (default). - `fa_center`: The x coordinate marks the exact horizontal center of the string. - `fa_right`: The x coordinate marks the right edge of the string. Essential for anchoring values to the right side of the screen so numbers expand leftward cleanly regardless of digit count.

- **Vertical Alignment (`draw_set_valign()`):** - `fa_top`: The y coordinate marks the top boundary of the text bounding box. - `fa_middle`: The y coordinate marks the vertical midpoint of the characters. - `fa_bottom`: The y coordinate corresponds to the text's bottom baseline.

Practical Example: Drawing a Health Bar with Primitives in Draw GUI

In Module 7, we implemented player health variables (`hp` and `hp_max`). Rather than just printing raw numbers, let's build a stylized visual health bar in the Draw GUI event using 2D drawing primitives—complete with a dark border, background trough, and color-reactive fill.

For this hands-on implementation, we will use the following geometric functions:

- `draw_rectangle(x1, y1, x2, y2, outline)`: Draws a filled or outlined rectangle based on the boolean passed in the final parameter. - `draw_rectangle_color()`: Draws rectangles featuring distinct corner color gradients.

/// obj_hud Draw GUI Event (Health Bar Section)

if (instance_exists(obj_jugador))
{
    // Read health values from the player instance
    var _salud_actual = obj_jugador.salud;
    var _salud_maxima = obj_jugador.salud_max;
    
    // Calculate normalized health percentage (range 0.0 to 1.0)
    // clamp prevents drawing negative width or oversized bars
    var _porcentaje = clamp(_salud_actual / _salud_maxima, 0, 1);
    
    // Screen coordinate definitions for the health bar
    var _bx1 = 16;  // Left margin
    var _by1 = 45;  // Positioned directly below score text
    var _ancho_barra = 120;
    var _alto_barra  = 12;
    var _bx2 = _bx1 + _ancho_barra;
    var _by2 = _by1 + _alto_barra;
    
    // 1. Draw dark gray background for the empty trough
    draw_set_color(c_dkgray);
    draw_rectangle(_bx1, _by1, _bx2, _by2, false);
    
    // 2. Draw dynamic health fill
    // Fill width scales proportionally to remaining health percentage
    var _relleno_x2 = _bx1 + (_ancho_barra * _porcentaje);
    
    // Select dynamic status color: green when healthy, red when critical
    var _color_salud = c_lime;
    if (_porcentaje < 0.3)
    {
        _color_salud = c_red;
    }
    else if (_porcentaje < 0.6)
    {
        _color_salud = c_yellow;
    }
    
    draw_set_color(_color_salud);
    draw_rectangle(_bx1, _by1, _relleno_x2, _by2, false);
    
    // 3. Draw black outer border to frame the bar
    draw_set_color(c_black);
    draw_rectangle(_bx1 - 1, _by1 - 1, _bx2 + 1, _by2 + 1, true);
    
    // Restore default draw color
    draw_set_color(c_white);
}

Let's review the runtime behavior: whenever the player takes damage from an enemy contact, `salud` on `obj_jugador` drops. On the very next frame, the Draw GUI event recalculates `_porcentaje`. Multiplying this ratio by `_ancho_barra` causes the green fill rectangle to smoothly shrink toward the left. Most importantly, even if the player travels 3000 pixels east across the room, the bar remains rock-solid at coordinates (16, 45) on your screen.

Troubleshooting and Common Pitfalls

When working with the GUI layer for the first time, you may encounter a few unexpected behaviors. Here are the most frequent issues and how to resolve them:

1. **The HUD doesn't render after placing the object in the Room:** Check if the `Visible` property on either the instance or the `obj_hud` asset in the editor is unchecked. If an object is marked invisible, GameMaker skips all of its draw events entirely (both Draw and Draw GUI).

2. **Text or sprites appear blurry or distorted:** This happens when the aspect ratio set via `display_set_gui_size()` does not match the aspect ratio of the viewport or game window. For example, if your game displays in 16:9 (such as 640x360 or 1920x1080) but you force a GUI size of 800x600 (a 4:3 ratio), the UI elements will stretch horizontally.

3. **The HUD jitters or stutters as the camera moves:** This classic artifact occurs when developers attempt to draw a fixed UI inside the standard Draw event by offsetting camera coordinates: `draw_text(camera_get_view_x(cam) + 20, ...)`. Because camera positions and instance rendering update at slightly different sub-pixel intervals in the frame cycle, a one-frame mismatch causes visual jitter. Moving the rendering logic into the Draw GUI event eliminates camera tracking jitter entirely, as GUI coordinates are completely decoupled from camera math.

Hands-On Exercises

Complete the following three exercises to solidify your grasp of GUI coordinates before building a full HUD layout in the next session.

**Exercise 1: Empirical Coordinate Space Test (Coding)** Create a test object called `obj_test_coordenadas`. In its standard Draw event, draw a red circle at position (100, 100). Next, in its Draw GUI event, draw a blue circle at position (100, 100). Run the game, move your player forward through the room so the camera scrolls, and observe how each circle behaves. - *Success Criteria:* The red circle stays locked to the world tiles at the beginning of the room and disappears as the camera pans away. The blue circle remains permanently anchored to the top-left of the display.

**Exercise 2: Resolution-Independent Dynamic Positioning (Coding)** Inside `obj_hud`'s Draw GUI event, implement an indicator centered at the bottom of the screen displaying the text "LEVEL 1 - GREEN FOREST". Do not hardcode rigid pixel values (such as 320, 340). Instead, dynamically calculate the horizontal center and bottom margin using `display_get_gui_width()` and `display_get_gui_height()` paired with the appropriate text alignment. - *Success Criteria:* Changing the GUI canvas resolution in the Create event between `display_set_gui_size(640, 360)` and `display_set_gui_size(960, 540)` keeps the level title centered along the bottom edge without clipping or drifting off-center.

**Exercise 3: Diagnosing and Fixing Graphics State Leaks (Analysis)** Imagine that after adding a flashing warning message in the Draw GUI event using `draw_set_color(c_red)` and `draw_set_alpha(0.5)`, you notice that all platform tiles and your player sprite in the game world appear tinted red and semi-transparent. - Explain in detail why this occurred in GameMaker's render pipeline. - Write the fix required at the end of the GUI script to restore correct rendering states. - *Success Criteria:* Accurately identify the global persistence of GameMaker's draw state machine and restore `draw_set_color(c_white)` and `draw_set_alpha(1.0)` upon event completion.

Summary

  • Room Space coordinates are tied to level geometry and are directly affected by camera panning, rotation, and zoom.
  • GUI Space represents an overlay plane anchored to the display window, with its origin (0, 0) permanently locked to the visible top-left corner.
  • The Draw GUI event fires after world rendering, ensuring interface elements always draw on top of tile layers, instances, and backgrounds.
  • display_set_gui_size(width, height) establishes an explicit virtual resolution for the interface layer, decoupling UI pixel scale from display window size.
  • Global draw properties like draw_set_color(), draw_set_alpha(), and draw_set_halign() must always be reset to defaults at the end of draw events to prevent visual side effects elsewhere in the game.

Practise what you learned

Exercise 1 · 1 point

Why is it recommended to create a dedicated controller object (such as `obj_hud`) for the interface instead of handling its drawing inside `obj_player`?

You are practising without an account: nothing is saved. Sign up to keep your progress.

Exercise 2 · 1 point

Arrange the following rendering events in chronological order according to GameMaker's draw pipeline, from first to last in a single frame:

  1. 1Draw Begin
  2. 2Draw GUI End
  3. 3Draw GUI
  4. 4Post Draw
  5. 5Draw (Standard Event)
You are practising without an account: nothing is saved. Sign up to keep your progress.

Exercise 3 · 1 point

To set the mathematical resolution of the graphical user interface canvas to fixed dimensions, call the function (640, 360). Additionally, according to the engine's golden rule, movement and collision logic must remain in events and never be placed in rendering events.

You are practising without an account: nothing is saved. Sign up to keep your progress.

Sign in to save your progress — Sign in