Brick Breaker

by MusicBar, progress bar

A glowing ball smashes a wall of glossy bricks up to the playhead, the bricks shimmer with the music, and every beat bursts a brick into flying chips.

8 downloads Download file, 7.1 KB
How it installs, its SHA-256 and its code

Click Apply: MusicBar downloads it, checks its SHA-256 and puts it on the taskbar, or just puts it on if it already has it. Needs MusicBar 1.8 or newer.

SHA-256 7a619c4e8eab7bbd58d94c61cbf13b89f60c4ccbfa8e3245290c7a971e9bc1fd

//! name: Brick Breaker
//! author: MusicBar
//! kind: progress
//! description: A glowing ball smashes a wall of glossy bricks up to the playhead, the bricks shimmer with the music, and every beat bursts a brick into flying chips.

float hash1(float n) { return frac(sin(n * 78.233 + 12.9898) * 43758.5453); }

float sdRound(float2 p, float2 b, float r) { float2 q = abs(p) - b + r; return length(max(q, 0.0)) + min(max(q.x, q.y), 0.0) - r; }

float sdSeg(float2 p, float2 a, float2 b)
{
    float2 pa = p - a, ba = b - a;
    return length(pa - ba * saturate(dot(pa, ba) / dot(ba, ba)));
}

// A 0..1..0 triangle wave, so the ball moves in straight lines and bounces.
float tri(float t) { return abs(frac(t) - 0.5) * 2.0; }

// Where the ball is at time t (in bar heights, relative to the wall face): it ricochets between the wall and the paddle.
float2 ballAt(float t, float u)
{
    return float2(lerp(-0.1, -1.72, tri(t * 0.85)), lerp(0.14, 0.86, tri(t * 1.31 + 0.3))) * u;
}

float3 rowColor(float r) { return r < 0.5 ? colorA.rgb : (r < 1.5 ? colorB.rgb : colorC.rgb); }

void layer(inout float3 acc, inout float cover, float3 rgb, float a)
{
    a = saturate(a);
    acc = rgb * a + acc * (1.0 - a);
    cover = a + cover * (1.0 - a);
}

