Introducción al Desarrollo de Videojuegos con GameMaker · Módulo 4: Controles, movimientos y manejo del jugador.

Inversión de sprites según la dirección

Clase 18 de 60 · 45 min

También disponible en: EN

Introducción a la orientación visual del personaje

En las dos primeras clases de este módulo establecimos las bases para capturar las pulsaciones del teclado y el gamepad, y transformamos esas lecturas en un desplazamiento físico horizontal fluido para nuestro objeto jugador (obj_jugador). Sin embargo, si has probado tu proyecto hasta este momento, habrás notado un detalle visual evidente: cuando el personaje camina hacia la izquierda, su ilustración continúa mirando hacia la derecha. El héroe parece desplazarse de espaldas, deslizándose como sobre hielo en reversa.

En la era clásica de los videojuegos de 8 y 16 bits, la memoria de video y los cartuchos eran sumamente limitados. Los desarrolladores no podían permitirse dibujar dos secuencias completas de animación para cada personaje (una mirando hacia la derecha y otra hacia la izquierda), ya que esto duplicaría el consumo de memoria gráfica de forma innecesaria. La solución técnica universal consistía en almacenar una única versión del sprite —comúnmente mirando hacia la derecha— y aplicar un reflejo geométrico por hardware en el eje horizontal cuando el personaje se moviese hacia la izquierda. GameMaker implementa esta misma técnica mediante la manipulación de variables internas de transformación matricial.

En esta clase aprenderás a manipular la variable incorporada image_xscale para invertir tu sprite en tiempo real de manera limpia, eficiente y matemáticamente precisa. Analizaremos por qué esta técnica ahorra tiempo en la producción artística, cuál es el rol crítico del punto de origen o pivote del sprite, y cómo evitar los errores más comunes de temblor o desalineación física.

Variables de transformación en GameMaker: image_xscale e image_yscale

Cada instancia activa dentro de una room en GameMaker posee un conjunto de variables incorporadas (built-in variables) que controlan cómo el motor dibuja su sprite asignado en pantalla. Entre estas variables se encuentran las propiedades de escala horizontal y vertical: image_xscale e image_yscale.

Por defecto, cuando una instancia se crea en el juego, ambas variables tienen un valor decimal de 1.0. Este valor representa el 100 % del tamaño original del sprite: un píxel del archivo gráfico equivale exactamente a un píxel en el lienzo de renderizado del juego. Si alteras image_xscale asignándole un valor de 2.0, el sprite se dibujará al doble de su anchura normal; si le asignas 0.5, se dibujará a la mitad de su anchura. Lo crucial es comprender qué sucede cuando este factor numérico toma un valor negativo.

En geometría por computadora, multiplicar una coordenada sobre un eje por un escalar negativo genera una reflexión especular respecto al origen de dicho eje. Por consiguiente, si asignamos a image_xscale el valor de -1.0, GameMaker dibuja el sprite con su anchura exacta (100 %), pero completamente reflejado de izquierda a derecha. Si el dibujo original miraba a la derecha, al valer -1.0 mirará exactamente a la izquierda, conservando todas las animaciones y subimágenes sin necesidad de duplicar recursos en el explorador de assets.

El rol determinante del origen del sprite (Origin Point)

El escalado e inversión de una imagen no ocurre en el vacío; se produce siempre tomando como punto de anclaje el punto de origen (origin) definido en la configuración del sprite. Este punto de origen es el par de coordenadas relativas (X, Y) dentro de la imagen que coincide exactamente con la posición x e y de la instancia en la room.

Si el origen de tu sprite está ubicado en la esquina superior izquierda (Top Left, coordenadas 0, 0) y estableces image_xscale en -1, GameMaker reflejará el sprite hacia la izquierda tomando como eje la coordenada X = 0. En consecuencia, todo el cuerpo del personaje se proyectará repentinamente hacia la izquierda de su coordenada física en el mundo, haciendo que parezca 'teletransportarse' o saltar bruscamente la distancia correspondiente al ancho total del sprite cada vez que cambias de dirección.

Para que la inversión visual se perciba natural y sin brincos espaciales, el punto de origen en el eje X debe situarse en el centro geométrico de la masa del personaje. De esta forma, el reflejo se produce a lo largo de la línea vertical media del cuerpo: el brazo derecho pasa a ocupar el lugar del izquierdo y viceversa, manteniendo el torso exactamente en la misma posición de la room.

