lingdev
↓ 0 downloadsLingDev: a Godot / Unity-style game editor written in Ling. 2-D and 3-D scenes, Flash-style timeline and cutscenes, HUDs over 3-D, Ling scripting, audio / music / MIDI, builds for Windows, Linux, macOS, web, Raspberry Pi and LingOS. Ships with Ling Agent (an N64-style spy FPS), Ling Breaker (a 3-D breakout) and Moko's Picture Book (a kawaii storybook platformer).
Keywords game-engine, editor, 3d, 2d, cutscene, timeline, ling, lingos, raspberry-pi
lingfu install lingdevCode / Files (136)
ling/rt/rt_brick.ling
# rt_brick.ling - "brick" mode: a 3-D breakout. A scene with `mode=brick` in its ex field becomes
# a stage: box nodes tagged `brick` are the bricks (ex: t=normal|tough|steel|bomb|crystal|gold,
# hp=N, drop=E|M|L|S|F|C|B|1|R, slide=amplitude), the node tagged `paddle` is the paddle, and the
# scene ex gives the field (field=xmin,xmax,zmax), the stage number, its music and `.beats` file.
#
# Solid substepped ball physics, a cel-shaded depth-buffered look with ink outlines, FXAA and
# bloom, firework bursts on every impact, brick debris, power-ups, lasers, multiball, a shield,
# extra lives, combos, a beat-synced world (the MIDI-derived .beats onsets pulse the bricks, the
# floor grid and the lights), brick chimes tuned to the song's key, and the HUD.
fn brick_on() { return st_get("bk_on", 0.0) > 0.5 }
# ── colours ──────────────────────────────────────────────────────────────────
fn bk_row_col(r) {
bind cols = [c_rgb(255.0, 70.0, 110.0), c_rgb(255.0, 150.0, 60.0), c_rgb(255.0, 220.0, 70.0), c_rgb(90.0, 230.0, 120.0), c_rgb(60.0, 200.0, 255.0), c_rgb(120.0, 120.0, 255.0), c_rgb(200.0, 100.0, 255.0), c_rgb(255.0, 110.0, 210.0)]
bind k = floor(r) - floor(floor(r) / 8.0) * 8.0
return list_get(cols, k)
}
fn bk_type_code(t) {
if t == "tough" { return 1.0 }
if t == "steel" { return 2.0 }
if t == "bomb" { return 3.0 }
if t == "crystal" { return 4.0 }
if t == "gold" { return 5.0 }
return 0.0
}
fn bk_pu_letters() { return ["E", "M", "L", "S", "F", "C", "B", "1", "R"] }
fn bk_pu_names() { return ["EXPAND", "MULTIBALL", "LASER", "SLOW", "FIREBALL", "CATCH", "SHIELD", "EXTRA LIFE", "SHRINK"] }
fn bk_pu_col(k) {
bind cols = [c_rgb(70.0, 150.0, 255.0), c_rgb(255.0, 80.0, 220.0), c_rgb(255.0, 60.0, 60.0), c_rgb(70.0, 230.0, 110.0), c_rgb(255.0, 140.0, 30.0), c_rgb(255.0, 225.0, 60.0), c_rgb(80.0, 240.0, 240.0), c_rgb(255.0, 255.0, 255.0), c_rgb(130.0, 130.0, 140.0)]
return list_get(cols, clampf(k, 0.0, 8.0))
}
fn bk_bright(c, k) { return c_rgb(c_r(c) + (255.0 - c_r(c)) * k, c_g(c) + (255.0 - c_g(c)) * k, c_b(c) + (255.0 - c_b(c)) * k) }
fn bk_dark(c, k) { return c_rgb(c_r(c) * k, c_g(c) * k, c_b(c) * k) }
# ── sound ────────────────────────────────────────────────────────────────────
fn bk_sfx_names() { return ["bell", "bell_hi", "paddle", "wall", "shatter", "clank", "crack", "pop", "firework", "boom", "laser", "drop", "powerup", "powerdown", "oneup", "lose", "launch", "clear", "gameover", "blip", "select", "stick"] }
fn bk_sfx_load() {
if st_get("bk_sfx_loaded", 0.0) > 0.5 || !plat_has_audio() { return 0.0 }
bind names = bk_sfx_names()
bind i = 0.0
while i < len(names) {
bind n = list_get(names, i)
st_set("bk_sfx_" + n, plat_sample_load(game_path("assets/sfx/" + n + ".wav")))
bind i = i + 1.0
}
st_set("bk_sfx_loaded", 1.0)
return 0.0
}
fn bk_sfx(name, vol, pitch) {
if !plat_has_audio() { return 0.0 }
bind id = st_get("bk_sfx_" + name, 0.0 - 1.0)
if id >= 0.0 { plat_sample_play_pitch(id, vol * st_get("bk_vol", 1.0), pitch) }
return 0.0
}
# a brick chime: the row picks a degree of the song's pentatonic scale
fn bk_chime(row, vol) {
bind minor = st_get("bk_minor", 1.0) > 0.5
bind deg = [0.0, 2.0, 4.0, 7.0, 9.0]
if minor { bind deg = [0.0, 3.0, 5.0, 7.0, 10.0] }
bind r = floor(row) + st_get("bk_combo", 0.0)
bind octave = floor(r / 5.0)
bind semi = st_get("bk_key", 0.0) + list_get(deg, r - octave * 5.0) + min(octave, 2.0) * 12.0
if semi > 14.0 { bind semi = semi - 12.0 }
bk_sfx("bell", vol, pow(2.0, (semi - 5.0) / 12.0))
return 0.0
}
# ── beats (from tools/make_breaker_audio.py) ─────────────────────────────────
fn bk_beats_load(path) {
bind names = ["kick", "snare", "hat", "bass"]
bind i = 0.0
while i < 4.0 {
st_lclear("bt_" + list_get(names, i))
st_set("bt_i_" + list_get(names, i), 0.0)
bind i = i + 1.0
}
st_set("bk_key", 0.0)
st_set("bk_minor", 1.0)
bind text = plat_read(game_path(path))
if text == "" { return 0.0 }
bind lines = str_split(text, "\n")
bind li = 0.0
while li < len(lines) {
bind parts = str_split(trim(list_get(lines, li)), " ")
if len(parts) > 1.5 {
bind head = list_get(parts, 0)
if head == "key" {
st_set("bk_key", to_number(list_get(parts, 1)))
if len(parts) > 2.5 { st_set("bk_minor", sel(list_get(parts, 2) == "minor", 1.0, 0.0)) }
} else if head == "kick" || head == "snare" || head == "hat" || head == "bass" {
bind k = 1.0
while k < len(parts) {
st_lpush("bt_" + head, to_number(list_get(parts, k)))
bind k = k + 1.0
}
} else if head == "bpm" { st_set("bk_bpm", to_number(list_get(parts, 1))) }
}
bind li = li + 1.0
}
return 0.0
}
# how many onsets of `name` were crossed since the last call (music position `pos`)
fn bk_beat_hits(name, pos) {
bind key = "bt_" + name
bind n = st_llen(key)
bind i = st_get("bt_i_" + name, 0.0)
if pos < st_get("bk_last_pos", 0.0) - 1.0 { bind i = 0.0 }
bind hits = 0.0
while i < n && st_lget(key, i) <= pos {
if st_lget(key, i) > pos - 0.25 { bind hits = hits + 1.0 }
bind i = i + 1.0
}
st_set("bt_i_" + name, i)
return hits
}
# ── enter / leave ────────────────────────────────────────────────────────────
fn bk_lists() { return ["bk_x", "bk_x0", "bk_z", "bk_hw", "bk_hd", "bk_hh", "bk_hp", "bk_hp0", "bk_t", "bk_c", "bk_alive", "bk_fl", "bk_die", "bk_drop", "bk_slide", "bk_row", "bk_node", "bl_x", "bl_z", "bl_vx", "bl_vz", "bl_stuck", "bl_off", "pu_x", "pu_z", "pu_k", "pu_t", "lz_x", "lz_z", "db_x", "db_y", "db_z", "db_vx", "db_vy", "db_vz", "db_l", "db_c", "db_s", "pp_x", "pp_z", "pp_txt", "pp_t", "pp_c", "ex_i", "ex_t", "st_x", "st_y", "st_z", "st_s", "st_c"] }
fn brick_enter() {
bind keys = bk_lists()
bind i = 0.0
while i < len(keys) {
st_lclear(list_get(keys, i))
bind i = i + 1.0
}
st_set("bk_on", 1.0)
bk_sfx_load()
plat_pfx_clear()
plat_pfx_max(4000.0)
fx_reset()
# a fresh run starts at stage 1 (or whichever stage the menu picked) with 3 lives
if game_var("newrun") > 0.5 { st_set("bk_run", 0.0) game_set("newrun", 0.0) }
if st_get("bk_run", 0.0) < 0.5 {
st_set("bk_run", 1.0)
st_set("bk_score", 0.0)
st_set("bk_lives", 3.0)
st_set("bk_next_life", 25000.0)
}
st_set("bk_hi", to_number(plat_read(game_path("hiscore.txt"))))
bind fld = nums_parse(sc_ex("field", "-7.2,7.2,19.2"))
st_set("bk_xmin", nums_get(fld, 0, 0.0 - 7.2))
st_set("bk_xmax", nums_get(fld, 1, 7.2))
st_set("bk_zmax", nums_get(fld, 2, 19.2))
st_set("bk_stage", to_number(sc_ex("stage", "1")))
st_set("bk_title", sc_ex("title", sc_get("name", "")))
st_set("bk_base_speed", to_number(sc_ex("speed", "10")))
bk_beats_load(sc_ex("beats", ""))
st_set("bk_last_pos", 0.0)
# bricks from the scene's nodes
bind n = nd_count()
bind i = 0.0
while i < n {
if nd_get(i, "tag") == "brick" {
bind t = bk_type_code(nd_ex(i, "t", "normal"))
bind hp0 = to_number(nd_ex(i, "hp", sel(t == 1.0, "3", "1")))
st_lpush("bk_x", nd_get(i, "px"))
st_lpush("bk_x0", nd_get(i, "px"))
st_lpush("bk_z", nd_get(i, "pz"))
st_lpush("bk_hw", nd_get(i, "sx"))
st_lpush("bk_hd", nd_get(i, "sz"))
st_lpush("bk_hh", nd_get(i, "sy"))
st_lpush("bk_hp", hp0)
st_lpush("bk_hp0", hp0)
st_lpush("bk_t", t)
st_lpush("bk_c", nd_get(i, "col"))
st_lpush("bk_alive", 1.0)
st_lpush("bk_fl", 0.0)
st_lpush("bk_die", 0.0)
bind dr = nd_ex(i, "drop", "")
st_lpush("bk_drop", sel(dr == "", 0.0 - 1.0, l_index(bk_pu_letters(), dr)))
st_lpush("bk_slide", to_number(nd_ex(i, "slide", "0")))
st_lpush("bk_row", to_number(nd_ex(i, "row", "0")))
st_lpush("bk_node", i)
}
bind i = i + 1.0
}
bind p = nd_find_tag("paddle")
st_set("bk_pz", sel(p >= 0.0, nd_get(p, "pz"), 0.6))
st_set("bk_px", 0.0)
st_set("bk_pvx", 0.0)
st_set("bk_pw", 1.3)
st_set("bk_pwt", 1.3)
bind timers = ["bk_t_expand", "bk_t_laser", "bk_t_slow", "bk_t_fire", "bk_t_catch", "bk_t_shrink", "bk_barrier", "bk_combo", "bk_shake", "bk_flash", "bk_pk", "bk_ps", "bk_pb", "bk_cool", "bk_msg_t", "bk_paused", "bk_hits"]
bind i = 0.0
while i < len(timers) {
st_set(list_get(timers, i), 0.0)
bind i = i + 1.0
}
st_set("bk_speed", st_get("bk_base_speed", 10.0))
bk_new_ball_on_paddle()
st_set("bk_state", "intro")
st_set("bk_state_t", 0.0)
# a distant star field
bind k = 0.0
while k < 140.0 {
bind a = rand() * 6.283
bind r = 40.0 + rand() * 60.0
st_lpush("st_x", cos(a) * r)
st_lpush("st_y", 0.0 - 6.0 - rand() * 50.0)
st_lpush("st_z", 30.0 + sin(a) * r * 0.5 + rand() * 40.0)
st_lpush("st_s", 0.25 + rand() * 0.9)
st_lpush("st_c", list_get([c_rgb(255.0, 255.0, 255.0), c_rgb(170.0, 200.0, 255.0), c_rgb(255.0, 200.0, 240.0), c_rgb(255.0, 240.0, 190.0)], floor(rand() * 3.99)))
bind k = k + 1.0
}
plat_post(1.0, 1.0, 1.3, c_rgb(10.0, 8.0, 24.0), 0.45)
plat_capture_mouse()
return 0.0
}
fn brick_leave() {
if brick_on() {
plat_post(0.0, 0.0, 0.0, 0.0, 0.0)
plat_release_mouse()
plat_pfx_clear()
}
st_set("bk_on", 0.0)
return 0.0
}
# ── balls ────────────────────────────────────────────────────────────────────
fn bk_new_ball_on_paddle() {
st_lpush("bl_x", st_get("bk_px", 0.0))
st_lpush("bl_z", st_get("bk_pz", 0.6) + 0.7)
st_lpush("bl_vx", 0.0)
st_lpush("bl_vz", 0.0)
st_lpush("bl_stuck", 1.0)
st_lpush("bl_off", (rand() - 0.5) * 0.6)
return 0.0
}
fn bk_ball_del(b) {
st_ldel("bl_x", b) st_ldel("bl_z", b) st_ldel("bl_vx", b) st_ldel("bl_vz", b) st_ldel("bl_stuck", b) st_ldel("bl_off", b)
return 0.0
}
fn bk_add_ball(x, z, vx, vz) {
if st_llen("bl_x") >= 8.0 { return 0.0 }
st_lpush("bl_x", x)
st_lpush("bl_z", z)
st_lpush("bl_vx", vx)
st_lpush("bl_vz", vz)
st_lpush("bl_stuck", 0.0)
st_lpush("bl_off", 0.0)
return 0.0
}
fn bk_ball_speed() {
bind s = st_get("bk_speed", 10.0)
if st_get("bk_t_slow", 0.0) > 0.0 { bind s = s * 0.62 }
return s
}
fn bk_launch(b) {
bind off = st_lget("bl_off", b) / max(0.3, st_get("bk_pw", 1.3))
bind ang = clampf(off * 0.9 + (rand() - 0.5) * 0.25, 0.0 - 1.0, 1.0)
bind s = bk_ball_speed()
st_lset("bl_vx", b, sin(ang) * s)
st_lset("bl_vz", b, cos(ang) * s)
st_lset("bl_stuck", b, 0.0)
bk_sfx("launch", 0.6, 1.0)
return 0.0
}
# ── effects ──────────────────────────────────────────────────────────────────
# the ball bursts like a firework shell where it strikes
fn bk_firework(x, z, c, big) {
bind y = 0.0 - 0.35
bind hi = bk_bright(c, 0.55)
if big > 0.5 {
plat_pfx_burst(x, y - 0.4, z, 90.0, 11.0, 1.6, 0.22, 0.0, hi, c_rgb(255.0, 255.0, 255.0), 1.0, 0.55, 0.0, 0.0 - 3.0, 0.0)
plat_pfx_burst(x, y - 0.4, z, 40.0, 6.0, 1.9, 0.5, 0.0, c, c_rgb(255.0, 230.0, 160.0), 0.0, 0.4, 0.0, 0.0 - 2.0, 0.0)
plat_pfx_burst(x, y - 0.4, z, 60.0, 15.0, 1.3, 0.16, 0.0, c_rgb(255.0, 255.0, 255.0), bk_bright(bk_row_col(rand() * 8.0), 0.3), 1.0, 0.7, 0.0, 0.0 - 4.0, 0.0)
fx_light(x, y - 1.5, z, hi, 7.0, 14.0, 0.5)
return 0.0
}
plat_pfx_burst(x, y, z, 26.0, 7.5, 0.85, 0.15, 0.0, hi, c_rgb(255.0, 255.0, 255.0), 1.0, 0.5, 0.0, 0.0 - 1.5, 0.0)
plat_pfx_burst(x, y, z, 8.0, 2.5, 0.5, 0.55, 0.05, c_rgb(255.0, 255.0, 255.0), c, 0.0, 0.0, 0.0, 0.0, 0.0)
fx_light(x, y - 0.8, z, hi, 3.5, 7.0, 0.22)
return 0.0
}
fn bk_debris(x, z, c, n) {
bind i = 0.0
while i < n {
if st_llen("db_x") > 110.0 {
st_ldel("db_x", 0.0) st_ldel("db_y", 0.0) st_ldel("db_z", 0.0) st_ldel("db_vx", 0.0) st_ldel("db_vy", 0.0)
st_ldel("db_vz", 0.0) st_ldel("db_l", 0.0) st_ldel("db_c", 0.0) st_ldel("db_s", 0.0)
}
st_lpush("db_x", x + (rand() - 0.5) * 1.2)
st_lpush("db_y", 0.0 - 0.35 - rand() * 0.3)
st_lpush("db_z", z + (rand() - 0.5) * 0.5)
st_lpush("db_vx", (rand() - 0.5) * 7.0)
st_lpush("db_vy", 0.0 - 3.0 - rand() * 5.0)
st_lpush("db_vz", (rand() - 0.5) * 6.0)
st_lpush("db_l", 1.0 + rand() * 0.8)
st_lpush("db_c", sel(rand() < 0.3, bk_bright(c, 0.4), c))
st_lpush("db_s", 0.06 + rand() * 0.11)
bind i = i + 1.0
}
return 0.0
}
fn bk_popup(x, z, txt, c) {
st_lpush("pp_x", x)
st_lpush("pp_z", z)
st_lpush("pp_txt", txt)
st_lpush("pp_t", 0.0)
st_lpush("pp_c", c)
return 0.0
}
fn bk_message(m) {
st_set("bk_msg", m)
st_set("bk_msg_t", 2.2)
return 0.0
}
fn bk_add_score(pts, x, z, c) {
bind mult = 1.0 + min(st_get("bk_combo", 0.0), 16.0) * 0.25
bind gain = floor(pts * mult / 10.0) * 10.0
st_set("bk_score", st_get("bk_score", 0.0) + gain)
bind txt = "+" + num_str(gain)
if mult > 1.01 { bind txt = txt + " x" + num_str(floor(mult * 100.0) / 100.0) }
bk_popup(x, z, txt, c)
if st_get("bk_score", 0.0) >= st_get("bk_next_life", 25000.0) {
st_set("bk_next_life", st_get("bk_next_life", 25000.0) + 25000.0)
st_set("bk_lives", st_get("bk_lives", 3.0) + 1.0)
bk_sfx("oneup", 0.8, 1.0)
bk_message("EXTRA LIFE!")
}
return 0.0
}
# ── bricks ───────────────────────────────────────────────────────────────────
fn bk_points(t) { return list_get([50.0, 120.0, 200.0, 150.0, 120.0, 500.0], clampf(t, 0.0, 5.0)) }
fn bk_breakable_left() {
bind n = 0.0
bind i = 0.0
while i < st_llen("bk_x") {
if st_lget("bk_alive", i) > 0.5 && st_lget("bk_t", i) != 2.0 { bind n = n + 1.0 }
bind i = i + 1.0
}
return n
}
# damage brick i; force = destroys anything (fireball, explosions)
fn bk_hit_brick(i, x, z, force) {
if st_lget("bk_alive", i) < 0.5 { return 0.0 }
bind t = st_lget("bk_t", i)
bind c = st_lget("bk_c", i)
st_lset("bk_fl", i, 0.14)
if t == 2.0 && force < 0.5 {
bk_sfx("clank", 0.7, 0.9 + rand() * 0.2)
plat_pfx_burst(x, 0.0 - 0.35, z, 14.0, 6.0, 0.4, 0.1, 0.0, c_rgb(255.0, 240.0, 200.0), c_rgb(255.0, 160.0, 60.0), 1.0, 0.8, 0.0, 0.0, 0.0)
return 0.0
}
bind hp = st_lget("bk_hp", i) - sel(force > 0.5, 99.0, 1.0)
st_lset("bk_hp", i, hp)
if hp > 0.0 {
bk_sfx("crack", 0.6, 0.9 + rand() * 0.25)
bk_chime(st_lget("bk_row", i), 0.35)
bk_debris(x, z, c, 3.0)
return 0.0
}
# destroyed
st_lset("bk_alive", i, 0.0)
st_lset("bk_die", i, 0.35)
st_set("bk_combo", st_get("bk_combo", 0.0) + 1.0)
st_set("bk_hits", st_get("bk_hits", 0.0) + 1.0)
bind bx = st_lget("bk_x", i)
bind bz = st_lget("bk_z", i)
bk_add_score(bk_points(t), bx, bz, bk_bright(c, 0.3))
bk_debris(bx, bz, c, sel(t == 4.0, 14.0, 9.0))
bk_chime(st_lget("bk_row", i), 0.5)
if t == 4.0 { bk_sfx("shatter", 0.75, 1.15) } else { bk_sfx("shatter", 0.5, 0.85 + rand() * 0.35) }
if t == 5.0 {
plat_pfx_burst(bx, 0.0 - 0.5, bz, 40.0, 5.0, 1.2, 0.3, 0.0, c_rgb(255.0, 240.0, 150.0), c_rgb(255.0, 180.0, 40.0), 0.0, 0.3, 0.0, 0.0 - 2.0, 0.0)
bk_sfx("bell_hi", 0.6, 1.0)
}
if t == 3.0 {
# bombs take their neighbours with them, a beat later
bk_sfx("boom", 0.8, 0.9 + rand() * 0.2)
bk_firework(bx, bz, c_rgb(255.0, 120.0, 40.0), 1.0)
st_set("bk_shake", 0.5)
st_set("bk_flash", 0.5)
bind j = 0.0
while j < st_llen("bk_x") {
if j != i && st_lget("bk_alive", j) > 0.5 {
bind dx = st_lget("bk_x", j) - bx
bind dz = st_lget("bk_z", j) - bz
if dx * dx + dz * dz < 2.5 * 2.5 {
st_lpush("ex_i", j)
st_lpush("ex_t", 0.09 + sqrt(dx * dx + dz * dz) * 0.03)
}
}
bind j = j + 1.0
}
}
# power-up?
bind drop = st_lget("bk_drop", i)
if drop < 0.0 {
bind chance = list_get([0.1, 0.2, 0.3, 0.25, 1.0, 0.3], clampf(t, 0.0, 5.0))
if rand() < chance { bind drop = bk_random_pu() }
}
if drop >= 0.0 { bk_spawn_pu(bx, bz, drop) }
return 0.0
}
fn bk_random_pu() {
bind r = rand()
if r < 0.04 { return 7.0 }
if r < 0.12 { return 8.0 }
return floor(rand() * 6.99)
}
fn bk_spawn_pu(x, z, k) {
st_lpush("pu_x", x)
st_lpush("pu_z", z)
st_lpush("pu_k", k)
st_lpush("pu_t", 0.0)
bk_sfx("drop", 0.45, 1.0)
return 0.0
}
# ── power-ups ────────────────────────────────────────────────────────────────
fn bk_apply_pu(k) {
bind nm = list_get(bk_pu_names(), k)
bk_message(nm)
if k == 8.0 { bk_sfx("powerdown", 0.7, 1.0) } else if k == 7.0 { bk_sfx("oneup", 0.85, 1.0) } else { bk_sfx("powerup", 0.7, 1.0) }
if k == 0.0 { st_set("bk_t_expand", 15.0) st_set("bk_t_shrink", 0.0) }
if k == 1.0 {
bind nb = st_llen("bl_x")
bind b = 0.0
while b < nb {
if st_lget("bl_stuck", b) < 0.5 {
bind vx = st_lget("bl_vx", b)
bind vz = st_lget("bl_vz", b)
bind a = atan2(vx, vz)
bind s = sqrt(vx * vx + vz * vz)
bk_add_ball(st_lget("bl_x", b), st_lget("bl_z", b), sin(a + 0.45) * s, cos(a + 0.45) * s)
bk_add_ball(st_lget("bl_x", b), st_lget("bl_z", b), sin(a - 0.45) * s, cos(a - 0.45) * s)
} else {
bk_add_ball(st_lget("bl_x", b), st_lget("bl_z", b) + 0.2, 0.0 - 3.0, bk_ball_speed())
bk_add_ball(st_lget("bl_x", b), st_lget("bl_z", b) + 0.2, 3.0, bk_ball_speed())
}
bind b = b + 1.0
}
}
if k == 2.0 { st_set("bk_t_laser", 12.0) }
if k == 3.0 { st_set("bk_t_slow", 10.0) }
if k == 4.0 { st_set("bk_t_fire", 8.0) }
if k == 5.0 { st_set("bk_t_catch", 15.0) }
if k == 6.0 { st_set("bk_barrier", 1.0) }
if k == 7.0 { st_set("bk_lives", st_get("bk_lives", 3.0) + 1.0) }
if k == 8.0 { st_set("bk_t_shrink", 10.0) st_set("bk_t_expand", 0.0) }
return 0.0
}
# ── input ────────────────────────────────────────────────────────────────────
fn bk_fire_pressed() {
bind md = mouse_down() || key_down("space") || plat_gamepad_button(0.0) || plat_gamepad_rt() > 0.5
bind was = st_get("bk_fire_was", 0.0) > 0.5
st_set("bk_fire_was", sel(md, 1.0, 0.0))
return md && !was
}
fn bk_fire_held() { return mouse_down() || key_down("space") || plat_gamepad_button(0.0) || plat_gamepad_rt() > 0.5 }
# ── the update ───────────────────────────────────────────────────────────────
fn brick_update(dt) {
if !brick_on() { return 0.0 }
bind dt = min(dt, 0.033)
# pause
if key_pressed("p") || (key_pressed("escape") && st_get("game_embedded", 0.0) < 0.5) {
st_set("bk_paused", sel(st_get("bk_paused", 0.0) > 0.5, 0.0, 1.0))
if st_get("bk_paused", 0.0) > 0.5 { plat_release_mouse() } else { plat_capture_mouse() }
bk_sfx("blip", 0.5, 1.0)
}
if st_get("bk_paused", 0.0) > 0.5 {
if key_pressed("q") { bk_game_over() }
return 0.0
}
bind st = st_get("bk_state", "play")
st_set("bk_state_t", st_get("bk_state_t", 0.0) + dt)
bind stt = st_get("bk_state_t", 0.0)
# music sync
bind pos = 0.0
if plat_has_audio() { bind pos = plat_music_pos() }
if bk_beat_hits("kick", pos) > 0.0 { st_set("bk_pk", 1.0) }
if bk_beat_hits("snare", pos) > 0.0 { st_set("bk_ps", 1.0) }
if bk_beat_hits("bass", pos) > 0.0 { st_set("bk_pb", 1.0) }
if bk_beat_hits("hat", pos) > 0.0 { st_set("bk_ph", 1.0) }
st_set("bk_last_pos", pos)
st_set("bk_pk", max(0.0, st_get("bk_pk", 0.0) - dt * 6.0))
st_set("bk_ps", max(0.0, st_get("bk_ps", 0.0) - dt * 5.0))
st_set("bk_pb", max(0.0, st_get("bk_pb", 0.0) - dt * 7.0))
st_set("bk_ph", max(0.0, st_get("bk_ph", 0.0) - dt * 12.0))
# timers
bind tl = ["bk_t_expand", "bk_t_laser", "bk_t_slow", "bk_t_fire", "bk_t_catch", "bk_t_shrink", "bk_shake", "bk_flash", "bk_cool", "bk_msg_t"]
bind i = 0.0
while i < len(tl) {
st_set(list_get(tl, i), max(0.0, st_get(list_get(tl, i), 0.0) - dt))
bind i = i + 1.0
}
# paddle
bind pwt = 1.3
if st_get("bk_t_expand", 0.0) > 0.0 { bind pwt = 2.2 }
if st_get("bk_t_shrink", 0.0) > 0.0 { bind pwt = 0.8 }
st_set("bk_pw", st_get("bk_pw", 1.3) + (pwt - st_get("bk_pw", 1.3)) * min(1.0, dt * 8.0))
bind pw = st_get("bk_pw", 1.3)
bind px0 = st_get("bk_px", 0.0)
bind px = px0 + mouse_dx() * 0.022
bind ax = 0.0
if key_down("a") || key_down("left") { bind ax = ax - 1.0 }
if key_down("d") || key_down("right") { bind ax = ax + 1.0 }
if abs(plat_gamepad_lx()) > 0.15 { bind ax = ax + plat_gamepad_lx() }
bind px = px + ax * 16.0 * dt
# attract / test mode: the paddle chases the lowest incoming ball
if st_get("game_autoplay", 0.0) > 0.5 {
bind best = 99.0
bind tx = px
bind b = 0.0
while b < st_llen("bl_x") {
if st_lget("bl_z", b) < best && (st_lget("bl_vz", b) < 0.0 || st_lget("bl_stuck", b) > 0.5) {
bind best = st_lget("bl_z", b)
bind tx = st_lget("bl_x", b) + sin(st_get("game_t", 0.0) * 1.3) * pw * 0.6
}
bind b = b + 1.0
}
bind px = px0 + clampf(tx - px0, 0.0 - 22.0 * dt, 22.0 * dt)
}
bind px = clampf(px, st_get("bk_xmin", 0.0 - 7.2) + pw + 0.3, st_get("bk_xmax", 7.2) - pw - 0.3)
st_set("bk_pvx", (px - px0) / max(dt, 0.001))
st_set("bk_px", px)
bind pz = st_get("bk_pz", 0.6)
# state machine
bind fire = bk_fire_pressed()
if st_get("game_autoplay", 0.0) > 0.5 && stt > 1.0 { bind fire = true }
if st == "intro" {
if stt > 0.6 && (fire || stt > 3.5) {
st_set("bk_state", "play")
st_set("bk_state_t", 0.0)
bind st = "play"
}
}
if st == "clear" {
if stt < 2.6 && floor(stt * 3.0) != floor((stt - dt) * 3.0) {
bk_firework(st_get("bk_xmin", 0.0 - 7.0) + rand() * 14.0, 8.0 + rand() * 9.0, bk_row_col(rand() * 8.0), 1.0)
bk_sfx("firework", 0.6, 0.85 + rand() * 0.3)
}
if stt > 3.6 { bk_next_stage() }
}
if st == "lost" {
if stt > 1.6 {
if st_get("bk_lives", 0.0) < 0.5 { bk_game_over() } else {
bk_new_ball_on_paddle()
st_set("bk_state", "play")
st_set("bk_state_t", 0.0)
}
}
}
# balls
if st == "play" { bk_balls_step(dt, px, pz, pw, fire) }
# lasers
if st == "play" && st_get("bk_t_laser", 0.0) > 0.0 && bk_fire_held() && st_get("bk_cool", 0.0) <= 0.0 && bk_any_flying() {
st_set("bk_cool", 0.28)
st_lpush("lz_x", px - pw + 0.25)
st_lpush("lz_z", pz + 0.4)
st_lpush("lz_x", px + pw - 0.25)
st_lpush("lz_z", pz + 0.4)
bk_sfx("laser", 0.4, 1.0 + rand() * 0.1)
}
bk_lasers_step(dt)
bk_powerups_step(dt, px, pz, pw)
bk_bricks_step(dt)
bk_debris_step(dt)
# popups rise
bind i = st_llen("pp_t") - 1.0
while i >= 0.0 {
bind t = st_lget("pp_t", i) + dt
if t > 1.1 { st_ldel("pp_x", i) st_ldel("pp_z", i) st_ldel("pp_txt", i) st_ldel("pp_t", i) st_ldel("pp_c", i) } else { st_lset("pp_t", i, t) }
bind i = i - 1.0
}
# beat twinkles in the sky
if st_get("bk_ph", 0.0) > 0.95 && st_llen("st_x") > 0.0 {
bind k = floor(rand() * (st_llen("st_x") - 0.01))
plat_pfx_burst(st_lget("st_x", k), st_lget("st_y", k), st_lget("st_z", k), 6.0, 2.0, 0.5, 0.5, 0.0, c_rgb(255.0, 255.0, 255.0), st_lget("st_c", k), 0.0, 0.0, 0.0, 0.0, 0.0)
}
plat_pfx_step(dt, 9.8, 0.9, 0.0 - 0.02, 0.35)
fx_update(dt)
bk_camera(dt)
# stage clear?
if st == "play" && bk_breakable_left() < 0.5 {
st_set("bk_state", "clear")
st_set("bk_state_t", 0.0)
bk_sfx("clear", 0.9, 1.0)
bk_add_score(1000.0 * st_get("bk_stage", 1.0), 0.0, 10.0, c_rgb(255.0, 255.0, 200.0))
bk_message("STAGE CLEAR")
}
return 0.0
}
fn bk_any_flying() {
bind b = 0.0
while b < st_llen("bl_x") {
if st_lget("bl_stuck", b) < 0.5 { return true }
bind b = b + 1.0
}
return false
}
fn bk_balls_step(dt, px, pz, pw, fire) {
bind br = 0.3
bind pr = 0.32
bind xmin = st_get("bk_xmin", 0.0 - 7.2)
bind xmax = st_get("bk_xmax", 7.2)
bind zmax = st_get("bk_zmax", 19.2)
bind fireball = st_get("bk_t_fire", 0.0) > 0.0
bind b = st_llen("bl_x") - 1.0
while b >= 0.0 {
bind x = st_lget("bl_x", b)
bind z = st_lget("bl_z", b)
bind vx = st_lget("bl_vx", b)
bind vz = st_lget("bl_vz", b)
bind dead = false
if st_lget("bl_stuck", b) > 0.5 {
bind x = px + st_lget("bl_off", b)
bind z = pz + pr + br + 0.02
if fire { bk_launch(b) bind vx = st_lget("bl_vx", b) bind vz = st_lget("bl_vz", b) }
} else {
bind s = bk_ball_speed()
bind steps = max(1.0, ceil(s * dt / 0.12))
bind h = dt / steps
bind k = 0.0
while k < steps && !dead {
bind x = x + vx * h
bind z = z + vz * h
# walls
if x < xmin + br { bind x = xmin + br bind vx = abs(vx) bk_wall_hit(x, z) }
if x > xmax - br { bind x = xmax - br bind vx = 0.0 - abs(vx) bk_wall_hit(x, z) }
if z > zmax - br { bind z = zmax - br bind vz = 0.0 - abs(vz) bk_wall_hit(x, z) }
# paddle (a capsule along x)
if vz < 0.0 && z < pz + pr + br + 0.05 && z > pz - pr - br {
bind cx = clampf(x, px - pw, px + pw)
bind dx = x - cx
bind dz = z - pz
if dx * dx + dz * dz < (br + pr) * (br + pr) {
bind off = clampf((x - px) / (pw + pr), 0.0 - 1.0, 1.0)
bind ang = off * 1.05 + clampf(st_get("bk_pvx", 0.0) * 0.012, 0.0 - 0.2, 0.2)
bind ang = clampf(ang, 0.0 - 1.15, 1.15)
st_set("bk_speed", min(st_get("bk_base_speed", 10.0) + 8.0, st_get("bk_speed", 10.0) + 0.18))
bind s = bk_ball_speed()
bind vx = sin(ang) * s
bind vz = cos(ang) * s
bind z = pz + pr + br + 0.01
st_set("bk_combo", 0.0)
st_set("bk_pb", 1.0)
bk_sfx("paddle", 0.6, 0.9 + abs(off) * 0.25)
plat_pfx_burst(x, 0.0 - 0.3, z, 10.0, 4.0, 0.35, 0.12, 0.0, c_rgb(160.0, 230.0, 255.0), c_rgb(80.0, 120.0, 255.0), 1.0, 0.3, 0.0, 0.0, 2.0)
if st_get("bk_t_catch", 0.0) > 0.0 {
st_lset("bl_stuck", b, 1.0)
st_lset("bl_off", b, clampf(x - px, 0.0 - pw, pw))
bk_sfx("stick", 0.6, 1.0)
bind k = steps
}
}
}
# bricks
if z > 2.0 {
bind hit = bk_ball_vs_bricks(x, z, vx, vz, br, fireball)
if len(hit) > 4.5 {
bind x = list_get(hit, 0)
bind z = list_get(hit, 1)
bind vx = list_get(hit, 2)
bind vz = list_get(hit, 3)
}
}
# shield / lost
if z < pz - 0.9 && st_get("bk_barrier", 0.0) > 0.5 {
st_set("bk_barrier", 0.0)
bind vz = abs(vz)
bind z = pz - 0.85
bk_sfx("clank", 0.6, 1.4)
bk_firework(x, z, c_rgb(80.0, 240.0, 240.0), 0.0)
}
if z < pz - 2.5 { bind dead = true }
bind k = k + 1.0
}
# keep the speed and stop near-horizontal paths
bind sp = sqrt(vx * vx + vz * vz)
if sp > 0.001 {
bind s = bk_ball_speed()
bind vx = vx / sp * s
bind vz = vz / sp * s
if abs(vz) < s * 0.28 {
bind vz = sel(vz < 0.0, 0.0 - 1.0, 1.0) * s * 0.28
bind vx = sel(vx < 0.0, 0.0 - 1.0, 1.0) * sqrt(max(0.0, s * s - vz * vz))
}
}
# trail
if fireball {
plat_pfx_emit(x + (rand() - 0.5) * 0.3, 0.0 - 0.3, z + (rand() - 0.5) * 0.3, (rand() - 0.5) * 1.5, 0.0 - 2.0 - rand() * 2.0, (rand() - 0.5) * 1.5, 0.45, 0.55, 0.05, c_rgb(255.0, 200.0, 80.0), c_rgb(255.0, 50.0, 10.0), 0.0, 0.0, 0.0 - 0.3, 1.0)
} else {
plat_pfx_emit(x, 0.0 - 0.3, z, 0.0, 0.0, 0.0, 0.28, 0.42, 0.05, c_rgb(190.0, 230.0, 255.0), c_rgb(120.0, 60.0, 255.0), 0.0, 0.0, 0.0, 0.7)
}
}
if dead {
bk_ball_del(b)
if st_llen("bl_x") < 0.5 { bk_lose_life(x, z) }
} else {
st_lset("bl_x", b, x)
st_lset("bl_z", b, z)
st_lset("bl_vx", b, vx)
st_lset("bl_vz", b, vz)
}
bind b = b - 1.0
}
return 0.0
}
fn bk_wall_hit(x, z) {
bk_sfx("wall", 0.35, 0.95 + rand() * 0.1)
st_set("bk_ps", max(st_get("bk_ps", 0.0), 0.6))
plat_pfx_burst(x, 0.0 - 0.3, z, 6.0, 3.0, 0.3, 0.1, 0.0, c_rgb(200.0, 220.0, 255.0), c_rgb(120.0, 140.0, 255.0), 1.0, 0.3, 0.0, 0.0, 0.0)
return 0.0
}
# returns [x, z, vx, vz, hit] after the first brick contact, or [] for none
fn bk_ball_vs_bricks(x, z, vx, vz, br, fireball) {
bind n = st_llen("bk_x")
bind i = 0.0
while i < n {
if st_lget("bk_alive", i) > 0.5 {
bind bx = st_lget("bk_x", i)
bind bz = st_lget("bk_z", i)
bind hw = st_lget("bk_hw", i)
bind hd = st_lget("bk_hd", i)
if abs(x - bx) < hw + br && abs(z - bz) < hd + br {
bind cx = clampf(x, bx - hw, bx + hw)
bind cz = clampf(z, bz - hd, bz + hd)
bind dx = x - cx
bind dz = z - cz
bind d2 = dx * dx + dz * dz
if d2 < br * br {
bind nx = 0.0
bind nz = 0.0
if d2 > 0.000001 {
bind d = sqrt(d2)
bind nx = dx / d
bind nz = dz / d
} else {
# centre inside: leave by the shallow side
bind px = hw - abs(x - bx)
bind pz = hd - abs(z - bz)
if px < pz { bind nx = sel(x < bx, 0.0 - 1.0, 1.0) } else { bind nz = sel(z < bz, 0.0 - 1.0, 1.0) }
}
bind steel = st_lget("bk_t", i) == 2.0
bk_hit_brick(i, cx, cz, sel(fireball, 1.0, 0.0))
bk_firework(cx, cz, sel(fireball, c_rgb(255.0, 150.0, 40.0), st_lget("bk_c", i)), 0.0)
bk_sfx("pop", 0.35, 0.9 + rand() * 0.3)
st_set("bk_ball_flash", 0.12)
if fireball && !steel { return [x, z, vx, vz, 0.0] }
if fireball && steel && st_lget("bk_alive", i) < 0.5 { return [x, z, vx, vz, 0.0] }
bind vn = vx * nx + vz * nz
if vn < 0.0 {
bind vx = vx - 2.0 * vn * nx
bind vz = vz - 2.0 * vn * nz
}
if d2 > 0.000001 {
bind x = cx + nx * (br + 0.002)
bind z = cz + nz * (br + 0.002)
} else {
if nx != 0.0 { bind x = bx + nx * (hw + br + 0.002) } else { bind z = bz + nz * (hd + br + 0.002) }
}
return [x, z, vx, vz, 1.0]
}
}
}
bind i = i + 1.0
}
return []
}
fn bk_lose_life(x, z) {
st_set("bk_lives", st_get("bk_lives", 1.0) - 1.0)
st_set("bk_state", "lost")
st_set("bk_state_t", 0.0)
st_set("bk_shake", 0.7)
st_set("bk_flash", 0.6)
st_set("bk_t_expand", 0.0) st_set("bk_t_laser", 0.0) st_set("bk_t_slow", 0.0) st_set("bk_t_fire", 0.0) st_set("bk_t_catch", 0.0) st_set("bk_t_shrink", 0.0)
st_set("bk_speed", st_get("bk_base_speed", 10.0))
st_lclear("pu_x") st_lclear("pu_z") st_lclear("pu_k") st_lclear("pu_t")
bk_firework(clampf(x, st_get("bk_xmin", 0.0 - 7.0) + 1.0, st_get("bk_xmax", 7.0) - 1.0), max(z, st_get("bk_pz", 0.6)), c_rgb(255.0, 90.0, 140.0), 1.0)
bk_sfx("lose", 0.8, 1.0)
bk_sfx("firework", 0.7, 1.0)
if st_get("bk_lives", 0.0) < 0.5 { bk_message("GAME OVER") } else { bk_message("BALL LOST") }
return 0.0
}
fn bk_lasers_step(dt) {
bind i = st_llen("lz_x") - 1.0
while i >= 0.0 {
bind x = st_lget("lz_x", i)
bind z = st_lget("lz_z", i) + 30.0 * dt
bind gone = z > st_get("bk_zmax", 19.0)
if !gone {
bind n = st_llen("bk_x")
bind j = 0.0
while j < n && !gone {
if st_lget("bk_alive", j) > 0.5 {
if abs(x - st_lget("bk_x", j)) < st_lget("bk_hw", j) + 0.05 && abs(z - st_lget("bk_z", j)) < st_lget("bk_hd", j) + 0.2 {
bk_hit_brick(j, x, st_lget("bk_z", j) - st_lget("bk_hd", j), 0.0)
plat_pfx_burst(x, 0.0 - 0.35, z, 10.0, 5.0, 0.35, 0.12, 0.0, c_rgb(255.0, 200.0, 200.0), c_rgb(255.0, 40.0, 40.0), 1.0, 0.5, 0.0, 0.0, 0.0 - 2.0)
bind gone = true
}
}
bind j = j + 1.0
}
}
if gone { st_ldel("lz_x", i) st_ldel("lz_z", i) } else { st_lset("lz_z", i, z) }
bind i = i - 1.0
}
return 0.0
}
fn bk_powerups_step(dt, px, pz, pw) {
bind i = st_llen("pu_x") - 1.0
while i >= 0.0 {
bind z = st_lget("pu_z", i) - 4.6 * dt
st_lset("pu_t", i, st_lget("pu_t", i) + dt)
bind x = st_lget("pu_x", i)
bind got = abs(x - px) < pw + 0.55 && abs(z - pz) < 0.55
if got {
bind k = st_lget("pu_k", i)
plat_pfx_burst(x, 0.0 - 0.4, pz, 30.0, 6.0, 0.7, 0.3, 0.0, bk_pu_col(k), c_rgb(255.0, 255.0, 255.0), 0.0, 0.2, 0.0, 0.0 - 3.0, 0.0)
bk_apply_pu(k)
st_ldel("pu_x", i) st_ldel("pu_z", i) st_ldel("pu_k", i) st_ldel("pu_t", i)
} else if z < pz - 3.0 {
st_ldel("pu_x", i) st_ldel("pu_z", i) st_ldel("pu_k", i) st_ldel("pu_t", i)
} else { st_lset("pu_z", i, z) }
bind i = i - 1.0
}
return 0.0
}
fn bk_bricks_step(dt) {
bind t = st_get("game_t", 0.0)
bind n = st_llen("bk_x")
bind i = 0.0
while i < n {
st_lset("bk_fl", i, max(0.0, st_lget("bk_fl", i) - dt))
st_lset("bk_die", i, max(0.0, st_lget("bk_die", i) - dt))
bind sl = st_lget("bk_slide", i)
if sl > 0.0 && st_lget("bk_alive", i) > 0.5 { st_lset("bk_x", i, st_lget("bk_x0", i) + sin(t * 0.9 + st_lget("bk_z", i)) * sl) }
bind i = i + 1.0
}
# chained explosions
bind i = st_llen("ex_t") - 1.0
while i >= 0.0 {
bind et = st_lget("ex_t", i) - dt
if et <= 0.0 {
bind j = st_lget("ex_i", i)
st_ldel("ex_i", i)
st_ldel("ex_t", i)
bk_hit_brick(j, st_lget("bk_x", j), st_lget("bk_z", j), 1.0)
bk_firework(st_lget("bk_x", j), st_lget("bk_z", j), st_lget("bk_c", j), 0.0)
} else { st_lset("ex_t", i, et) }
bind i = i - 1.0
}
return 0.0
}
fn bk_debris_step(dt) {
bind i = st_llen("db_x") - 1.0
while i >= 0.0 {
bind l = st_lget("db_l", i) - dt
if l <= 0.0 {
st_ldel("db_x", i) st_ldel("db_y", i) st_ldel("db_z", i) st_ldel("db_vx", i) st_ldel("db_vy", i)
st_ldel("db_vz", i) st_ldel("db_l", i) st_ldel("db_c", i) st_ldel("db_s", i)
} else {
bind vy = st_lget("db_vy", i) + 22.0 * dt
bind y = st_lget("db_y", i) + vy * dt
bind vx = st_lget("db_vx", i)
bind vz = st_lget("db_vz", i)
bind s = st_lget("db_s", i)
if y > 0.0 - s {
bind y = 0.0 - s
bind vy = 0.0 - abs(vy) * 0.38
bind vx = vx * 0.7
bind vz = vz * 0.7
}
st_lset("db_x", i, st_lget("db_x", i) + vx * dt)
st_lset("db_z", i, st_lget("db_z", i) + vz * dt)
st_lset("db_y", i, y)
st_lset("db_vx", i, vx)
st_lset("db_vy", i, vy)
st_lset("db_vz", i, vz)
st_lset("db_l", i, l)
}
bind i = i - 1.0
}
return 0.0
}
# ── camera ───────────────────────────────────────────────────────────────────
fn bk_camera(dt) {
bind px = st_get("bk_px", 0.0)
bind sway = st_get("bk_cam_sway", 0.0)
bind sway = sway + (px * 0.06 - sway) * min(1.0, dt * 3.0)
st_set("bk_cam_sway", sway)
bind sh = st_get("bk_shake", 0.0)
bind jx = (rand() - 0.5) * sh * 0.6
bind jy = (rand() - 0.5) * sh * 0.6
bind intro = 1.0 - clampf(st_get("bk_state_t", 0.0) / 1.4, 0.0, 1.0)
if st_get("bk_state", "") != "intro" { bind intro = 0.0 }
bind e = intro * intro
bind dist = 16.6 + e * 9.0
bind pitch = 0.9 + e * 0.25 - st_get("bk_pk", 0.0) * 0.004
cam_orbit(jx + sway * 2.0, 0.0 - 0.5 + jy, 7.9, sway + e * 0.6, pitch, dist, 1.0)
return 0.0
}
fn bk_next_stage() {
bind next = sc_ex("next", "")
st_set("gs_score", num_str(st_get("bk_score", 0.0)))
st_set("gs_stage", num_str(st_get("bk_stage", 1.0)))
bk_save_hi()
brick_leave()
if next == "" { st_set("bk_run", 0.0) game_goto("scenes/victory.lscene") } else { game_goto(next) }
return 0.0
}
fn bk_game_over() {
st_set("gs_score", num_str(st_get("bk_score", 0.0)))
st_set("gs_stage", num_str(st_get("bk_stage", 1.0)))
bk_save_hi()
st_set("bk_run", 0.0)
brick_leave()
bk_sfx("gameover", 0.9, 1.0)
game_goto("scenes/gameover.lscene")
return 0.0
}
fn bk_save_hi() {
bind hi = max(st_get("bk_hi", 0.0), st_get("bk_score", 0.0))
st_set("bk_hi", hi)
st_set("gs_hi", num_str(hi))
st_set("gs_newhi", sel(st_get("bk_score", 0.0) >= hi && hi > 0.0, "NEW HIGH SCORE!", ""))
plat_write(game_path("hiscore.txt"), num_str(hi))
return 0.0
}
# ── drawing ──────────────────────────────────────────────────────────────────
# lights before the scene's shapes are lit: the balls glow, the beat pumps the key light
fn brick_lights() {
if !brick_on() { return 0.0 }
bind pk = st_get("bk_pk", 0.0)
plat_light(0.0, 0.0 - 9.0, 6.0, 255.0, 240.0, 255.0, 1.6 + pk * 0.9, 40.0)
plat_light(0.0 - 9.0, 0.0 - 4.0, 18.0, 255.0, 80.0, 200.0, 1.4 + st_get("bk_ps", 0.0) * 1.4, 22.0)
plat_light(9.0, 0.0 - 4.0, 18.0, 80.0, 160.0, 255.0, 1.4 + st_get("bk_ps", 0.0) * 1.4, 22.0)
bind b = 0.0
while b < min(3.0, st_llen("bl_x")) {
bind c = sel(st_get("bk_t_fire", 0.0) > 0.0, c_rgb(255.0, 140.0, 40.0), c_rgb(150.0, 200.0, 255.0))
plat_light(st_lget("bl_x", b), 0.0 - 1.0, st_lget("bl_z", b), c_r(c), c_g(c), c_b(c), 2.2, 6.0)
bind b = b + 1.0
}
return 0.0
}
fn bk_box(c, x, y, z, sx, sy, sz, rx, ry, rz) {
plat_color(c)
plat_shape("box", x, y, z, sx, sy, sz, rx, ry, rz, 0.0)
return 0.0
}
fn brick_world_native() {
if !brick_on() { return 0.0 }
bind t = st_get("game_t", 0.0)
bind pk = st_get("bk_pk", 0.0)
bind ps = st_get("bk_ps", 0.0)
bind xmin = st_get("bk_xmin", 0.0 - 7.2)
bind xmax = st_get("bk_xmax", 7.2)
bind zmax = st_get("bk_zmax", 19.2)
bk_deco(t, pk, ps, xmin, xmax, zmax)
# bricks
bind n = st_llen("bk_x")
bind i = 0.0
while i < n {
bind alive = st_lget("bk_alive", i) > 0.5
bind die = st_lget("bk_die", i)
if alive || die > 0.0 {
bind x = st_lget("bk_x", i)
bind z = st_lget("bk_z", i)
bind hw = st_lget("bk_hw", i)
bind hd = st_lget("bk_hd", i)
bind hh = st_lget("bk_hh", i) * (1.0 + pk * 0.22)
bind ty = st_lget("bk_t", i)
bind c = st_lget("bk_c", i)
bind fl = st_lget("bk_fl", i)
if fl > 0.0 { bind c = c_mix(c, c_rgb(255.0, 255.0, 255.0), fl / 0.14) }
bind sc = 1.0
bind lift = 0.0
bind spin = 0.0
if !alive {
bind k = die / 0.35
bind sc = k
bind lift = (1.0 - k) * 1.2
bind spin = (1.0 - k) * 2.5
bind c = c_mix(c_rgb(255.0, 255.0, 255.0), c, k)
}
bind y = 0.0 - hh - lift
bk_box(c, x, y, z, hw * sc, hh * sc, hd * sc, spin * 0.5, spin, 0.0)
# bevelled cap
bk_box(bk_bright(c, 0.35), x, y - hh * sc, z, hw * sc * 0.82, 0.04 * sc, hd * sc * 0.7, spin * 0.5, spin, 0.0)
if alive {
if ty == 1.0 {
# tough: cracks as hits land
bind dmg = st_lget("bk_hp0", i) - st_lget("bk_hp", i)
if dmg > 0.5 { bk_box(bk_dark(c, 0.3), x - hw * 0.2, y - hh - 0.05, z, hw * 0.5, 0.012, 0.03, 0.0, 0.5, 0.0) }
if dmg > 1.5 { bk_box(bk_dark(c, 0.3), x + hw * 0.3, y - hh - 0.05, z + hd * 0.2, 0.03, 0.012, hd * 0.6, 0.0, 0.3, 0.0) }
bk_box(bk_dark(c, 0.55), x, y, z - hd - 0.01, hw * 0.9, hh * 0.25, 0.01, 0.0, 0.0, 0.0)
} else if ty == 2.0 {
bk_box(c_rgb(235.0, 240.0, 255.0), x, y - hh - 0.05, z, hw * 0.9, 0.012, 0.035, 0.0, 0.0, 0.0)
bk_box(c_rgb(90.0, 95.0, 110.0), x - hw * 0.75, y - hh - 0.06, z, 0.06, 0.02, 0.06, 0.0, 0.0, 0.0)
bk_box(c_rgb(90.0, 95.0, 110.0), x + hw * 0.75, y - hh - 0.06, z, 0.06, 0.02, 0.06, 0.0, 0.0, 0.0)
} else if ty == 3.0 {
bind glow = 0.5 + 0.5 * sin(t * 9.0 + i)
plat_color(c_mix(c_rgb(255.0, 80.0, 20.0), c_rgb(255.0, 240.0, 120.0), glow))
plat_shape("sphere", x, y - hh - 0.12, z, 0.2, 0.2, 0.2, 0.0, 0.0, 0.0, 0.0)
} else if ty == 4.0 {
bind tw = 0.5 + 0.5 * sin(t * 4.0 + i * 1.7)
plat_tm_billboard(0.0 - 1.0, x + hw * 0.5, y - hh - 0.1, z, 0.5 + tw * 0.4, 0.0, c_rgb(200.0, 255.0, 255.0), 0.5 + tw * 0.5, 1.0)
} else if ty == 5.0 {
bind tw = 0.5 + 0.5 * sin(t * 5.0 + i * 2.3)
plat_tm_billboard(0.0 - 1.0, x - hw * 0.4, y - hh - 0.12, z, 0.35 + tw * 0.5, 0.0, c_rgb(255.0, 240.0, 160.0), tw, 1.0)
}
}
}
bind i = i + 1.0
}
# paddle: a capsule with glowing caps
bind px = st_get("bk_px", 0.0)
bind pz = st_get("bk_pz", 0.6)
bind pw = st_get("bk_pw", 1.3)
bind pb = st_get("bk_pb", 0.0)
bind body = c_rgb(230.0, 235.0, 255.0)
if st_get("bk_t_catch", 0.0) > 0.0 { bind body = c_rgb(255.0, 240.0, 150.0) }
plat_color(body)
plat_shape("capsule", px, 0.0 - 0.34, pz, 0.32, pw + 0.32, 0.32, 0.0, 0.0, 1.5708, 0.0)
bind capc = c_mix(c_rgb(80.0, 150.0, 255.0), c_rgb(200.0, 240.0, 255.0), pb)
plat_color(capc)
plat_shape("sphere", px - pw, 0.0 - 0.34, pz, 0.36, 0.36, 0.36, 0.0, 0.0, 0.0, 0.0)
plat_shape("sphere", px + pw, 0.0 - 0.34, pz, 0.36, 0.36, 0.36, 0.0, 0.0, 0.0, 0.0)
bk_box(c_mix(c_rgb(255.0, 80.0, 200.0), c_rgb(255.0, 255.0, 255.0), pb), px, 0.0 - 0.66, pz, pw * 0.7, 0.025, 0.05, 0.0, 0.0, 0.0)
if st_get("bk_t_laser", 0.0) > 0.0 {
bk_box(c_rgb(255.0, 60.0, 60.0), px - pw + 0.25, 0.0 - 0.75, pz + 0.1, 0.08, 0.18, 0.2, 0.0, 0.0, 0.0)
bk_box(c_rgb(255.0, 60.0, 60.0), px + pw - 0.25, 0.0 - 0.75, pz + 0.1, 0.08, 0.18, 0.2, 0.0, 0.0, 0.0)
}
# shield
if st_get("bk_barrier", 0.0) > 0.5 {
bind sc = c_mix(c_rgb(60.0, 220.0, 240.0), c_rgb(255.0, 255.0, 255.0), 0.5 + 0.5 * sin(t * 6.0))
bk_box(sc, 0.0, 0.0 - 0.15, pz - 0.95, (xmax - xmin) * 0.5, 0.05, 0.05, 0.0, 0.0, 0.0)
}
# balls
bind fireball = st_get("bk_t_fire", 0.0) > 0.0
bind flash = st_get("bk_ball_flash", 0.0)
st_set("bk_ball_flash", max(0.0, flash - 0.016))
bind b = 0.0
while b < st_llen("bl_x") {
bind bx = st_lget("bl_x", b)
bind bz = st_lget("bl_z", b)
bind r = 0.3 * (1.0 + flash * 2.5)
plat_color(sel(fireball, c_rgb(255.0, 210.0, 120.0), c_rgb(255.0, 255.0, 255.0)))
plat_shape("sphere", bx, 0.0 - 0.3, bz, r, r, r, 0.0, t * 6.0, 0.0, 0.0)
plat_tm_billboard(0.0 - 1.0, bx, 0.0 - 0.3, bz, 2.0 + flash * 5.0, 0.0, sel(fireball, c_rgb(255.0, 120.0, 30.0), c_rgb(130.0, 190.0, 255.0)), 0.85, 1.0)
bind b = b + 1.0
}
# lasers
bind i = 0.0
while i < st_llen("lz_x") {
bk_box(c_rgb(255.0, 90.0, 90.0), st_lget("lz_x", i), 0.0 - 0.5, st_lget("lz_z", i), 0.04, 0.04, 0.45, 0.0, 0.0, 0.0)
plat_tm_billboard(0.0 - 1.0, st_lget("lz_x", i), 0.0 - 0.5, st_lget("lz_z", i) + 0.4, 0.8, 0.0, c_rgb(255.0, 80.0, 80.0), 0.9, 1.0)
bind i = i + 1.0
}
# power-up capsules
bind i = 0.0
while i < st_llen("pu_x") {
bind k = st_lget("pu_k", i)
bind c = bk_pu_col(k)
bind x = st_lget("pu_x", i)
bind z = st_lget("pu_z", i)
bind spin = st_lget("pu_t", i) * 4.0
plat_color(c)
plat_shape("capsule", x, 0.0 - 0.45, z, 0.26, 0.55, 0.26, spin, 0.0, 1.5708, 0.0)
bk_box(c_rgb(255.0, 255.0, 255.0), x, 0.0 - 0.45, z, 0.18, 0.28, 0.28, spin, 0.0, 0.0)
plat_tm_billboard(0.0 - 1.0, x, 0.0 - 0.5, z, 1.6, 0.0, c, 0.55, 1.0)
bind i = i + 1.0
}
# debris
bind i = 0.0
while i < st_llen("db_x") {
bind s = st_lget("db_s", i) * clampf(st_lget("db_l", i) * 2.0, 0.0, 1.0)
bind l = st_lget("db_l", i)
bk_box(st_lget("db_c", i), st_lget("db_x", i), st_lget("db_y", i), st_lget("db_z", i), s, s, s, l * 7.0, l * 5.0, l * 3.0)
bind i = i + 1.0
}
plat_pfx_draw()
return 0.0
}
# the stage's surroundings: floor grid, neon rails, the sky. Also drawn for a brick stage used
# as a menu backdrop (not in brick mode), with a slow idle pulse instead of the beat.
fn bk_deco(t, pk, ps, xmin, xmax, zmax) {
# floor grid lines (glow on the kick)
bind gc = c_mix(c_rgb(40.0, 50.0, 120.0), c_rgb(120.0, 200.0, 255.0), pk * 0.8)
bind gx = xmin
while gx <= xmax + 0.01 {
bk_box(gc, gx, 0.0 - 0.005, (zmax - 1.5) * 0.5, 0.025, 0.006, (zmax + 1.5) * 0.5, 0.0, 0.0, 0.0)
bind gx = gx + 1.8
}
bind gz = 0.0 - 1.5
while gz <= zmax {
bk_box(gc, 0.0, 0.0 - 0.005, gz, (xmax - xmin) * 0.5, 0.006, 0.025, 0.0, 0.0, 0.0)
bind gz = gz + 1.8
}
# neon rails on the walls (flash on the snare)
bind rc = c_mix(c_rgb(255.0, 70.0, 200.0), c_rgb(255.0, 255.0, 255.0), ps * 0.6)
bind rc2 = c_mix(c_rgb(70.0, 170.0, 255.0), c_rgb(255.0, 255.0, 255.0), ps * 0.6)
bk_box(rc, xmin - 0.12, 0.0 - 0.95, (zmax - 1.5) * 0.5, 0.06, 0.06, (zmax + 1.5) * 0.5, 0.0, 0.0, 0.0)
bk_box(rc2, xmax + 0.12, 0.0 - 0.95, (zmax - 1.5) * 0.5, 0.06, 0.06, (zmax + 1.5) * 0.5, 0.0, 0.0, 0.0)
bk_box(c_mix(rc, rc2, 0.5), 0.0, 0.0 - 0.95, zmax + 0.12, (xmax - xmin) * 0.5 + 0.2, 0.06, 0.06, 0.0, 0.0, 0.0)
if st_llen("st_x") < 1.0 {
bind k = 0.0
while k < 140.0 {
bind a = rand() * 6.283
bind r = 40.0 + rand() * 60.0
st_lpush("st_x", cos(a) * r)
st_lpush("st_y", 0.0 - 6.0 - rand() * 50.0)
st_lpush("st_z", 30.0 + sin(a) * r * 0.5 + rand() * 40.0)
st_lpush("st_s", 0.25 + rand() * 0.9)
st_lpush("st_c", list_get([c_rgb(255.0, 255.0, 255.0), c_rgb(170.0, 200.0, 255.0), c_rgb(255.0, 200.0, 240.0), c_rgb(255.0, 240.0, 190.0)], floor(rand() * 3.99)))
bind k = k + 1.0
}
}
# the sky: stars, a ringed planet, nebula glows
bind i = 0.0
while i < st_llen("st_x") {
bind tw = 0.6 + 0.4 * sin(t * 2.0 + i * 1.3)
plat_tm_billboard(0.0 - 1.0, st_lget("st_x", i), st_lget("st_y", i), st_lget("st_z", i), st_lget("st_s", i) * (1.0 + st_get("bk_ph", 0.0) * 0.3), 0.0, st_lget("st_c", i), tw, 1.0)
bind i = i + 1.0
}
plat_tm_billboard(0.0 - 1.0, 0.0 - 30.0, 0.0 - 22.0, 70.0, 60.0, 0.0, c_rgb(120.0, 40.0, 160.0), 0.35, 1.0)
plat_tm_billboard(0.0 - 1.0, 35.0, 0.0 - 30.0, 80.0, 70.0, 0.0, c_rgb(30.0, 80.0, 170.0), 0.3, 1.0)
plat_color(c_rgb(255.0, 170.0, 120.0))
plat_shape("sphere", 24.0, 0.0 - 16.0, 62.0, 7.0, 7.0, 7.0, 0.3, t * 0.05, 0.0, 0.0)
plat_color(c_rgb(255.0, 220.0, 170.0))
plat_shape("torus", 24.0, 0.0 - 16.0, 62.0, 11.0, 0.5, 11.0, 0.35, 0.0, 0.2, 0.0)
return 0.0
}
fn brick_deco_native() {
if brick_on() || sc_ex("mode", "") != "brick" { return 0.0 }
bind t = st_get("game_t", 0.0)
bind fld = nums_parse(sc_ex("field", "-7.2,7.2,19.2"))
bind pk = 0.5 + 0.5 * sin(t * 3.0)
bk_deco(t, pk * 0.5, 0.0, nums_get(fld, 0, 0.0 - 7.2), nums_get(fld, 1, 7.2), nums_get(fld, 2, 19.2))
return 0.0
}
# ── HUD ──────────────────────────────────────────────────────────────────────
fn bk_fmt(n) {
bind s = num_str(floor(n))
bind out = ""
bind k = 0.0
bind l = str_len(s)
while k < l {
if k > 0.0 && (l - k) - floor((l - k) / 3.0) * 3.0 < 0.5 { bind out = out + "," }
bind out = out + str_slice(s, k, 1.0)
bind k = k + 1.0
}
return out
}
fn brick_hud(x, y, w, h) {
if !brick_on() { return 0.0 }
bind s = h / 720.0
bind t = st_get("game_t", 0.0)
bind ink = c_rgb(235.0, 240.0, 255.0)
bind dim = c_rgb(150.0, 160.0, 210.0)
# screen flash
if st_get("bk_flash", 0.0) > 0.0 { g_rect_a(x, y, w, h, c_rgb(255.0, 255.0, 255.0), st_get("bk_flash", 0.0) * 0.35) }
# popups
bind i = 0.0
while i < st_llen("pp_x") {
bind pt = st_lget("pp_t", i)
bind p = cam_project(st_lget("pp_x", i), 0.0 - 1.0 - pt * 1.6, st_lget("pp_z", i))
if cam_visible(list_get(p, 2)) {
bind a = clampf((1.1 - pt) * 2.0, 0.0, 1.0)
bind txt = st_lget("pp_txt", i)
g_text_a_c(list_get(p, 0), list_get(p, 1), 16.0 * s, st_lget("pp_c", i), txt, a)
}
bind i = i + 1.0
}
# top bar
g_rect_a(x, y, w, 64.0 * s, c_rgb(8.0, 6.0, 24.0), 0.55)
g_rect_a(x, y + 64.0 * s, w, 2.0 * s, c_mix(c_rgb(255.0, 80.0, 200.0), c_rgb(80.0, 180.0, 255.0), 0.5 + 0.5 * sin(t)), 0.8)
g_text(x + 24.0 * s, y + 8.0 * s, 12.0 * s, dim, "SCORE")
g_text(x + 24.0 * s, y + 24.0 * s, 28.0 * s, ink, bk_fmt(st_get("bk_score", 0.0)))
g_text(x + 260.0 * s, y + 8.0 * s, 12.0 * s, dim, "HI-SCORE")
g_text(x + 260.0 * s, y + 28.0 * s, 20.0 * s, c_rgb(255.0, 220.0, 120.0), bk_fmt(max(st_get("bk_hi", 0.0), st_get("bk_score", 0.0))))
g_text_c(x + w * 0.5, y + 8.0 * s, 12.0 * s, dim, "STAGE " + num_str(st_get("bk_stage", 1.0)))
g_text_c(x + w * 0.5, y + 26.0 * s, 22.0 * s, ink, st_get("bk_title", ""))
# lives
bind lives = st_get("bk_lives", 0.0)
g_text_r(x + w - 24.0 * s, y + 8.0 * s, 12.0 * s, dim, "LIVES")
bind k = 0.0
while k < min(lives, 8.0) {
bind cx = x + w - 34.0 * s - k * 26.0 * s
g_disc_a(cx, y + 40.0 * s, 11.0 * s, c_rgb(120.0, 180.0, 255.0), 0.35)
g_disc(cx, y + 40.0 * s, 7.0 * s, c_rgb(240.0, 245.0, 255.0))
bind k = k + 1.0
}
if lives > 8.0 { g_text_r(x + w - 34.0 * s - 8.0 * 26.0 * s, y + 30.0 * s, 16.0 * s, ink, "x" + num_str(lives)) }
# active power-ups
bind names = ["bk_t_expand", "bk_t_laser", "bk_t_slow", "bk_t_fire", "bk_t_catch", "bk_t_shrink"]
bind ks = [0.0, 2.0, 3.0, 4.0, 5.0, 8.0]
bind fulls = [15.0, 12.0, 10.0, 8.0, 15.0, 10.0]
bind cx = x + 24.0 * s
bind k = 0.0
while k < 6.0 {
bind v = st_get(list_get(names, k), 0.0)
if v > 0.0 {
bind pk = list_get(ks, k)
bind c = bk_pu_col(pk)
g_rrect_a(cx, y + h - 64.0 * s, 132.0 * s, 40.0 * s, 8.0 * s, c_rgb(10.0, 8.0, 30.0), 0.7)
g_disc(cx + 20.0 * s, y + h - 44.0 * s, 13.0 * s, c)
g_text_c(cx + 20.0 * s, y + h - 53.0 * s, 15.0 * s, c_rgb(20.0, 20.0, 40.0), list_get(bk_pu_letters(), pk))
g_text(cx + 40.0 * s, y + h - 60.0 * s, 12.0 * s, ink, list_get(bk_pu_names(), pk))
g_rect_a(cx + 40.0 * s, y + h - 38.0 * s, 82.0 * s, 5.0 * s, c_rgb(60.0, 60.0, 90.0), 0.8)
g_rect(cx + 40.0 * s, y + h - 38.0 * s, 82.0 * s * clampf(v / list_get(fulls, k), 0.0, 1.0), 5.0 * s, c)
bind cx = cx + 142.0 * s
}
bind k = k + 1.0
}
if st_get("bk_barrier", 0.0) > 0.5 {
g_rrect_a(cx, y + h - 64.0 * s, 110.0 * s, 40.0 * s, 8.0 * s, c_rgb(10.0, 8.0, 30.0), 0.7)
g_disc(cx + 20.0 * s, y + h - 44.0 * s, 13.0 * s, bk_pu_col(6.0))
g_text_c(cx + 20.0 * s, y + h - 53.0 * s, 15.0 * s, c_rgb(20.0, 20.0, 40.0), "B")
g_text(cx + 40.0 * s, y + h - 52.0 * s, 12.0 * s, ink, "SHIELD")
}
# combo
bind combo = st_get("bk_combo", 0.0)
if combo >= 3.0 {
bind pulse = 1.0 + st_get("bk_pk", 0.0) * 0.12
g_text_r(x + w - 24.0 * s, y + h - 60.0 * s, 26.0 * s * pulse, c_mix(c_rgb(255.0, 200.0, 80.0), c_rgb(255.0, 90.0, 200.0), 0.5 + 0.5 * sin(t * 6.0)), "COMBO x" + num_str(floor((1.0 + min(combo, 16.0) * 0.25) * 100.0) / 100.0))
}
# messages
if st_get("bk_msg_t", 0.0) > 0.0 {
bind a = clampf(st_get("bk_msg_t", 0.0) * 2.0, 0.0, 1.0)
bind sz = (34.0 + (1.0 - clampf((2.2 - st_get("bk_msg_t", 0.0)) * 6.0, 0.0, 1.0)) * 20.0) * s
g_text_a_c(x + w * 0.5, y + h * 0.42, sz, c_rgb(255.0, 255.0, 255.0), st_get("bk_msg", ""), a)
}
# intro card
if st_get("bk_state", "") == "intro" {
bind a = clampf(st_get("bk_state_t", 0.0) * 2.0, 0.0, 1.0)
g_rect_a(x, y + h * 0.36, w, 120.0 * s, c_rgb(8.0, 6.0, 24.0), 0.6 * a)
g_text_a_c(x + w * 0.5, y + h * 0.36 + 14.0 * s, 16.0 * s, c_rgb(255.0, 140.0, 220.0), "STAGE " + num_str(st_get("bk_stage", 1.0)), a)
g_text_a_c(x + w * 0.5, y + h * 0.36 + 36.0 * s, 40.0 * s, c_rgb(255.0, 255.0, 255.0), st_get("bk_title", ""), a)
if floor(t * 2.0) - floor(t * 2.0 / 2.0) * 2.0 < 0.5 { g_text_a_c(x + w * 0.5, y + h * 0.36 + 90.0 * s, 14.0 * s, dim, "click or press SPACE to launch", a) }
}
# pause
if st_get("bk_paused", 0.0) > 0.5 {
g_rect_a(x, y, w, h, c_rgb(6.0, 4.0, 18.0), 0.6)
g_text_c(x + w * 0.5, y + h * 0.4, 48.0 * s, ink, "PAUSED")
g_text_c(x + w * 0.5, y + h * 0.4 + 70.0 * s, 16.0 * s, dim, "P resume Q quit to the game over screen")
}
return 0.0
}
# centred text with alpha
fn g_text_a_c(cx, y, size, c, s, a) {
if a <= 0.01 { return 0.0 }
bind w = g_text_w(size, s)
if a > 0.98 {
g_text(cx - w * 0.5 + 2.0, y + 2.0, size, c_rgb(10.0, 6.0, 30.0), s)
g_text(cx - w * 0.5, y, size, c, s)
return 0.0
}
g_text(cx - w * 0.5, y, size, c_mix(c_rgb(10.0, 8.0, 30.0), c, a), s)
return 0.0
}