GameMaker: draw_sprite_ext_skew

(click to interact) (mouseover/click to play GIF) Drag sliders and click things (if JS is enabled)

This post is about a slightly fancier version of draw_sprite_ext - maybe you want to apply skewing/shearing to the sprite, or scale it after rotating it, or use a 2d matrix transformation.

The built-in function isn't made for that, but, with a little bit of math and draw_sprite_pos, you can have a script for this.

The idea

First, meet draw_sprite_pos.

draw_sprite_pos allows you to draw sprites with arbitrary affine transformations by providing coordinates of each corner of the sprite. That's exactly what the situation demands.

From hereon, the trick is to calculate coordinates of each point correctly.

Fortunately, this is no complicated matter, and I already wrote a tutorial about this before.

The parameters

The first bunch of these are the same as those of regular draw_sprite_ext - you have sprite index, image index, position, scale, rotation, and opacity (no color blending because draw_sprite_pos doesn't have that). Then come the 4 additional parameters,

Skew/shear X

Essentially, this determines how much every pixel of sprite is offset on X axis for each subsequent pixel of Y axis.

So, a value of 1 would cause a normally rectangular sprite to be drawn as a rhombus, left and right edges aligned to point between -X-Y and +X+Y.

Skew/shear Y

Same as above, but for affecting Y axis. Can also be utilized for isometric projection.

Mult X/Y

This is much like regular scale but it is applied after rotating and skewing the image.

So, for example, if you set Mult Y to 0.5, the image will always be vertically squished, regardless of rotation angle.

The code

With aforementioned things in mind, everything is pretty straightforward here.

A pair of global arrays is created and reused for corner coordinates as it doesn't make much of a difference in this case, but you probably don't want to create-destroy a new array every time you draw a sprite.

/// draw_sprite_ext_skew(sprite,subimg, x,y, xscale,yscale, rot,alpha, kx,ky, xmult,ymult)
/// https://yal.cc/draw_sprite_ext_skew
/// @arg sprite
/// @arg subimg
/// @arg x
/// @arg y
/// @arg xscale
/// @arg yscale
/// @arg rot
/// @arg alpha
/// @arg kx How much X skews per each pixel of Y
/// @arg ky How much Y skews per each pixel of X
/// @arg xmult Post-skew, post-rotate scale X
/// @arg ymult Post-skew, post-rotate scale Y

// get the arguments:
var sprite = argument0, subimg = argument1, _x = argument2, _y = argument3,
    scalex = argument4, scaley = argument5, rot = argument6, alpha = argument7,
    skew_kx = argument8, skew_ky = argument9, skew_sx = argument10, skew_sy = argument11;

// compute values that will be reused:
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;

// compute corner coordinates:
for (var c = 0; c < 4; c++) {
    // pick local corner
    var lx; if (c & 1) lx = x2; else lx = x1;
    var ly; if (c & 2) ly = y2; else ly = y1;
    // see https://yal.cc/2d-pivot-points/:
    var rx = lx * rcos - ly * rsin;
    var ry = lx * rsin + ly * rcos;
    // transform and store corner coordinates:
    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;
}

// draw the sprite quad:
draw_sprite_pos(sprite, subimg,
    global._draw_sprite_ext_skew_x[0],
    global._draw_sprite_ext_skew_y[0],
    global._draw_sprite_ext_skew_x[1],
    global._draw_sprite_ext_skew_y[1],
    global._draw_sprite_ext_skew_x[3],
    global._draw_sprite_ext_skew_y[3],
    global._draw_sprite_ext_skew_x[2],
    global._draw_sprite_ext_skew_y[2],
    alpha
);

Update: If your sprites don't take up the whole image and get cropped by GameMaker, you can either disable cropping in texture page settings or replace the "compute values that will be reused" block by the following:

// compute values that will be reused:
var rcos = dcos(rot);
var rsin = -dsin(rot);
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;

Related posts:

12 thoughts on “GameMaker: draw_sprite_ext_skew

  1. hey! i tried appying your function into my game to make a direction pointer for the player that is slightly “squished” on the Y axis due to the game’s perspective, but when i use it it it simply does not draw the sprite at all.
    here’s a copy of the line of code that should do the drawing:
    if draw_direction and !global.is_talking draw_sprite_ext_skew(spr_direction, 0, x,y,1, 1, dir, c_white, 1, 1, 1, .7);
    this line is in the player’s draw event. i have no clue why it does not work.

  2. Very versatile function! I love it!

    Does this function also animate the sprite? I’m drawing multiple things with one object. And I want to be able to display icons that will animate themselves. I was wondering if this also handles that?

  3. This is fantastic! I’m tinkering with it to cast shadows in a 2D dungeon crawler, and to do that I need to skew at specific angles (angle from light source to object). Can you suggest an efficient way to do this? I can only get it to look right by skewing kw by cos(degtorad(angle)) when the angle is +/- 45. Not sure how to skew beyond that range.

    • Apparently I never responded to this!

      If you mean to achieve an effect similar to shadows in Dungeon of the Endless, you would want to use lengthdir_x/y to move the first two (TL/TR) points away from the light source based on distance to it.

      var lx = mouse_x, ly = mouse_y;
      draw_circle(lx, ly, 10, 1);
      var nx1 = 100, ny1 = 150;
      var nx2 = 140, ny2 = 150;
      var mx = (nx1 + nx2) / 2;
      var my = (ny1 + ny2) / 2;
      
      var l = sprite_get_height(spr) * min(1, 100 / point_distance(lx, ly, mx, my));
      var d = point_direction(lx, ly, mx, my);
      var fx1 = nx1 + lengthdir_x(l, d);
      var fy1 = ny1 + lengthdir_y(l, d);
      var fx2 = nx2 + lengthdir_x(l, d);
      var fy2 = ny2 + lengthdir_y(l, d);
      
      draw_sprite_pos(spr, 0, fx1, fy1, fx2, fy2, nx1, ny1, nx2, ny2, 1);

      live demo

      • For anyone wondering why this may have odd results, I believe the “n#1” and “n#2” variables are switched for the draw_sprite_pos call in YAL’s example (under “if(0)”).

  4. I think this technique would work well for a type of “dynamic shadow” that my character casts. As the sun moves across the sky, the shadow (a separate object and sprite) could skew appropriately. The only thing is, I think I would also need to dynamically move the origin of the shadow sprite at the same time as it skews, so that it will stay attached to the character’s feet. I’ll play around with it and see if I can figure it out. Thank you for this post!

    • The origin is the part of the calculation (see “y1”), so you can make a version of the script that accepts custom origin arguments fairly easily.

  5. did they fix the distortion in draw_sprite_pos at the center diagonal line between the 2 facets , because the thing used 2 facets instead 4 with a center point…

    • That’s non-affine transformation (see link), and adding two more triangles won’t really fix that – for correct perspective you need either a shader (someone wrote one) or a 3d matrix, both of which are much more computation-intensive to switch to/from.

Leave a Reply to Chimeric Cancel reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.