Diagrama esquemático que compara el reflejo de un sprite con origen en la esquina superior izquierda frente al reflejo simétrico y correcto con origen en el centro inferior.
Impacto del punto de origen en la inversión: un origen descentrado desplaza el dibujo en la room, mientras que un origen centrado asegura simetría perfecta. · Imagen generada con IA

Configuración del punto de origen en el Sprite Editor

Antes de escribir una sola línea de código para invertir el sprite, debemos asegurarnos de que el recurso gráfico esté debidamente calibrado en la interfaz de GameMaker. Para ello, realiza el siguiente procedimiento con tu sprite de jugador (spr_jugador):

1. En el panel del explorador de recursos (Asset Browser), localiza la carpeta Sprites y haz doble clic sobre spr_jugador para abrir el Sprite Editor en el espacio de trabajo. 2. En el panel superior derecho del editor verás un menú desplegable denominado Origin. Por defecto, GameMaker suele configurarlo en 'Top Left' (0, 0). 3. Despliega la lista y selecciona 'Bottom Centre' (Centro inferior) o 'Middle Centre' (Centro medio). Para un videojuego de plataformas clásico, la convención más recomendada y estandarizada es 'Bottom Centre'. Esto sitúa la coordenada X en la mitad del ancho del sprite y la coordenada Y en el borde inferior de los pies. 4. Si tu sprite tiene una anchura par de 32 píxeles, el centro exacto se ubicará en X = 16. Si su altura es de 32 píxeles, la base estará en Y = 32. Puedes verificar estos valores numéricos directamente en los campos numéricos adyacentes al menú desplegable.

Ubicar el origen en 'Bottom Centre' ofrece una doble ventaja: simplifica la detección de suelo en las futuras lecciones de colisión y salto, y garantiza que la inversión horizontal a través de image_xscale = -1 se realice simétricamente respecto al eje vertical central del personaje.

Implementación de la inversión con GML Code

Recordemos la lógica de movimiento que programamos en la lección anterior dentro del evento Step de obj_jugador. Allí calculábamos la variable local move_x mediante la diferencia entre la tecla derecha y la tecla izquierda, obteniendo un valor de 1 (derecha), -1 (izquierda) o 0 (quieto):

// Fragmento previo de lectura de controles
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;

Para actualizar la orientación del sprite, el instinto inicial de muchos principiantes es escribir directamente: image_xscale = _move_x;. Sin embargo, esto introduce un error crítico: cuando el jugador suelta las teclas de dirección, _move_x se convierte en 0. Si asignas image_xscale = 0, el ancho del sprite se multiplica por cero, provocando que el personaje desaparezca por completo de la pantalla mientras está inmóvil.

La solución lógica requiere una condición: solo debemos actualizar image_xscale cuando el jugador realmente esté presionando una dirección de movimiento, es decir, cuando _move_x sea diferente de cero. Si no hay movimiento, image_xscale debe conservar su último valor asignado (sea 1 o -1), permitiendo que el personaje permanezca de pie mirando hacia la dirección a la que se dirigía por última vez.

// Actualización de la orientación horizontal del sprite
if (_move_x != 0) {
    image_xscale = _move_x;
}

Analicemos la elegancia de este bloque de código: - Cuando el jugador pulsa la tecla derecha, _move_x vale 1. Como 1 != 0 es verdadero, image_xscale adopta el valor 1. El sprite se dibuja orientado a la derecha en su escala normal. - Cuando el jugador pulsa la tecla izquierda, _move_x vale -1. Como -1 != 0 es verdadero, image_xscale adopta el valor -1. El sprite se refleja horizontalmente y mira a la izquierda. - Cuando el jugador suelta ambas teclas, _move_x vale 0. La condición 0 != 0 es falsa, por lo que el bloque interno no se ejecuta. image_xscale conserva su valor previo (1 o -1), y el personaje se mantiene visible y mirando correctamente hacia su último destino.

💡 Ten presente que image_xscale no solo invierte la representación visual del sprite, sino también su máscara de colisión (Collision Mask). Si la máscara de tu sprite es asimétrica, invertir la escala modificará la posición de los límites de colisión en la room. Por esta razón, se recomienda utilizar máscaras centradas y simétricas para los personajes jugables.

Manejo de escalas base personalizadas

El código anterior asume que tu personaje fue importado en su tamaño definitivo y que su escala natural en el juego es exactamente 1. No obstante, en muchos desarrollos independientes es común que los sprites se dibujen a baja resolución (por ejemplo, 16x16 píxeles) y se decida ampliar su escala general en el juego a 2.0, o bien reducir un sprite de alta definición a 0.5.