float4 main(float2 uv : TEXCOORD) : COLOR
{
    float u = size.y;
    float2 px = float2(uv.x * size.x, rise(uv) * size.y);
    float head = progress * size.x;
    float aa = 1.0;
    float3 acc = float3(0, 0, 0);
    float cover = 0;

    // Bricks: 3 rows in a running bond, each brick about 2.3 times as wide as it is tall.
    float bh = 0.27 * u, gap = 0.05 * u, bw = 0.62 * u, pitch = bw + gap;
    float r = clamp(floor((px.y - 0.045 * u) / (bh + gap)), 0.0, 2.0);
    float cy = 0.045 * u + r * (bh + gap) + 0.5 * bh;
    float off = frac(r * 0.5) * 2.0 * 0.5 * pitch;
    float n = floor((px.x - off) / pitch);
    float left = n * pitch + off;
    float2 lp = px - float2(left + 0.5 * bw, cy);
    float dBrick = sdRound(lp, float2(0.5 * bw, 0.5 * bh), 0.05 * u);

    // The brick the ball is working on gets chipped away from its left side and cracks; older ones are gone.
    float f = saturate((head - left) / bw);
    float jag = 0.05 * u * sin(px.y / u * 37.0 + n * 3.0) + 0.025 * u * sin(px.y / u * 91.0 + n);
    float eaten = step(px.x, left + f * bw * 0.75 + jag * step(0.01, f));
    float alive = step(head, left + bw) * (1.0 - eaten);
    float2 cp = px - float2(left + f * bw * 0.6, cy);
    float crack = min(min(sdSeg(cp, float2(0, 0), float2(0.22, 0.09) * u), sdSeg(cp, float2(0.22, 0.09) * u, float2(0.34, 0.14) * u)),
                      min(sdSeg(cp, float2(0, 0), float2(0.2, -0.1) * u), sdSeg(cp, float2(0, 0), float2(0.1, 0.13) * u)));
    float crackA = smoothstep(1.4, 0.4, crack) * step(0.01, f) * step(cp.x, f * bw * 1.4);

    // Gloss and bevel: a lit top-left rim, a dark bottom-right rim, a soft highlight band, and a shimmer with the spectrum.
    float spec = spectrum(frac(n * 0.137 + r * 0.29) * 0.8);
    float2 q = abs(lp) - float2(0.5 * bw, 0.5 * bh) + 0.05 * u;
    float2 nrm = (q.x > 0.0 || q.y > 0.0) ? normalize(max(q, 0.0)) * sign(lp) : (q.x > q.y ? float2(sign(lp.x), 0) : float2(0, sign(lp.y)));
    float rim = smoothstep(-0.06 * u, -0.01 * u, dBrick);
    float near = exp(-max(left - head, 0.0) / (3.5 * u));                                  // bricks far from the action stay quieter
    float3 base = rowColor(r) * (0.5 + 0.6 * spec) * lerp(0.7, 1.0, near);
    float3 col = base * (1.0 + rim * 0.75 * dot(nrm, float2(-0.55, 0.83)));
    col += float3(1, 1, 1) * 0.3 * smoothstep(0.0, 0.08 * u, lp.y) * (1.0 - rim) * (0.5 + 0.7 * spec);
    col += float3(1, 1, 1) * 0.45 * smoothstep(1.2, 0.0, abs(lp.y - 0.075 * u)) * step(abs(lp.x), 0.36 * u);   // a crisp gloss line
    float sweep = exp(-abs(frac((px.x - px.y * 0.6) / (7.0 * u) - time * 0.25) - 0.5) * 60.0);
    col += sweep * 0.35 * (1.0 - rim);
    col = lerp(col, rowColor(r) * 0.18, crackA);
    col += float3(1, 1, 1) * beat * 0.6 * step(left, head + 0.5 * bw) * step(head, left + bw);    // the brick at the face flashes on a beat
    float wallA = alive * lerp(0.42 + 0.3 * spec, 0.95, near);
    layer(acc, cover, rowColor(r) * 0.15, wallA * smoothstep(aa, -aa, dBrick - 1.2));
    layer(acc, cover, col, wallA * smoothstep(aa, -aa, dBrick));

    // Debris rains down behind the face: small chips falling and tumbling, fading as they fall.
    float dc = 0.28 * u;
    float di = floor(px.x / dc);
    float h1 = hash1(di), h2 = hash1(di + 31.0), h3 = hash1(di + 57.0);
    float life = frac(time * (0.6 + 0.5 * h2) + h1);
    float2 dp = float2((di + 0.5) * dc - life * 0.3 * u, (0.3 + 0.6 * h3) * u - life * life * 0.95 * u);
    float rotA = time * 5.0 * (h2 - 0.5) + h1 * 6.0;
    float2 dl = px - dp;
    dl = float2(cos(rotA) * dl.x - sin(rotA) * dl.y, sin(rotA) * dl.x + cos(rotA) * dl.y);
    float chip = smoothstep(aa, -aa, sdRound(dl, float2(0.05, 0.035) * u, 0.01 * u));
    float behind = step(px.x, head - 0.1 * u) * exp(-(head - px.x) / (2.4 * u));
    layer(acc, cover, rowColor(floor(h3 * 2.99)) * (0.8 + 0.3 * (1.0 - life)), chip * behind * (1.0 - life * life) * step(0.15, h2));

    // On a beat a brick bursts: chips fly back from the face and fall.
    for (int i = 0; i < 6; i++)
    {
        float fi = i;
        float t = (1.0 - beat) * 0.5;
        float2 v = float2(-(0.8 + 2.2 * hash1(fi + 1.0)), 0.4 + 2.2 * hash1(fi + 2.0)) * u;
        float2 p0 = float2(head, (0.15 + 0.7 * hash1(fi + 3.0)) * u);
        float2 cpos = p0 + v * t - float2(0.0, 4.5 * u) * t * t;
        float bs = 0.05 * u * (1.0 - t);
        layer(acc, cover, rowColor(floor(hash1(fi + 4.0) * 2.99)), step(0.04, beat) * smoothstep(aa, -aa, sdRound(px - cpos, float2(bs, bs * 0.7), 0.01 * u)));
    }

    // The ball with a motion trail, and the paddle that sends it back.
    float2 face = float2(head, 0.0);
    float2 ball = face + ballAt(time, u);
    float2 prev = ball;
    float trail = 0;
    for (int k = 1; k < 7; k++)
    {
        float tk = k;
        float2 bp = face + ballAt(time - tk * 0.02, u);
        float w = 0.07 * u * (1.0 - tk / 7.0);
        trail = max(trail, smoothstep(aa, -aa, sdSeg(px, prev, bp) - w) * (1.0 - tk / 7.0));
        prev = bp;
    }
    layer(acc, cover, lerp(colorC.rgb, float3(1, 1, 1), 0.25), trail * 0.6);
    float bd = length(px - ball);
    float br = 0.075 * u * (1.0 + 0.3 * beat);
    layer(acc, cover, colorC.rgb, exp(-max(bd - br, 0.0) / (0.09 * u)) * (0.55 + 0.35 * beat));
    float3 ballCol = lerp(float3(1, 1, 1), colorC.rgb, smoothstep(0.0, br, length(px - ball - float2(-0.3, 0.3) * br)) * 0.6);
    layer(acc, cover, ballCol, smoothstep(aa, -aa, bd - br));

    float py = clamp(ballAt(time - 0.08, u).y, 0.2 * u, 0.8 * u);
    float2 pp = px - float2(head - 1.84 * u, py);
    float dPad = sdRound(pp, float2(0.045, 0.2) * u, 0.045 * u);
    float pt = saturate(pp.y / (0.4 * u) + 0.5);
    float3 padCol = lerp(float3(0.55, 0.58, 0.68), float3(0.95, 0.96, 1.0), smoothstep(0.03 * u, -0.03 * u, pp.x));
    padCol = lerp(padCol, colorA.rgb, step(0.15 * u, abs(pp.y)));
    layer(acc, cover, float3(0.06, 0.06, 0.1), smoothstep(aa, -aa, dPad - 1.2));
    layer(acc, cover, padCol, smoothstep(aa, -aa, dPad));

    return paint(acc / max(cover, 0.001), cover);
}