Introducción al Desarrollo de Videojuegos con GameMaker · Module 4: Controls, Movement, and Player Handling

Flipping Sprites Based on Direction

Lesson 18 of 60 · 45 min

Also available in: ES

Introduction to Character Facing Direction

In the first two lessons of this module, we laid the foundation for capturing keyboard and gamepad inputs and translated those readings into smooth horizontal movement for our player object (obj_jugador). However, if you tested your project up to this point, you probably noticed an obvious visual issue: when the character walks to the left, the art continues facing to the right. The hero appears to moonwalk across the screen, sliding backward like on ice.

During the classic 8-bit and 16-bit console eras, video memory and cartridge storage were extremely limited. Developers could not afford to draw two complete animation sets for every character (one facing right and another facing left), as this would needlessly double graphics memory usage. The universal technical solution was storing a single sprite set—typically facing right—and applying a geometric hardware flip across the horizontal axis whenever the character moved left. GameMaker implements this exact technique through built-in transformation variables.

In this lesson, you will learn how to manipulate the built-in image_xscale variable to mirror your sprite in real time in a clean, efficient, and mathematically precise way. We will break down why this saves art production time, the critical role of the sprite origin or pivot point, and how to avoid common jitter and collision misalignment issues.

Transformation Variables in GameMaker: image_xscale and image_yscale

Every active instance inside a GameMaker room has a set of built-in variables that dictate how the engine draws its assigned sprite on screen. Among these properties are the horizontal and vertical scale factors: image_xscale and image_yscale.

By default, when an instance is created in the game, both variables have a decimal value of 1.0. This represents 100% of the sprite's original size: one pixel in the graphics file corresponds to exactly one pixel on the game's render canvas. If you set image_xscale to 2.0, the sprite renders at twice its normal width; setting it to 0.5 draws it at half width. The key insight lies in understanding what happens when this multiplier takes a negative value.

In computer graphics and geometry, multiplying an axis coordinate by a negative scalar produces a specular reflection across that axis's origin. Therefore, assigning -1.0 to image_xscale tells GameMaker to draw the sprite at its exact width (100%), but completely flipped from left to right. If the original sprite faces right, setting image_xscale to -1.0 mirrors it to face left, preserving all animation frames and sub-images without duplicating assets in the Asset Browser.

The Critical Role of the Sprite Origin

Scaling and flipping an image does not happen in a vacuum; it always occurs around the anchor point known as the sprite origin, configured in the sprite asset properties. The origin is the relative coordinate pair (X, Y) within the image canvas that matches the instance's x and y world coordinates in the room.

If your sprite origin is set to Top Left (coordinates 0, 0) and you set image_xscale to -1, GameMaker flips the sprite to the left using X = 0 as the reflection axis. Consequently, the entire character body suddenly shifts to the left of its physical world position, causing the character to visibly 'teleport' or snap horizontally by the full width of the sprite every time you change direction.

For the visual flip to look seamless without spatial snapping, the origin's X coordinate must be placed at the geometric center of the character's mass. This ensures the reflection occurs along the central vertical axis of the body: the right arm swaps places with the left arm and vice versa, keeping the torso in the exact same spot in the room.

Schematic diagram comparing a sprite flip with a Top-Left origin versus a correct, symmetrical flip using a Bottom-Center origin.
How the sprite origin affects flipping: an off-center origin offsets the graphic in the room, while a centered origin guarantees clean symmetry. · AI-generated image

Configuring the Sprite Origin in the Sprite Editor

Before writing any code to flip the sprite, we must ensure our visual asset is properly calibrated in the GameMaker interface. Follow these steps for your player sprite (spr_jugador):

1. In the Asset Browser, locate the Sprites folder and double-click spr_jugador to open the Sprite Editor in the workspace. 2. In the top-right panel of the editor, find the Origin dropdown menu. By default, GameMaker usually sets this to 'Top Left' (0, 0). 3. Open the dropdown and select 'Bottom Centre' or 'Middle Centre'. For a standard 2D platformer, the industry convention is 'Bottom Centre'. This sets the X coordinate to half the sprite's width and the Y coordinate to the bottom edge of the feet. 4. If your sprite has an even width of 32 pixels, the exact center sits at X = 16. If its height is 32 pixels, the base sits at Y = 32. You can verify these values in the numeric input fields next to the dropdown.

Setting the origin to 'Bottom Centre' offers a dual benefit: it streamlines ground detection for upcoming collision and jumping lessons, and it ensures horizontal flipping via image_xscale = -1 mirrors symmetrically around the character's vertical center line.

Implementing the Flip in GML Code