Si simplemente asignas image_xscale = _move_x, forzarás el tamaño a 1 o -1, perdiendo la escala personalizada que habías definido en el evento Create. Para solucionar este inconveniente de forma profesional, creamos una variable que guarde la escala base del personaje.

En el evento Create de obj_jugador, definimos la escala de referencia:

/// Evento Create de obj_jugador
// Variables de movimiento
vel_caminar = 4;
vel_h = 0;

// Factor de escala visual base
escala_base_x = 1.0;
escala_base_y = 1.0;

// Asignación inicial de escala
image_xscale = escala_base_x;
image_yscale = escala_base_y;

Luego, en el evento Step, al momento de actualizar la dirección multiplicamos el signo del movimiento por nuestra escala base:

/// Evento Step de obj_jugador (fragmento de orientación)
if (_move_x != 0) {
    image_xscale = _move_x * escala_base_x;
}

Con este método, si en el futuro decides que tu personaje debe verse el doble de grande (escala_base_x = 2.0), el código asignará automáticamente 2.0 al moverse a la derecha y -2.0 al moverse a la izquierda, preservando siempre la proporción estética elegida sin alterar la lógica de movimiento.

Tabla comparativa de configuraciones de punto de origen

Para consolidar este principio técnico, examinemos cómo reacciona un sprite de 32x32 píxeles ante una inversión horizontal según las diferentes opciones de punto de origen disponibles en el editor de GameMaker:

Alineación del origenCoordenadas (X, Y)Comportamiento al valer image_xscale = 1Comportamiento al valer image_xscale = -1Recomendación de uso
Top LeftX: 0, Y: 0El sprite se dibuja de (x) a (x + 32).El sprite se dibuja de (x - 32) a (x). Da un salto visual notable hacia la izquierda.No recomendado para personajes jugables.
Middle CentreX: 16, Y: 16El sprite se dibuja de (x - 16) a (x + 16).El sprite se refleja perfectamente sobre su eje vertical medio sin moverse del lugar.Excelente para naves espaciales, proyectiles y juegos con vista cenital.
Bottom CentreX: 16, Y: 32El sprite se dibuja de (x - 16) a (x + 16), apoyado en (y).El sprite se refleja simétricamente y los pies permanecen en la misma base.Estándar absoluto para juegos de plataformas 2D.
Custom (Descentrado)X: 10, Y: 32El sprite se dibuja asimétricamente.Al invertir, el centro de masa se desplaza 12 píxeles, causando vibración visual.Desaconsejado salvo requerimientos mecánicos muy específicos.

Equivalencia en GML Visual (Visual Scripting)

Aunque nuestro curso se fundamenta principalmente en la escritura de código textual con GML Code debido a su velocidad y flexibilidad en entornos de desarrollo profesional, es instructivo conocer cómo se estructura esta misma operación en la interfaz visual de bloques de GameMaker (GML Visual).

En GML Visual, la lógica se ensambla mediante dos bloques encadenados dentro del evento Step: 1. Bloque 'If Variable': Se evalúa la variable _move_x con la condición 'Not Equal' (Distinto de) y valor 0. 2. Bloque 'Set Instance Scale' (o 'Assign Variable'): Dentro del alcance de la condición afirmativa, se asigna el valor _move_x (o _move_x * escala_base_x) a la propiedad image_xscale, dejando la casilla 'Relative' desmarcada. Esto demuestra que, sin importar si programas con nodos visuales o con texto, la arquitectura matemática subyacente de GameMaker es exactamente la misma.

Ejemplo práctico completo: Integración en obj_jugador

A continuación presentamos la estructura íntegra y contextualizada del objeto obj_jugador, combinando los conocimientos de entrada de datos, cálculo de velocidad horizontal e inversión visual aprendidos a lo largo de este cuarto módulo.

/// ========================================== ///
/// OBJETO: obj_jugador                        ///
/// EVENTO: Create                             ///
/// ========================================== ///

// --- Parámetros de movimiento horizontal ---
vel_caminar = 4;        // Velocidad constante de desplazamiento (píxeles por paso)
vel_h = 0;              // Vector de velocidad horizontal actual

// --- Parámetros de renderizado y escala ---
escala_base_x = 1.0;    // Magnitud de escala horizontal base
escala_base_y = 1.0;    // Magnitud de escala vertical base

