Rhythm Lane

của MusicBar, thanh tiến trình

Nốt nhạc trượt vào vòng ở chỗ đang phát rồi nổ lấp lánh y như game nhịp điệu, nhạc càng to thì nốt càng dày.

21 lượt tải Tải file, 7.0 KB
Cách cài, mã SHA-256 và mã nguồn

Bấm Áp dụng: MusicBar tự tải về, kiểm tra mã SHA-256 rồi mặc lên taskbar; có rồi thì mặc luôn. Cần MusicBar 1.8 trở lên.

SHA-256 9bd0eef3acd5ab83bc208fe3181c83b3781ba9fb966fd3a4cbf347ab08619cc4

//! name: Rhythm Lane
//! author: MusicBar
//! kind: progress
//! description: A rhythm-game highway runs along the bar: note gems slide into the hit ring at the playhead and burst into a perfect-hit sparkle, more of them when the music gets loud.

// A steady random number.
float hash1(float n) { return frac(sin(n * 91.345) * 43758.5453); }

// A rhombus (a cut gem seen from the front), b its half width and half height. Negative inside.
float gem(float2 p, float2 b)
{
    p = abs(p);
    return (p.x * b.y + p.y * b.x - b.x * b.y) / length(b);
}

// The note a lane holds in cell c: where it sits in note space, and how likely it is to be there.
float2 noteIn(float c, float seed, float cw)
{
    return float2(c * cw + (0.22 + 0.56 * hash1(c * 7.7 + seed + 1.3)) * cw, hash1(c * 3.1 + seed));
}

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
{
    float2 px = float2(uv.x * size.x, rise(uv) * size.y);   // pixels: along the bar, up from the screen edge
    float H = size.y;
    float head = progress * size.x;
    float v = 3.0 * H;                                      // how fast the notes scroll, pixels per second
    float cw = 1.1 * H;                                     // each lane holds at most one note per cell
    float density = 0.22 + 0.62 * level;                    // louder music, more notes

    // Three lanes, bottom to top in the accent, second and main colors.
    float gapL = 0.28 * H;
    float li = clamp(floor((px.y - 0.5 * H) / gapL + 0.5), -1.0, 1.0);
    float laneY = 0.5 * H + li * gapL;
    float3 lc = li < -0.5 ? colorC.rgb : (li < 0.5 ? colorB.rgb : colorA.rgb);
    float dyL = px.y - laneY;
    float seed = li * 17.0 + 5.0;
    float ahead = smoothstep(-0.5, 0.5, px.x - head);

    // This pixel's note, in note space that scrolls toward the ring.
    float n = px.x + time * v;
    float2 nt = noteIn(floor(n / cw), seed, cw);
    float nx = nt.x - time * v;
    float has = smoothstep(nt.y - 0.02, nt.y + 0.02, density);
    float2 gp = px - float2(nx, laneY);

    // The newest hit on this lane: the notes that most recently slid past the ring.
    float nR = head + time * v;
    float2 h0 = noteIn(floor(nR / cw), seed, cw);
    float2 h1 = noteIn(floor(nR / cw) - 1.0, seed, cw);
    float2 h2 = noteIn(floor(nR / cw) - 2.0, seed, cw);
    float t0 = (nR - h0.x) / v;
    float ok0 = step(0.0, t0) * step(h0.y, density);
    float age = h2.y < density ? (nR - h2.x) / v : 9.0;
    age = h1.y < density ? (nR - h1.x) / v : age;
    age = ok0 > 0.5 ? t0 : age;
    float burst = pow(saturate(1.0 - age / 0.6), 1.5);

    float3 acc = float3(0, 0, 0);
    float cover = 0;

    // The highway ahead: dashed lane guides and measure lines, scrolling in with the notes.
    float dash = step(frac(n / (0.32 * H)), 0.45) * smoothstep(1.0, 0.2, abs(dyL));
    float dm = abs(n - floor(n / (4.0 * cw) + 0.5) * 4.0 * cw);
    float measure = smoothstep(1.0, 0.2, dm) * step(abs(px.y - 0.5 * H), 0.4 * H);
    layer(acc, cover, lerp(float3(0.6, 0.62, 0.7), lc, 0.5), ahead * (dash * 0.3 + measure * (0.14 + 0.2 * beat)));

    // The played part: a glowing combo rail per lane, with the hit notes drifting off it as afterimages.
    float rail = exp(-abs(dyL) / (0.05 * H)) * 0.5 + smoothstep(1.1, 0.3, abs(dyL)) * 0.55;
    float fresh = exp(-max(head - px.x, 0.0) / (9.0 * H));
    layer(acc, cover, lc, (1.0 - ahead) * rail * (0.45 + 0.35 * fresh + 0.2 * level));
    float ghostAge = (head - nx) / v;
    float ghost = has * step(nx, head) * exp(-ghostAge / 1.6);
    float dGhost = gem(gp, float2(0.11 * H, 0.075 * H));
    float ghostHalo = pow(saturate(1.0 - max(dGhost, 0.0) / (0.08 * H)), 2.0) * smoothstep(0.135 * H, 0.1 * H, abs(dyL));
    layer(acc, cover, lerp(lc, float3(1, 1, 1), 0.35), ghost * (ghostHalo * 0.45 + smoothstep(0.5, -0.5, dGhost) * 0.6));

    // The judgment bar through the lanes, then the hit rings, which pulse on the beat and flash on a hit.
    float dBar = abs(px.x - head);
    float span = smoothstep(gapL + 0.14 * H, gapL, abs(px.y - 0.5 * H));
    layer(acc, cover, lerp(colorB.rgb, float3(1, 1, 1), 0.5), exp(-dBar / (0.12 * H)) * span * (0.14 + 0.12 * beat));
    layer(acc, cover, float3(1, 1, 1), (exp(-dBar / (0.04 * H + 0.8)) * 0.35 + smoothstep(0.9, 0.3, dBar) * 0.5) * step(abs(px.y - 0.5 * H), gapL));
    float2 bp = px - float2(head, laneY);
    float rr = 0.1 * H * (1.0 + 0.1 * beat + 0.12 * burst);
    float dRing = abs(length(bp) - rr) - 0.02 * H;
    float inLane = smoothstep(0.14 * H, 0.12 * H, abs(dyL));
    layer(acc, cover, lc * 0.6, smoothstep(rr, rr - 1.0, length(bp)) * (0.25 + 0.35 * burst));
    layer(acc, cover, lc, exp(-max(dRing, 0.0) / (0.04 * H)) * (0.35 + 0.4 * beat) * inLane);
    layer(acc, cover, lerp(lc, float3(1, 1, 1), 0.45 + 0.5 * burst), smoothstep(0.5, -0.5, dRing));

    // The incoming gems: cut facets lit from above, a white rim and glint, glowing as they near the ring.
    // The glow fades out inside the gem's own cell and lane, so it never shows a seam.
    float dG = gem(gp, float2(0.15 * H, 0.1 * H));
    float near = exp(-max(nx - head, 0.0) / (3.0 * H));
    float facet = gp.y > 0.0 ? 1.15 - 0.2 * step(0.0, gp.x) : 0.62 + 0.15 * step(gp.x, 0.0);
    float table = smoothstep(0.5, -0.5, gem(gp, float2(0.065 * H, 0.043 * H)));
    float3 gemColor = lc * lerp(facet, 1.35, table);
    gemColor = lerp(gemColor, float3(1, 1, 1), smoothstep(-0.03 * H, 0.0, dG) * 0.6);
    gemColor = lerp(gemColor, float3(1, 1, 1), exp(-length(gp - float2(-0.04 * H, 0.03 * H)) / (0.016 * H + 0.4)));
    float show = has * step(head, nx);
    float halo = pow(saturate(1.0 - max(dG, 0.0) / (0.075 * H)), 2.0) * smoothstep(0.135 * H, 0.1 * H, abs(dyL));
    layer(acc, cover, lc, show * halo * (0.3 + 0.5 * near));
    layer(acc, cover, gemColor, show * smoothstep(0.5, -0.5, dG));

    // The perfect-hit sparkle: a white flash, a shock ring spreading along the lane, a four-point star
    // and three sparks thrown out along the lane.
    float shock = exp(-abs(abs(bp.x) - (0.15 * H + age * 2.2 * H)) / (0.025 * H + 0.5)) * smoothstep(0.11 * H, 0.02 * H, abs(bp.y)) * burst;
    float rays = exp(-abs(bp.y) / (0.012 * H + 0.4)) * exp(-abs(bp.x) / (0.2 * H + 1.2 * H * burst))
               + exp(-abs(bp.x) / (0.012 * H + 0.4)) * exp(-abs(bp.y) / (0.09 * H)) * inLane;
    float flash = exp(-length(bp) / (0.04 * H + 0.08 * H * burst)) * smoothstep(0.14 * H, 0.05 * H, abs(dyL));
    float2 s0 = bp - age * float2(-2.6 * H, 0.18 * H);
    float2 s1 = bp - age * float2(1.9 * H, -0.15 * H);
    float2 s2 = bp - age * float2(-1.3 * H, -0.2 * H);
    float sparks = exp(-length(s0) / (0.02 * H + 0.5)) + exp(-length(s1) / (0.02 * H + 0.5)) + exp(-length(s2) / (0.02 * H + 0.5));
    layer(acc, cover, lerp(lc, float3(1, 1, 1), 0.4), shock * 0.85);
    layer(acc, cover, lerp(lc, float3(1, 1, 1), 0.55), saturate(sparks * 1.6) * burst * step(0.02, age));
    layer(acc, cover, lerp(lc, float3(1, 1, 1), 0.8), saturate(rays * burst * 1.6 + flash * burst * 1.3));

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