Recall the movement logic we implemented in the previous lesson inside the Step event of obj_jugador. We calculated the local variable _move_x by subtracting the left key input from the right key input, resulting in 1 (moving right), -1 (moving left), or 0 (standing still):

// Previous control reading snippet
var _tecla_der = keyboard_check(vk_right) || keyboard_check(ord("D"));
var _tecla_izq = keyboard_check(vk_left) || keyboard_check(ord("A"));

var _move_x = _tecla_der - _tecla_izq;
vel_h = _move_x * vel_caminar;
x += vel_h;

To update the sprite's facing direction, many beginners intuitively write: image_xscale = _move_x;. However, this introduces a critical bug: when the player releases the directional keys, _move_x drops to 0. If you assign image_xscale = 0, the sprite's width is multiplied by zero, making the character vanish from the screen whenever they stand still.

The proper logic requires a condition: we should only update image_xscale when the player is actively providing directional input—that is, when _move_x is not equal to zero. When there is no input, image_xscale should keep its previous value (whether 1 or -1), allowing the character to remain standing while facing the direction they were last moving.

// Update sprite horizontal facing direction
if (_move_x != 0) {
    image_xscale = _move_x;
}

Let's break down why this short block is so effective: - When the player presses the right key, _move_x is 1. Because 1 != 0 evaluates to true, image_xscale is set to 1. The sprite renders facing right at normal scale. - When the player presses the left key, _move_x is -1. Because -1 != 0 evaluates to true, image_xscale is set to -1. The sprite flips horizontally and faces left. - When the player releases both keys, _move_x is 0. The condition 0 != 0 evaluates to false, so the inner block does not run. image_xscale preserves its previous value (1 or -1), and the character stays visible, facing their last direction.

💡 Keep in mind that image_xscale flips not only the visual rendering of the sprite, but also its collision mask. If your sprite's collision mask is asymmetrical, inverting the scale shifts the bounds of the collider in the room. For this reason, it is best practice to keep collision masks symmetrical and centered on playable characters.

Handling Custom Base Scales

The previous code assumes your character sprite was imported at its final in-game size and that its natural scale is 1.0. However, in many indie projects, pixel art is created at low resolutions (such as 16x16 pixels) and scaled up globally in-game to 2.0, or high-resolution art is scaled down to 0.5.

If you simply write image_xscale = _move_x, you will force the scale to 1 or -1, overriding any custom scale defined in the Create event. To handle this professionally, we store the character's base visual scale in a dedicated variable.

In the Create event of obj_jugador, declare the baseline scale:

/// Create Event of obj_jugador
// Movement variables
vel_caminar = 4;
vel_h = 0;

// Base visual scale factor
escala_base_x = 1.0;
escala_base_y = 1.0;

// Initial scale assignment
image_xscale = escala_base_x;
image_yscale = escala_base_y;

Then, in the Step event, multiply the movement direction by our base scale when updating orientation:

/// Step Event of obj_jugador (facing direction snippet)
if (_move_x != 0) {
    image_xscale = _move_x * escala_base_x;
}

With this setup, if you later decide your character should appear twice as large (escala_base_x = 2.0), the code automatically applies 2.0 when moving right and -2.0 when moving left, preserving the intended proportions without affecting movement logic.

Origin Alignment Comparison Table

To reinforce this concept, let's examine how a 32x32 pixel sprite responds to horizontal flipping under different origin presets available in the GameMaker Sprite Editor:

Origin AlignmentCoordinates (X, Y)Behavior at image_xscale = 1Behavior at image_xscale = -1Recommended Use
Top LeftX: 0, Y: 0Sprite renders from (x) to (x + 32).Sprite renders from (x - 32) to (x). Visually snaps 32 pixels to the left.Not recommended for playable characters.
Middle CentreX: 16, Y: 16Sprite renders from (x - 16) to (x + 16).Sprite mirrors cleanly across its vertical center without shifting position.Great for spaceships, projectiles, and top-down games.
Bottom CentreX: 16, Y: 32Sprite renders from (x - 16) to (x + 16), resting on (y).Sprite mirrors symmetrically with feet anchored on the ground plane.Industry standard for 2D platformers.
Custom (Off-Center)X: 10, Y: 32Sprite renders asymmetrically.Flipping shifts the visual center by 12 pixels, causing jarring jitter.Avoid unless required for specific asymmetrical mechanics.

GML Visual Equivalent (Visual Scripting)

While our course focuses primarily on text-based GML Code due to its speed and industry adoption, understanding how this logic maps to GameMaker's visual block interface (GML Visual) is valuable.