image_xscale = escala_base_x;
image_yscale = escala_base_y;
/// ========================================== ///
/// OBJETO: obj_jugador                        ///
/// EVENTO: Step                               ///
/// ========================================== ///

// 1. LECTURA DE CONTROLES (Teclado y 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);

// Procesamiento de dirección horizontal combinada
var _move_x = _tecla_der - _tecla_izq;

// Si no hay teclado pero el stick se inclina más allá de la zona muerta (deadzone)
if (_move_x == 0 && abs(_stick_h) > 0.25) {
    _move_x = sign(_stick_h);
}

// 2. CÁLCULO FÍSICO DE VELOCIDAD Y POSICIÓN
vel_h = _move_x * vel_caminar;
x += vel_h;

// 3. INVERSIÓN VISUAL DEL SPRITE
// Solo alteramos la escala cuando existe intención de movimiento
if (_move_x != 0) {
    image_xscale = _move_x * escala_base_x;
}

Errores comunes y depuración técnica

Al implementar la inversión de sprites, los desarrolladores novatos suelen enfrentarse a ciertos tropiezos clásicos. Conocerlos de antemano te ahorrará horas de desconcierto:

1. El sprite parpadea o tiembla al quedarse quieto: Ocurre si olvidaste encerrar la asignación dentro del condicional if (_move_x != 0). Cuando dejas de pulsar botones, _move_x vale 0. Si ejecutas image_xscale = _move_x, la escala se vuelve 0 y el sprite desaparece, reapareciendo en cuanto tocas una tecla.

2. El personaje parece flotar o hundirse medio cuerpo al voltearse: Esto sucede cuando el origen del sprite en Y no está ubicado en el mismo nivel relativo entre diferentes sprites o subimágenes, o cuando el origen X está ligeramente corrido de la línea central real del dibujo. Abre el editor de spr_jugador y asegúrate de que el punto guía (la diana cruzada blanca) corte exactamente por la mitad el cuerpo del personaje.

3. El jugador queda atrapado dentro de una pared al girar: Si tu sprite tiene una máscara de colisión precisa (Precise Collision Checking) o una máscara asimétrica respecto al punto de origen, al invertir image_xscale la caja física cambia de forma. Si estás muy cerca de un obstáculo, el borde invertido puede superponerse con el sólido, provocando que el motor de físicas bloquee al jugador. Para evitar esto, en la configuración de la máscara de colisión (Collision Mask) del sprite, selecciona siempre una forma rectangular fija (Rectangle) y ajusta sus márgenes de modo que sean perfectamente simétricos respecto al origen.

Ejercicios prácticos

Resumen

  • La variable interna image_xscale multiplica la anchura del sprite por un factor escalar; asignarle un valor negativo refleja la imagen horizontalmente.
  • El punto de origen (Origin) del sprite actúa como pivote de la inversión; la configuración 'Bottom Centre' es el estándar recomendado para plataformas 2D ya que garantiza simetría y estabilidad sobre el terreno.
  • La asignación de image_xscale debe condicionarse siempre a que la variable de movimiento no sea cero (if (_move_x != 0)) para evitar que el sprite desaparezca o revierta su orientación cuando el personaje se detiene.
  • Utilizar una variable personalizada como escala_base_x permite invertir la dirección del personaje manteniendo intactos los factores de escalado estético elegidos para el proyecto.
  • Las máscaras de colisión se reflejan conjuntamente con image_xscale; diseñar cajas de colisión simétricas y rectangulares previene que el personaje se atasque dentro de las paredes al girar.

Practica lo aprendido

Ejercicio 1 · 1 punto

¿Qué problema ocurre si se escribe directamente la asignación `image_xscale = _move_x;` en el evento Step sin verificar una condición previa?

Practicas sin cuenta: no se guarda nada. Regístrate para guardar tu progreso.

Ejercicio 2 · 1 punto

Completa el siguiente bloque en GML para invertir la orientación horizontal del sprite únicamente cuando el personaje se esté desplazando: ```gml if (_move_x 0) { = _move_x; } ```

Practicas sin cuenta: no se guarda nada. Regístrate para guardar tu progreso.

Ejercicio 3 · 1 punto

Relaciona cada valor o configuración de sprite con la consecuencia visual y técnica descrita en la lección:

  • image_xscale = 1.0
  • image_xscale = -1.0
  • image_xscale = 0
  • Origen en Top Left (0, 0)
Practicas sin cuenta: no se guarda nada. Regístrate para guardar tu progreso.

Ingresa para guardar tu progreso — Ingresar