GameMaker: Circular cooldown rectangle

GameMaker: Cooldown rectangle I do not know the exact name of this visual effect (angular/clockwise fade-in/fade-out?), but it is one of the most common things that you can see in interface of RPG and RTS games, displaying progress of construction/upgrade/ability cooldown. So, to the point...
You can read how this is actually being implemented, or just scroll down to the bottom of post and grab the code and/or GMK file.
Implementation of such is actually pretty simple,

1. Angle

Circular fade/cooldown for rectangle. Step 1. Angle

First, but still important thing to do, is to decide on a way that your code will calculate angle depending on progress. In this example, angle is being changed in a clock-like faschion (clockwise, starting and ending at top point), thus making formulae angle = PI * 2 * progress - PI / 2, where progress is a [0 ... 1] value, and angle is, well, output angle in radians, going clockwise from right-most point of a circle.
On image, cross in center indicates the zero (0, 0) point, while gray square is ((-1, -1) ... (+1, +1)) area, which final image must reside in. Nothing complicated.

2. Make a vector

Circular fade/cooldown for rectangle. Step 2. Making a vector

Second thing to do is to make a one-unit-long vector out of previously obtained angle. Here, "a vector" means a pair of values defining an offset. For this you will need sine/cosine functions or equivalent (even a lookup table would work, technically). In most cases this will remain as simple as x = cos(angle); y = sin(angle);. Image illustrates the resulting vector.

3. Normalize the vector

Circular fade/cooldown for rectangle. Step 3. Normalizing a vector

As you may or may not know, vector normalization is basically about modifying a vector to match given length while preserving direction. In this case, this is needed to make vector touch square boundary regardless of it's direction. "Touching" in this case means that either X or Y of vector must be -1 or +1. Figuring out, which of two is to be scaled, is also trivial - it's maximum of absolute values of two (l = max(abs(x), abs(y))). Afterwards, that is to be set to -1 or +1 accordingly, depending on it's original sign. The simplest (and valid) way of doing this is just to divide both of vector components by earlier found l (x = x / l; y = y / l;). After this operation, vector would look as illustrated on image.

4. Draw

Circular fade/cooldown for rectangle. Step 4. Drawing

Given the normalized vector, the final task remains - it has to be drawn as actual filled shape. Fortunately, GameMaker supports a "triangle fan" primitive type, which allows to define points in fan-like order (first point is center, every next two form a triangle), making it simpler. Other programming languages and frameworks tend to support a generic "path" shape, which would be used in just about the same way. The only minimal challenge here is to draw corners of rectangle. That is normally done by adding conditional checks against pre-calculated constants. In this case, corners happen at 1/8, 3/8, 5/8 and 7/8 of overall "progress", thus leading to values 0.125, 0.375, 0.625, 0.875. As illustrated on image, shape starts at center point, goes through the points that are "behind" the current progress, goes through previously calculated vector point, and ends back at the center.

That's all the logic behind this. If something is to be cleared up, comments are welcome, since I am considerably sleepy as of typing this.

You can download example here, or use the code below. Note that example has an advantage in form of second version of script, which is compatible with GameMaker: Lite.

/// draw_rectangle_cd(x1, y1, x2, y2, value)
var v, x1, y1, x2, y2, xm, ym, vd, vx, vy, vl;
v = argument4
if (v <= 0) return 0 // nothing to be drawn
x1 = argument0; y1 = argument1; // top-left corner
x2 = argument2; y2 = argument3; // bottom-right corner
if (v >= 1) return draw_rectangle(x1, y1, x2, y2, false) // entirely filled
xm = (x1 + x2) / 2; ym = (y1 + y2) / 2; // middle point
draw_primitive_begin(pr_trianglefan)
draw_vertex(xm, ym); draw_vertex(xm, y1)
// draw corners:
if (v >= 0.125) draw_vertex(x2, y1)
if (v >= 0.375) draw_vertex(x2, y2)
if (v >= 0.625) draw_vertex(x1, y2)
if (v >= 0.875) draw_vertex(x1, y1)
// calculate angle & vector from value:
vd = pi * (v * 2 - 0.5)
vx = cos(vd)
vy = sin(vd)
// normalize the vector, so it hits -1+1 at either side:
vl = max(abs(vx), abs(vy))
if (vl < 1) {
    vx /= vl
    vy /= vl
}
draw_vertex(xm + vx * (x2 - x1) / 2, ym + vy * (y2 - y1) / 2)
draw_primitive_end()

Related posts:

10 thoughts on “GameMaker: Circular cooldown rectangle

  1. Thanks for sharing this code. I’ve used it on a personal prototyping project of mine and wish to thank you for allowing me to.

    regards!

  2. Hey this is extremely neat. I noticed it is counter-clock-wise though. Is there an easy way to make this clock-wise?

    • Hey, sorry for the delayed response. The easiest way would be simply to swap x1 and x2 arguments when calling the script, causing it to draw a mirrored (and therefore CW) shape.

    • You could do a path via display.Graphics. A shader would probably work too, but I think a path is a simpler approach (and ideally should convert into the few needed triangles automatically).

Leave a Reply

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