09/09/2014
En el mundo del desarrollo de videojuegos con GameMaker, la función draw_sprite_ext es una de nuestras herramientas más utilizadas. Nos permite dibujar un sprite en pantalla con control sobre su posición, escala, rotación, color de mezcla y transparencia. Es increíblemente versátil para la mayoría de las tareas diarias. Sin embargo, ¿qué sucede cuando queremos ir un paso más allá? ¿Qué pasa si necesitamos aplicar deformaciones más complejas, como el sesgado (skewing) o cizallado (shearing), para crear efectos de pseudo-3D, proyecciones isométricas o animaciones de impacto más dinámicas? Para eso, la función estándar se queda corta.

Aquí es donde entra en juego la magia de las transformaciones afines. Estas transformaciones nos permiten no solo escalar y rotar, sino también deformar un sprite de maneras que draw_sprite_ext no puede manejar. En este artículo, exploraremos cómo superar estas limitaciones utilizando una función un poco menos conocida pero inmensamente poderosa: draw_sprite_pos. A través de un poco de matemáticas y un script personalizado, podrás añadir un nuevo arsenal de efectos visuales a tus proyectos.
La limitación de lo estándar y la solución
Como mencionamos, draw_sprite_ext es fantástica, pero opera bajo un modelo rígido: escala el sprite en sus ejes X e Y y luego lo rota. No hay forma de decirle que incline sus bordes verticales u horizontales. Este efecto, conocido como skew, es fundamental para simular perspectiva o para crear ciertos estilos artísticos.
La solución se encuentra en draw_sprite_pos. A diferencia de su prima más famosa, esta función no trabaja con escala y rotación. En su lugar, nos pide las coordenadas exactas de las cuatro esquinas del sprite (superior-izquierda, superior-derecha, inferior-derecha, inferior-izquierda). Esto nos da un control absoluto sobre el cuadrilátero en el que se dibujará nuestro sprite, permitiéndonos deformarlo de cualquier manera que se nos ocurra. El desafío, por supuesto, es calcular esas cuatro coordenadas correctamente para lograr el efecto deseado.
La matemática detrás de la deformación
Para crear nuestro script personalizado, necesitamos calcular la posición final de cada una de las cuatro esquinas del sprite después de aplicar todas las transformaciones: escala, rotación y, finalmente, el sesgado. El proceso se puede desglosar en los siguientes pasos para cada esquina:
- Coordenadas locales: Primero, determinamos la posición de cada esquina en relación con el punto de origen del sprite. Por ejemplo, la esquina superior izquierda estaría en
(-xoffset, -yoffset). - Escalado: Multiplicamos estas coordenadas locales por los factores de escala
xscaleyyscale. - Rotación: Aplicamos la fórmula de rotación 2D usando el coseno y el seno del ángulo deseado. Esto nos dará las coordenadas de la esquina después de haber sido rotada alrededor del origen.
- Sesgado (Skew): Esta es la parte nueva. El sesgado desplaza un eje en función de la posición en el otro. Por ejemplo, para sesgar en el eje X, añadimos a la coordenada X un valor proporcional a su coordenada Y.
- Escalado final: Opcionalmente, podemos aplicar un segundo escalado después de todas las transformaciones. Esto es útil para, por ejemplo, aplastar un sprite verticalmente sin importar su rotación.
- Traslación a la posición final: Finalmente, sumamos las coordenadas de la instancia (la posición en la habitación) a las coordenadas transformadas de la esquina para obtener su posición final en la pantalla.
Aunque pueda parecer complejo, este proceso se traduce en un código bastante directo una vez que se entiende la lógica.
El script: draw_sprite_ext_skew
A continuación, presentamos un script completo que encapsula toda esta lógica. Este script, al que llamaremos draw_sprite_ext_skew, se comportará de manera similar a la función original pero con parámetros adicionales para el control del sesgado y un escalado post-transformación.
Parámetros del script
El script aceptará los argumentos estándar de draw_sprite_ext más cuatro nuevos:
sprite: El índice del sprite a dibujar.subimg: El subíndice (frame) de la animación.x, y: Las coordenadas donde se dibujará el sprite.xscale, yscale: El escalado inicial horizontal y vertical.rot: El ángulo de rotación en grados.alpha: La transparencia (de 0 a 1).kx: El factor de sesgado en X. Determina cuánto se desplaza cada píxel horizontalmente por cada píxel de distancia vertical desde el origen.ky: El factor de sesgado en Y. Determina cuánto se desplaza cada píxel verticalmente por cada píxel de distancia horizontal.xmult, ymult: Factores de escalado final que se aplican después de la rotación y el sesgado.
El código GML
Aquí está el código completo del script. Puedes crearlo como un nuevo recurso de Script en tu proyecto de GameMaker.
/// draw_sprite_ext_skew(sprite, subimg, x, y, xscale, yscale, rot, alpha, kx, ky, xmult, ymult)/// @description Dibuja un sprite con transformaciones afines, incluyendo sesgado (skew)./// @argument sprite El índice del sprite./// @argument subimg El frame de la animación./// @argument x La coordenada X./// @argument y La coordenada Y./// @argument xscale El escalado horizontal./// @argument yscale El escalado vertical./// @argument rot La rotación en grados./// @argument alpha La transparencia (0-1)./// @argument kx Factor de sesgado X (cuánto se desplaza X por cada píxel de Y)./// @argument ky Factor de sesgado Y (cuánto se desplaza Y por cada píxel de X)./// @argument xmult Multiplicador de escala X post-rotación/sesgado./// @argument ymult Multiplicador de escala Y post-rotación/sesgado.// Obtenemos los argumentosvar sprite = argument0;var subimg = argument1;var _x = argument2;var _y = argument3;var scalex = argument4;var scaley = argument5;var rot = argument6;var alpha = argument7;var skew_kx = argument8;var skew_ky = argument9;var skew_sx = argument10;var skew_sy = argument11;// Calculamos valores que se reutilizarán:var rcos = dcos(rot);var rsin = -dsin(rot);var x1 = -sprite_get_xoffset(sprite) * scalex;var x2 = x1 + sprite_get_width(sprite) * scalex;var y1 = -sprite_get_yoffset(sprite) * scaley;var y2 = y1 + sprite_get_height(sprite) * scaley;// Arrays para guardar las coordenadas de las esquinas. Se recomienda declararlos// como globales o de instancia para no crearlos y destruirlos en cada fotograma.if (!variable_global_exists("_draw_sprite_ext_skew_x")) { global._draw_sprite_ext_skew_x = array_create(4); global._draw_sprite_ext_skew_y = array_create(4);}// Calculamos las coordenadas de las esquinasfor (var c = 0; c < 4; c++) { // Seleccionamos la esquina local var lx = (c & 1) ? x2: x1; var ly = (c & 2) ? y2: y1; // Aplicamos la rotación var rx = lx * rcos - ly * rsin; var ry = lx * rsin + ly * rcos; // Aplicamos el sesgado, el escalado final y la traslación a la posición global global._draw_sprite_ext_skew_x[c] = _x + (rx + ry * skew_kx) * skew_sx; global._draw_sprite_ext_skew_y[c] = _y + (ry + rx * skew_ky) * skew_sy;}// Dibujamos el cuadrilátero del spritedraw_sprite_pos(sprite, subimg, global._draw_sprite_ext_skew_x[0], global._draw_sprite_ext_skew_y[0], // Esquina superior izquierda global._draw_sprite_ext_skew_x[1], global._draw_sprite_ext_skew_y[1], // Esquina superior derecha global._draw_sprite_ext_skew_x[3], global._draw_sprite_ext_skew_y[3], // Esquina inferior derecha global._draw_sprite_ext_skew_x[2], global._draw_sprite_ext_skew_y[2], // Esquina inferior izquierda alpha);Tabla Comparativa de Funciones
Para entender mejor las ventajas, aquí tienes una comparación directa:
| Característica | draw_sprite_ext | draw_sprite_ext_skew (nuestro script) |
|---|---|---|
| Posición | Sí | Sí |
| Escalado | Sí | Sí (inicial y final) |
| Rotación | Sí | Sí |
| Mezcla de color | Sí | No (limitación de draw_sprite_pos) |
| Transparencia | Sí | Sí |
| Sesgado (Skew/Shear) | No | Sí |
| Control por esquina | No | Sí (implícito) |
Consideración Importante: Recorte de Sprites
GameMaker, por optimización, a menudo recorta los píxeles transparentes alrededor de tus sprites en las páginas de texturas. Esto puede causar que nuestro script no funcione como se espera, ya que se basa en el ancho y alto total del sprite. Si notas que tus sprites deformados se ven recortados, tienes dos opciones:
- Desactivar el recorte en la configuración de la página de texturas correspondiente.
- Modificar el script para que utilice las dimensiones del "bounding box" del sprite en lugar de su ancho y alto totales.
Para la segunda opción, simplemente reemplaza este bloque de código:
var x1 = -sprite_get_xoffset(sprite) * scalex;var x2 = x1 + sprite_get_width(sprite) * scalex;var y1 = -sprite_get_yoffset(sprite) * scaley;var y2 = y1 + sprite_get_height(sprite) * scaley;Por este otro:
var cx = sprite_get_bbox_left(sprite);var cy = sprite_get_bbox_top(sprite);var x1 = (cx - sprite_get_xoffset(sprite)) * scalex;var x2 = x1 + (sprite_get_bbox_right(sprite) + 1 - cx) * scalex;var y1 = (cy - sprite_get_yoffset(sprite)) * scaley;var y2 = y1 + (sprite_get_bbox_bottom(sprite) + 1 - cy) * scaley;Este nuevo código calcula las dimensiones basándose únicamente en la parte visible del sprite, solucionando el problema del recorte.
Preguntas Frecuentes (FAQ)
¿Afecta mucho al rendimiento usar este script en lugar de draw_sprite_ext?
El impacto en el rendimiento es mínimo en la mayoría de los casos. Nuestro script realiza más cálculos matemáticos por cada sprite dibujado (un bucle, multiplicaciones, sumas), pero las CPUs modernas son extremadamente rápidas en estas operaciones. A menos que estés dibujando decenas de miles de sprites deformados por fotograma, es muy poco probable que notes una diferencia. Como siempre, la optimización debe hacerse donde sea necesaria, así que testea tu juego para estar seguro.
¿Por qué el script no tiene un parámetro de mezcla de color (color)?
La función base que utilizamos, draw_sprite_pos, no admite un argumento para la mezcla de color como sí lo hace draw_sprite_ext. Para lograr un efecto similar, la solución más robusta sería utilizar shaders. Podrías pasar el color deseado como un parámetro "uniform" a un shader y aplicarlo justo antes de llamar a la función de dibujo.
¿Puedo usar esto para crear un efecto "Modo 7"?
¡Absolutamente! Esta técnica es uno de los pilares fundamentales para lograr un efecto similar al famoso "Modo 7" de la SNES. El Modo 7 consiste en dibujar un plano (como un suelo o un mapa) con transformaciones de perspectiva, lo cual se logra aplicando transformaciones afines a segmentos de la textura. Nuestro script es una herramienta perfecta para empezar a experimentar con esos efectos.
¿Qué son exactamente dcos y dsin?
Son las funciones trigonométricas Coseno y Seno que aceptan un ángulo en grados (de ahí la 'd' de 'degrees'). Son esenciales en gráficos 2D para cualquier cálculo que involucre rotación. En nuestro script, nos ayudan a encontrar la nueva posición de un punto después de rotarlo alrededor de un eje.
Conclusión
Dominar las transformaciones afines abre un nuevo mundo de posibilidades visuales para tus juegos en GameMaker. Aunque las funciones integradas son potentes, entender cómo funcionan a un nivel más profundo y cómo construir sobre ellas con herramientas como draw_sprite_pos te permite romper las barreras creativas. Con el script draw_sprite_ext_skew, ahora tienes el poder de sesgar, inclinar y deformar tus sprites para crear sombras, suelos isométricos, interfaces de usuario dinámicas y efectos especiales que antes parecían imposibles. ¡Ahora te toca experimentar y llevar tus gráficos al siguiente nivel!
Si quieres conocer otros artículos parecidos a Sprites con Transformaciones Afines en GameMaker puedes visitar la categoría Juegos.
