Gms2 Draw Sprite in Circle
Mouseover to activate, click to reposition the clipping star.
This is a tutorial near pretty much everything related to clipping fatigued graphics in GameMaker.
That is, having fatigued graphics only display inside a certain ("prune") surface area, exist that a rectangle (UI regions, minimaps, etc.), circle, or a completely arbitrary shape (pictured to a higher place).
Also I'k trying new things so this post is nicely stuffed with interactive demos and snippets.
Rectangular prune (via surfaces)
Mouseover to view, click resize clip region
This i is the most common case 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 prune area'due south elevation-left corner=' coordinates.
- Draw the prune area surface at it'south 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 cartoon 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); // stop 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 ascertain the clip' region.
Same arroyo is used in "scrollable content" case that I published a few years agone.
Rectangular clip (via shaders)
If you would prefer to use a shader over surface, you lot tin.
For this you would add a shader with the following vertex code:
aspect 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.ten, 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 - information technology 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 primary() { 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[ii] && 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 utilize 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 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, 40, false); // finish: shader_reset();
Where clip_ are the rectangle' bounds (much like with draw_rectangle).
A downloadable case of this is included at the end of the blog post for your convenience.
Rectangular prune for sprites
Mouseover to preview, click to motility 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 simply for one thing.
For case, in Don't Crawl we take multiple layers of graphics (terrain tiles - splatter - entities - unscaled screen-infinite effects - entity overlays - cursors - gui), which required to prune some things to views despite them beingness drawn over them in the GUI events.
While rotated graphics would require a scrap of fancy math to clip the fatigued polygon, for a standard instance, things are simple enough - calculate the resulting bounds of a graphic, check if it falls inside the prune area at all, draw a piece of information technology defined by intersecting rectangle if and so:
/// draw_sprite_clip(sprite, subimg, x, y, clipx, clipy, clipw, cliph) var s = argument0; var sw = sprite_get_width(southward); var sh = sprite_get_height(s); var sx = sprite_get_xoffset(southward); 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(southward); 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 one: 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); render true; } return simulated;
Or, if you lot desire 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 southward = argument0; var sw = sprite_get_width(south); var sh = sprite_get_height(s); var sx = sprite_get_xoffset(southward); var sy = sprite_get_yoffset(due south); 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(southward) * mx; var by1 = _y - sprite_get_yoffset(s) * my; var bx2 = bx1 + sprite_get_width(south) * 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; instance two: if (mx == 0 || my == 0) return truthful; 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(due south, si, lx1, ly1, lx2 - lx1, ly2 - ly1, _x + (lx1 - sx) * mx, _y + (ly1 - sy) * my, mx, my, sc, sa); return true; } return simulated;
Employ is unproblematic enough:
draw_sprite_ext(q, 0, mx, my, 1, one, 0, - i, 0.ane); // 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 prune surface area (via surfaces)
Mouseover to preview, click to move the clip-circumvolve.
This is where things get interesting:
- Create a surface for the prune-mask.
- Make full the surface with an opaque black color.
- Cut out (via draw_set_blend_mode(bm_subtract)) pigsty(s) for seeing things through.
- Create some other surface for prune area (same size).
- Draw the graphics into (and relative to) clip area surface.
- Cut out the mask surface out of clip expanse surface (once again, via bm_subtract).
- Describe the clip 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, imitation); // 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); } // starting time drawing: surface_set_target(clip_surface); draw_clear_alpha(c_black, 0); // depict things relative to clip-surface: draw_circle(mouse_x - clipx, mouse_y - clipy, 40, imitation); // cut out the mask-surface from information technology: 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' top-left corner, and clip_surface\mask_surface are the surfaces for prune area and mask accordingly (to exist set 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 kickoff of the post, this approach is used with a constantly updating mask surface to fill up the overlapping area betwixt two rotating shapes.
Arbitrary clip surface area (via shaders)
Every bit with other things, information technology is also possible to implement arbitrary clip masks via a shader.
So y'all would brand a new GLSL ES shader, proper name it something like shd_clip_mask, and gear up it'south 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 main() { v_vPosition = in_Position; gl_Position = gm_Matrices[MATRIX_WORLD_VIEW_PROJECTION] * vec4(in_Position.10, in_Position.y, in_Position.z, 1.0); v_vColour = in_Colour; v_vTexcoord = in_TextureCoord; }
The idea is the same equally in clip rectangle shader - v_vPosition is used to determine the drawing position inside the fragment shader, but hither 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; // compatible vec4 u_rect; compatible 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); }
So you would ready 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 cartoon: draw_set_color(make_color_rgb(94, 101, 124)); draw_circle(mouse_x, mouse_y, forty, 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, fake); // stop: shader_reset();
In decision
While GameMaker does not come up with congenital-in functions for clipping graphics (although possibly this will alter in 2.x at present that information technology is not tied to ancient DirectX9), viable workarounds exist for basically whatever imaginable use instance.
A sample projection containing shaders (and code for some of the demos shown hither) can be downloaded from crawling.io.
Accept fun!
Source: https://yal.cc/gamemaker-draw-clip/comment-page-1/