Javascript required
Skip to content Skip to sidebar Skip to footer

Draw Line Width Gamemaker Studio


Mouseover to activate, click to reposition the clipping star.

This is a tutorial about pretty much everything related to clipping drawn graphics in GameMaker.

That is, having drawn graphics only display inside a certain ("clip") area, be that a rectangle (UI regions, minimaps, etc.), circle, or a completely arbitrary shape (pictured above).

Also I'm trying new things so this post is nicely stuffed with interactive demos and snippets.

Rectangular clip (via surfaces)

Drawing sprites while clipping outside of rectangle in GameMaker.
Mouseover to view, click resize clip region

This one is the most common case and the one that is the easiest to implement:

  1. Create a clip-area-sized surface.
  2. Draw the graphics into it, offsetting them (either directly or via d3d_transform) by clip area's top-left corner=' coordinates.
  3. Draw the clip area surface at it's top-left corner' coordinates.

The code is straightforward too,

            // create a surface if it doesn't exist:            if            (!            surface_exists(clip_surface)) {            clip_surface            =            surface_create(clip_width,            clip_height); }            // clear and start drawing to surface:            surface_set_target(clip_surface);            draw_clear_alpha(c_black,            0);            // draw things here, subtracting (clip_x, clip_y) from coordinates:            draw_circle(mouse_x            -            clip_x,            mouse_y            -            clip_y,            40,            false);            // finish and draw the surface itself:            surface_reset_target();            draw_surface(clip_surface,            clip_x,            clip_y);

Where clip_surface is the surface ID used for clipping (can be set to -1 in Create), and clip_x\y\width\height define the clip' region.

Same approach is used in "scrollable content" example that I published a few years ago.

Rectangular clip (via shaders)

If you would prefer to use a shader over surface, you can.

For this you would add a shader with the following vertex code:

          attribute          vec3          in_Position;          attribute          vec4          in_Colour;          attribute          vec2          in_TextureCoord;          //                    varying          vec2          v_vTexcoord;          varying          vec4          v_vColour;          varying          vec3          v_vPosition;          //                    void          main() {     v_vPosition = in_Position;     gl_Position =          gm_Matrices[MATRIX_WORLD_VIEW_PROJECTION]         *          vec4(in_Position.x, in_Position.y, in_Position.z,          1.0);     v_vColour = in_Colour;     v_vTexcoord = in_TextureCoord; }

This is identical to the default "pass-through" code, except for one addition - it stores v_vPosition so that the fragment shader knows the coordinates of things being drawn. Fragment code, on other hand, would be as following:

          varying          vec2          v_vTexcoord;          varying          vec4          v_vColour;          varying          vec3          v_vPosition;          //                    uniform          vec4          u_bounds;          //                    void          main() {          vec4          col = v_vColour *          texture2D(gm_BaseTexture, v_vTexcoord);     col.a          *=          float(v_vPosition.x          >= u_bounds[0] && v_vPosition.y          >= u_bounds[1]         && v_vPosition.x          < u_bounds[2] && v_vPosition.y          < u_bounds[3]);          gl_FragColor          = col; }

This pulls RGBA from the texture like a default pass-through shader would, but sets the alpha channel to 0 if the point is outside the rectangle (u_bounds).

Then, to use this, you would set the shader and pass in the rectangle' data:

            // debug:            draw_circle(mouse_x,            mouse_y,            40,            true);            draw_rectangle(clip_x1,            clip_y1,            clip_x2,            clip_y2,            true);            // set up shader:            shader_set(shd_clip_rect);            var            u_bounds            =            shader_get_uniform(shd_clip_rect,            "u_bounds");            shader_set_uniform_f(u_bounds,            clip_x1,            clip_y1,            clip_x2,            clip_y2);            // draw things:            draw_circle(mouse_x,            mouse_y,            40,            false);            // finish:            shader_reset();

Where clip_ are the rectangle' bounds (much like with draw_rectangle).

A downloadable example of this is included at the end of the blog post for your convenience.

Rectangular clip for sprites

Drawing sprites while clipping outside of rectangle in GameMaker.
Mouseover to preview, click to move rectangle' points.

Sometimes, you might want to draw a clipped graphic (e.g. a cursor or an off-screen indicator) without creating a surface or adding a shader just for one thing.

For example, in Don't Crawl we have multiple layers of graphics (terrain tiles - splatter - entities - unscaled screen-space effects - entity overlays - cursors - gui), which required to clip some things to views despite them being drawn over them in the GUI events.