In GML Visual, this logic is built using two connected blocks in the Step event: 1. 'If Variable' block: Evaluates the variable _move_x with the condition 'Not Equal' to 0. 2. 'Set Instance Scale' (or 'Assign Variable') block: Nested inside the conditional branch, assigns the value _move_x (or _move_x * escala_base_x) to image_xscale, leaving the 'Relative' checkbox unchecked. This confirms that whether you use visual nodes or written code, GameMaker's underlying mathematical architecture remains identical.

Full Practical Example: Integration in obj_jugador

Here is the complete, integrated script structure for obj_jugador, bringing together input handling, horizontal velocity calculation, and sprite flipping covered across this module.

/// ========================================== ///
/// OBJECT: obj_jugador                        ///
/// EVENT: Create                             ///
/// ========================================== ///

// --- Horizontal movement parameters ---
vel_caminar = 4;        // Constant movement speed (pixels per step)
vel_h = 0;              // Current horizontal velocity vector

// --- Rendering and scale parameters ---
escala_base_x = 1.0;    // Base horizontal scale magnitude
escala_base_y = 1.0;    // Base vertical scale magnitude

image_xscale = escala_base_x;
image_yscale = escala_base_y;
/// ========================================== ///
/// OBJECT: obj_jugador                        ///
/// EVENT: Step                               ///
/// ========================================== ///

// 1. INPUT POLLING (Keyboard and Gamepad)
var _tecla_der = keyboard_check(vk_right) || keyboard_check(ord("D"));
var _tecla_izq = keyboard_check(vk_left) || keyboard_check(ord("A"));
var _stick_h   = gamepad_axis_value(0, gp_axislh);

// Calculate combined horizontal direction
var _move_x = _tecla_der - _tecla_izq;

// If no keyboard input is active but stick is tilted past deadzone
if (_move_x == 0 && abs(_stick_h) > 0.25) {
    _move_x = sign(_stick_h);
}

// 2. PHYSICS: VELOCITY AND POSITION UPDATE
vel_h = _move_x * vel_caminar;
x += vel_h;

// 3. SPRITE FACING DIRECTION
// Only update scale when there is active directional input
if (_move_x != 0) {
    image_xscale = _move_x * escala_base_x;
}

Common Pitfalls and Troubleshooting

When implementing sprite flipping, beginner developers often encounter a few classic issues. Identifying them early will save you hours of debugging:

1. The sprite flickers or disappears when stopping: This occurs if you forgot to wrap the scale assignment inside the if (_move_x != 0) condition. When you release the keys, _move_x becomes 0. If you execute image_xscale = _move_x, the scale becomes 0 and the sprite vanishes, reappearing only when an input key is pressed again.

2. The character appears to float or sink halfway into the floor upon turning: This happens when the Y origin is misaligned between different sprites or animation sub-images, or when the X origin is slightly off the true visual center. Open spr_jugador in the editor and ensure the crosshair guide cuts right through the center line of the character.

3. The player gets stuck inside walls when turning: If your sprite uses 'Precise Collision Checking' or an asymmetrical collision mask relative to the origin, flipping image_xscale alters the shape or bounds of the collider. If you are standing against a solid obstacle, the mirrored edge can clip into the wall, causing the physics or collision engine to trap the player. To avoid this, select a fixed 'Rectangle' in the sprite's Collision Mask settings and ensure its boundaries are symmetrical relative to the origin.

Hands-On Exercises

Summary

  • The built-in image_xscale variable multiplies the sprite's width by a scalar factor; assigning a negative value mirrors the sprite horizontally.
  • The sprite origin serves as the pivot for flipping; 'Bottom Centre' is the recommended standard for 2D platformers because it ensures symmetry and stability along the ground.
  • Assigning image_xscale must always be conditioned on movement input being non-zero (if (_move_x != 0)) to prevent the sprite from collapsing to zero width or snapping back when the character stops.
  • Using a custom variable like escala_base_x allows you to flip the character's facing direction while preserving any custom visual scaling applied to the project.
  • Collision masks flip alongside image_xscale; keeping collision boxes rectangular and symmetrical prevents characters from clipping into walls when changing direction.

Practise what you learned

Exercise 1 · 1 point

What issue occurs if you directly assign `image_xscale = _move_x;` in the Step event without checking a condition first?

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

Exercise 2 · 1 point

Complete the following GML code block to flip the sprite's horizontal orientation only when the character is moving: ```gml if (_move_x 0) { = _move_x; } ```

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

Exercise 3 · 1 point

Match each sprite value or configuration with its technical and visual outcome:

  • image_xscale = 1.0
  • image_xscale = -1.0
  • image_xscale = 0
  • Origin set to Top Left (0, 0)
You are practising without an account: nothing is saved. Sign up to keep your progress.

Sign in to save your progress — Sign in