SOLVED Drawing a partial sprite using a partial circle.

Heya!

I'm currently making a game, which will involve effects.
When a player gains an effect, it will show up on the side like this. The game is going to be turn-based, so the effects' duration will also be decreased after each turn.

Visual representation of effect duration decreasing.

The problem is, I'm having difficulty figuring a better implementation of this.
How I'm currently doing it is a bit tedious and inefficient.

The effect timer is a sprite with image_speed = 0
1634935727787.png

Each frame is enum'd, and set whenever the timer is updated.
GML:
switch (effect_timer/effect_timer_max) {
    case 1:
        effect_timer_id.image_index = states.full;
        break;
    case 1/6:
        effect_timer_id.image_index = states.onesixth;
        break;
    case 0.25:
        effect_timer_id.image_index = states.onefourth;
        break;
    case 1/3:
        effect_timer_id.image_index = states.onethird;
        break;
    case 0.5:
        effect_timer_id.image_index = states.half;
        break;
    case 2/3:
        effect_timer_id.image_index = states.twothirds;
        break;
    case 0.75:
        effect_timer_id.image_index = states.threefourths;
        break;
    case 5/6:
        effect_timer_id.image_index = states.fivesixths;
        break;
}

As mentioned by the title, I already have an idea to make this more efficient, but don't know how to implement it.
Ideally, I would get the ratio from effect_timer / effect_timer_max, then plug that into a function, that would draw a partial sprite, from the partial circle.

If the ratio equals 1, it's a full circle.
If it equals 0.5, it's half a circle.
1/3 - a third of a circle

effect_timer1.pngeffect_timer2.png

But I cannot figure out how I would have to write this in code.
I would appreciate any feedback! <3
 

Nidoking

Member
Seems like the kind of thing you could do with a shader. Pass in the center of the circle, determine the angle from that to the point in the sprite, and set the alpha accordingly.
 
SOLVED!
This ended up being more nerve-wracking than I personally would've liked. Never worked with Shaders, never want to work again lmfao.

SOLUTION:
I ended up just making a shader, that through some math that I myself forgot how it works, through some vector magic, calculates the angle that each pixel is relative to the reference vector, and if it is smaller than the provided angle, it doesn't get drawn.
TL;DR here is the shader code.
C#:
//
// Simple passthrough fragment shader
//
varying vec2 v_vTexcoord;
varying vec4 v_vColour;
uniform float angle;

#define PI 3.1415926538

float GetAnglesFromVectors(vec2 ref, vec2 current) {
    vec2 origin;
    float c;
    float yaw;
   
    origin = current - ref;
    c = sqrt( pow(origin.x, 2.0) + pow(origin.y, 2.0) );
   
   
    if (ref.y <= current.y) {
        yaw = atan( (origin.x / origin.y) ) * ( 180.0 / PI );
    }
    else if (ref.y > current.y) {
        yaw = atan( (origin.x / origin.y) ) * ( 180.0 / PI ) + 180.0;
    }
   
    return yaw;
}


void main() {
    vec2 texCenter;
    vec2 gridCoords;
    float alpha;
    float degree;
    float anglet;
   
    anglet = 360.0 - angle;
   
    texCenter = vec2(-0.4, -0.4);
    alpha = texture2D(gm_BaseTexture, v_vTexcoord).a;
   
    gridCoords.x = 2.0*(v_vTexcoord.x-0.5);
    gridCoords.y = 2.0*(v_vTexcoord.y-0.5);
   
    degree = GetAnglesFromVectors(gridCoords, texCenter);
   
   
    if (degree < 0.0) {
        degree += 360.0;
    }
   
    if ((degree < anglet)) {
        alpha = 0.0;
    }
       
    gl_FragColor = v_vColour * texture2D( gm_BaseTexture, v_vTexcoord );
    gl_FragColor.a = alpha;
}

And here is the result.
effecttimeout.png
 
Last edited:

YellowAfterlife

ᴏɴʟɪɴᴇ ᴍᴜʟᴛɪᴘʟᴀʏᴇʀ
Forum Staff
Moderator
I have solved this in past by building a few-polygon primitive
GameMaker: Circular cooldown rectangle (yal.cc)
Something like this for a textured version:
GML:
/// draw_rectangle_cd(x1, y1, x2, y2, value, texture)
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_texture(pr_trianglefan, argument5)
draw_vertex_texture(xm, ym, 0.5, 0.5); draw_vertex_texture(xm, y1, 0.5, 0)
// draw corners:
if (v >= 0.125) draw_vertex_texture(x2, y1, 1, 0)
if (v >= 0.375) draw_vertex_texture(x2, y2, 1, 1)
if (v >= 0.625) draw_vertex_texture(x1, y2, 0, 1)
if (v >= 0.875) draw_vertex_texture(x1, y1, 0, 0)
// 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_texture(xm + vx * (x2 - x1) / 2, ym + vy * (y2 - y1) / 2, 0.5 + vx * 0.5, 0.5 + vy * 0.5)
draw_primitive_end()
If you make your sprite contain N+1 frames (from empty to full, inclusive), your calculation would become image_index = round(clamp(fill, 0, 1) * N), which is pretty good too if you want a lot of these.
 
Top