Game Maker Studio Draw Circle Alpha
Mouseover to activate, click to reposition the clipping star.
This is a tutorial near pretty much everything related to clipping drawn graphics in GameMaker.
That is, having fatigued graphics but brandish within 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 and so this mail is nicely stuffed with interactive demos and snippets.
Rectangular prune (via surfaces)
Mouseover to view, click resize clip region
This one is the most common instance and the one that is the easiest to implement:
- Create a clip-area-sized surface.
- Draw the graphics into it, offsetting them (either directly or via d3d_transform) by clip expanse's top-left corner=' coordinates.
- Depict the clip expanse surface at it'south elevation-left corner' coordinates.
The code is straightforward likewise,
// create a surface if it doesn't exist: if (! surface_exists(clip_surface)) { clip_surface = surface_create(clip_width, clip_height); } // clear and starting time 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); // terminate 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 (tin can be set to -one in Create), and clip_x\y\width\height define the prune' region.
Aforementioned approach is used in "scrollable content" example that I published a few years ago.
Rectangular prune (via shaders)
If y'all would prefer to use a shader over surface, you tin.
For this you would add a shader with the post-obit vertex code:
attribute vec3 in_Position; aspect 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 1 improver - information technology stores v_vPosition so that the fragment shader knows the coordinates of things being drawn. Fragment lawmaking, on other mitt, would be as post-obit:
varying vec2 v_vTexcoord; varying vec4 v_vColour; varying vec3 v_vPosition; // uniform vec4 u_bounds; // void principal() { vec4 col = v_vColour * texture2D(gm_BaseTexture, v_vTexcoord); col.a *= bladder(v_vPosition.ten >= u_bounds[0] && v_vPosition.y >= u_bounds[ane] && v_vPosition.10 < u_bounds[two] && v_vPosition.y < u_bounds[3]); gl_FragColor = col; }
This pulls RGBA from the texture like a default laissez passer-through shader would, just sets the alpha channel to 0 if the point is exterior the rectangle (u_bounds).
Then, to utilise this, yous would prepare the shader and laissez passer in the rectangle' data:
// debug: draw_circle(mouse_x, mouse_y, 40, true); draw_rectangle(clip_x1, clip_y1, clip_x2, clip_y2, true); // gear up upwards 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, twoscore, false); // finish: shader_reset();
Where clip_ are the rectangle' bounds (much like with draw_rectangle).
A downloadable case of this is included at the stop of the blog post for your convenience.
Rectangular clip for sprites
Mouseover to preview, click to move rectangle' points.
Sometimes, you might want to draw a clipped graphic (due east.thousand. a cursor or an off-screen indicator) without creating a surface or adding a shader but for ane thing.
For example, in Don't Crawl we accept 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 fleck of fancy math to clip the drawn polygon, for a standard case, things are simple enough - calculate the resulting bounds of a graphic, bank check if it falls inside the clip area at all, draw a slice of information technology defined by intersecting rectangle if then:
/// draw_sprite_clip(sprite, subimg, ten, 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(due south); 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 i: draw_sprite(southward, si, _x, _y); return truthful; case ii: 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 faux;
Or, if you lot 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 due south = argument0; var sw = sprite_get_width(southward); 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(south) * my; var bx2 = bx1 + sprite_get_width(s) * mx; var by2 = by1 + sprite_get_height(south) * 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 truthful; example 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 truthful; } return false;
Use is simple plenty:
draw_sprite_ext(q, 0, mx, my, 1, 1, 0, - ane, 0.i); // 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); // prune border (debug)
Arbitrary clip area (via surfaces)
Mouseover to preview, click to move the clip-circle.
This is where things become interesting:
- Create a surface for the prune-mask.
- Fill up the surface with an opaque black colour.
- Cut out (via draw_set_blend_mode(bm_subtract)) hole(s) for seeing things through.
- Create some other surface for clip area (same size).
- Describe the graphics into (and relative to) clip surface area surface.
- Cut out the mask surface out of clip area surface (again, via bm_subtract).
- Draw the clip surface area surface.
Therefore, mask-surface acts like a stencil for preventing prune area surface' contents from showing through where they shouldn't be.
The code is but 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 prune-surface, if needed clip_surface = surface_create(256, 256); } // kickoff drawing: surface_set_target(clip_surface); draw_clear_alpha(c_black, 0); // describe things relative to clip-surface: draw_circle(mouse_x - clipx, mouse_y - clipy, xl, faux); // 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); // cease and draw the clip-surface itself: surface_reset_target(); draw_surface(clip_surface, clipx, clipy);
Where clipx, clipy are the clip surface' tiptop-left corner, and clip_surface\mask_surface are the surfaces for clip expanse and mask accordingly (to be ready to -1 in Create-consequence). Surface and mask' sizes are constant (256x256) in this case.
This allows for noticeably more advanced effects - for case, in the demo at the commencement of the mail, this approach is used with a constantly updating mask surface to fill the overlapping surface area between 2 rotating shapes.
Capricious clip surface area (via shaders)
As with other things, information technology is too possible to implement arbitrary clip masks via a shader.
Then you would make a new GLSL ES shader, name it something like shd_clip_mask, and ready it's vertex shader code to be as following:
attribute vec3 in_Position; aspect vec4 in_Colour; attribute vec2 in_TextureCoord; // varying vec2 v_vTexcoord; varying vec4 v_vColour; varying vec3 v_vPosition; // void chief() { v_vPosition = in_Position; gl_Position = gm_Matrices[MATRIX_WORLD_VIEW_PROJECTION] * vec4(in_Position.x, in_Position.y, in_Position.z, i.0); v_vColour = in_Colour; v_vTexcoord = in_TextureCoord; }
The idea is the same every bit in clip rectangle shader - v_vPosition is used to determine the cartoon position inside the fragment shader, but here it is used to decide the signal 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 y'all would set up the shader, give information technology the surface (or other texture) to sample mask pixels from, give information technology 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, fake); surface_reset_target(); } // debug drawing: draw_set_color(make_color_rgb(94, 101, 124)); draw_circle(mouse_x, mouse_y, forty, true); draw_circle(clipx + 128, clipy + 128, seventy, true); // prepare 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 two.10 at present that it is not tied to aboriginal DirectX9), feasible workarounds exist for basically any imaginable use case.
A sample projection containing shaders (and code for some of the demos shown here) can be downloaded from crawling.io.
Take fun!
Source: https://yal.cc/gamemaker-draw-clip/comment-page-1/