While rotated graphics would require a bit of fancy math to clip the drawn polygon, for a standard case, things are simple enough - calculate the resulting bounds of a graphic, check if it falls inside the clip area at all, draw a piece of it defined by intersecting rectangle if so:

            /// draw_sprite_clip(sprite, subimg, x, y, clipx, clipy, clipw, cliph)            var            s            =            argument0;            var            sw            =            sprite_get_width(s);            var            sh            =            sprite_get_height(s);            var            sx            =            sprite_get_xoffset(s);            var            sy            =            sprite_get_yoffset(s);            var            si            =            argument1;            var            _x            =            argument2;            var            _y            =            argument3;            var            cx1            =            argument4;            var            cy1            =            argument5;            var            cx2            =            cx1            +            argument6;            var            cy2            =            cy1            +            argument7;            //            var            bx1            =            _x            -            sprite_get_xoffset(s);            var            by1            =            _y            -            sprite_get_yoffset(s);            var            bx2            =            bx1            +            sprite_get_width(s);            var            by2            =            by1            +            sprite_get_height(s);            //            switch            (rectangle_in_rectangle(bx1,            by1,            bx2,            by2,            cx1,            cy1,            cx2,            cy2)) {            case            1:            draw_sprite(s,            si,            _x,            _y);            return            true;            case            2:            var            lx1            =            max(0,            cx1            -            bx1);            var            ly1            =            max(0,            cy1            -            by1);            var            lx2            =            sw            +            min(0,            cx2            -            bx2);            var            ly2            =            sh            +            min(0,            cy2            -            by2);            draw_sprite_part(s,            si,            lx1,            ly1,            lx2            -            lx1,            ly2            -            ly1,            _x            +            lx1            -            sx,            _y            +            ly1            -            sy);            return            true; }            return            false;

Or, if you want scaling and/or blending, a slightly fancier version:

            /// draw_sprite_clip_ext(sprite, subimg, x, y, xscale, yscale, color, alpha, rx, ry, rw, rh)            var            s            =            argument0;            var            sw            =            sprite_get_width(s);            var            sh            =            sprite_get_height(s);            var            sx            =            sprite_get_xoffset(s);            var            sy            =            sprite_get_yoffset(s);            var            si            =            argument1;            var            _x            =            argument2;            var            _y            =            argument3;            var            mx            =            argument4;            var            my            =            argument5;            var            sc            =            argument6;            var            sa            =            argument7;            var            cx1            =            argument8;            var            cy1            =            argument9;            var            cx2            =            cx1            +            argument10;            var            cy2            =            cy1            +            argument11;            //            var            bx1            =            _x            -            sprite_get_xoffset(s)            *            mx;            var            by1            =            _y            -            sprite_get_yoffset(s)            *            my;            var            bx2            =            bx1            +            sprite_get_width(s)            *            mx;            var            by2            =            by1            +            sprite_get_height(s)            *            my;            //            switch            (rectangle_in_rectangle(bx1,            by1,            bx2,            by2,            cx1,            cy1,            cx2,            cy2)) {            case            1:            draw_sprite_ext(s,            si,            _x,            _y,            mx,            my,            0,            sc,            sa);            return            true;            case            2:            if            (mx            ==            0            ||            my            ==            0)            return            true;            var            lx1            =            max(0,            cx1            -            bx1)            /            mx;            var            ly1            =            max(0,            cy1            -            by1)            /            my;            var            lx2            =            sw            +            min(0,            cx2            -            bx2)            /            mx;            var            ly2            =            sh            +            min(0,            cy2            -            by2)            /            my;            draw_sprite_part_ext(s,            si,            lx1,            ly1,            lx2            -            lx1,            ly2            -            ly1,            _x            +            (lx1            -            sx)            *            mx,            _y            +            (ly1            -            sy)            *            my,            mx,            my,            sc,            sa);            return            true; }            return            false;          

Use is simple enough:

            draw_sprite_ext(q,            0,            mx,            my,            1,            1,            0,            -            1,            0.1);            // background sprite (debug)            draw_sprite_clip(q,            0,            mx,            my,            x1,            y1,            x2            -            x1,            y2            -            y1);            // clipped sprite            draw_rectangle(x1,            y1,            x2,            y2,            1);            // clip border (debug)          

Arbitrary clip area (via surfaces)


Mouseover to preview, click to move the clip-circle.

This is where things get interesting:

  1. Create a surface for the clip-mask.
  2. Fill the surface with an opaque black color.
  3. Cut out (via draw_set_blend_mode(bm_subtract)) hole(s) for seeing things through.
  4. Create another surface for clip area (same size).
  5. Draw the graphics into (and relative to) clip area surface.
  6. Cut out the mask surface out of clip area surface (again, via bm_subtract).
  7. Draw the clip area surface.

Therefore, mask-surface acts like a stencil for preventing clip area surface' contents from showing through where they shouldn't be.

The code is only slightly more complex:

            if            (!            surface_exists(mask_surface)) {            // create the mask-surface, if needed            mask_surface            =            surface_create(256,            256);            surface_set_target(mask_surface);            draw_clear(c_black);            draw_set_blend_mode(bm_subtract);            // cut out shapes out of the mask-surface:            draw_circle(128,            128,            70,            false);            //            draw_set_blend_mode(bm_normal);            surface_reset_target(); }            if            (!            surface_exists(clip_surface)) {            // create the clip-surface, if needed            clip_surface            =            surface_create(256,            256); }            // start drawing:            surface_set_target(clip_surface);            draw_clear_alpha(c_black,            0);            // draw things relative to clip-surface:            draw_circle(mouse_x            -            clipx,            mouse_y            -            clipy,            40,            false);            // cut out the mask-surface from it:            draw_set_blend_mode(bm_subtract);            draw_surface(mask_surface,            0,            0);            draw_set_blend_mode(bm_normal);            // finish and draw the clip-surface itself:            surface_reset_target();            draw_surface(clip_surface,            clipx,            clipy);

Where clipx, clipy are the clip surface' top-left corner, and clip_surface\mask_surface are the surfaces for clip area and mask accordingly (to be set to -1 in Create-event). Surface and mask' sizes are constant (256x256) in this case.

This allows for noticeably more advanced effects - for example, in the demo at the start of the post, this approach is used with a constantly updating mask surface to fill the overlapping area between two rotating shapes.

Arbitrary clip area (via shaders)

As with other things, it is also possible to implement arbitrary clip masks via a shader.

So you would make a new GLSL ES shader, name it something like shd_clip_mask, and set it's vertex shader code to be as following:

          attribute          vec3          in_Position;          attribute          vec4          in_Colour;          attribute          vec2          in_TextureCoord;          //                    varying          vec2          v_vTexcoord;          varying          vec4          v_vColour;          varying          vec3          v_vPosition;          //                    void          main() {     v_vPosition = in_Position;     gl_Position =          gm_Matrices[MATRIX_WORLD_VIEW_PROJECTION]         *          vec4(in_Position.x, in_Position.y, in_Position.z,          1.0);     v_vColour = in_Colour;     v_vTexcoord = in_TextureCoord; }

The idea is the same as in clip rectangle shader - v_vPosition is used to determine the drawing position inside the fragment shader, but here it is used to determine the point in mask-texture to sample pixels from.

          varying          vec2          v_vTexcoord;          varying          vec4          v_vColour;          varying          vec3          v_vPosition;          //                    uniform          vec4          u_rect;          uniform          sampler2D          u_mask;          //                    void          main() {          gl_FragColor          = v_vColour         *          texture2D(gm_BaseTexture, v_vTexcoord)         *          texture2D(u_mask, (v_vPosition.xy          - u_rect.xy) / u_rect.zw); }        

Then you would set the shader, give it the surface (or other texture) to sample mask pixels from, give it the clip region via uniforms, and any things drawn will be drawn with the said mask.

            // create the mask surface if needed:            if            (!            surface_exists(mask)) {            mask            =            surface_create(256,            256);            surface_set_target(mask);            draw_clear_alpha(c_white,            0);            draw_set_color(c_white);            draw_circle(128,            128,            70,            false);            surface_reset_target(); }            // debug drawing:            draw_set_color(make_color_rgb(94,            101,            124));            draw_circle(mouse_x,            mouse_y,            40,            true);            draw_circle(clipx            +            128,            clipy            +            128,            70,            true);            // set up the shader:            shader_set(shd_clip_mask);            var            u_mask            =            shader_get_sampler_index(shd_clip_mask,            "u_mask");            texture_set_stage(u_mask,            surface_get_texture(clip_mask));            var            u_rect            =            shader_get_uniform(shd_clip_mask,            "u_rect");            shader_set_uniform_f(u_rect,            clipx,            clipy,            256,            256);            // draw things:            draw_circle(mouse_x,            mouse_y,            40,            false);            // finish:            shader_reset();

In conclusion

While GameMaker does not come with built-in functions for clipping graphics (although perhaps this will change in 2.x now that it is not tied to ancient DirectX9), feasible workarounds exist for basically any imaginable use case.

A sample project containing shaders (and code for some of the demos shown here) can be downloaded from itch.io.

Have fun!

Draw Line Width Gamemaker Studio

Source: https://yal.cc/gamemaker-draw-clip/comment-page-1/