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_hud.ling
# rt_hud.ling - 2-D scenes (menus, HUDs): panels, labels, buttons, images, bars.
# Positions are in the scene's design canvas (cw x ch); hud_draw maps them to the screen.
# Anchors: tl tc tr cl cc cr bl bc br - the node's (px,py) is measured from that point of its
# parent (or the canvas), so a HUD element sticks to a corner when the window size changes.
fn anchor_fx(a) {
if a == "tc" || a == "cc" || a == "bc" { return 0.5 }
if a == "tr" || a == "cr" || a == "br" { return 1.0 }
return 0.0
}
fn anchor_fy(a) {
if a == "cl" || a == "cc" || a == "cr" { return 0.5 }
if a == "bl" || a == "bc" || a == "br" { return 1.0 }
return 0.0
}
# canvas-space rect [x, y, w, h] of a 2-D node at time t
fn hud_rect(i, t) {
bind w = nd_val(i, "w", t)
bind h = nd_val(i, "h", t)
bind px = nd_val(i, "px", t)
bind py = nd_val(i, "py", t)
bind anc = nd_get(i, "anc")
bind ox = 0.0
bind oy = 0.0
bind pw = sc_get("cw", 1280.0)
bind ph = sc_get("ch", 720.0)
bind p = nd_get(i, "par")
if p > 0.0 {
bind pi = nd_find(p)
if pi >= 0.0 {
bind pr = hud_rect(pi, t)
bind ox = list_get(pr, 0)
bind oy = list_get(pr, 1)
bind pw = list_get(pr, 2)
bind ph = list_get(pr, 3)
}
}
bind fx = anchor_fx(anc)
bind fy = anchor_fy(anc)
return [ox + fx * pw - fx * w + px, oy + fy * ph - fy * h + py, w, h]
}
# image handles cached per path
fn image_handle(path) {
if path == "" || !plat_has_images() { return 0.0 - 1.0 }
bind key = "img:" + path
if st_has(key) > 0.5 { return st_get(key, 0.0 - 1.0) }
bind full = path
if !p_is_abs(full) && st_get("proj_root", "") != "" { bind full = p_join(st_get("proj_root", ""), path) }
bind h = plat_image_load(full)
st_set(key, h)
return h
}
# extra fonts by name (assets/fonts/<name>.otf|.ttf in the project, then LingDev's own)
fn hud_font(name) {
if name == "" { return st_get("font", 0.0) }
bind key = "fnt:" + name
if st_has(key) > 0.5 { return st_get(key, 0.0) }
bind f = 0.0 - 1.0
bind base = st_get("proj_root", "")
if base != "" {
bind f = font_load(p_join(base, "assets/fonts/" + name + ".otf"))
if f < 0.0 { bind f = font_load(p_join(base, "assets/fonts/" + name + ".ttf")) }
}
if f < 0.0 { bind f = font_load(p_join(st_get("app_root", "."), "assets/fonts/" + name + ".otf")) }
if f < 0.0 { bind f = font_load(p_join(st_get("app_root", "."), "assets/fonts/" + name + ".ttf")) }
if f < 0.0 { bind f = st_get("font", 0.0) }
st_set(key, f)
return f
}
# ids of the visible buttons of the active scene, in row order (keyboard navigation)
fn hud_buttons() {
bind out = []
bind n = nd_count()
bind i = 0.0
while i < n {
if nd_get(i, "kind") == "button" && nd_get(i, "vis") > 0.5 && hud_parent_visible(i) { bind out = list_push(out, nd_id(i)) }
bind i = i + 1.0
}
return out
}
# game variables shown by bound widgets (tag "health", "score", ...)
fn hud_bound_value(i, d) {
bind tag = nd_get(i, "tag")
if tag == "" { return d }
if st_has("gv_" + tag) > 0.5 { return st_get("gv_" + tag, d) }
return d
}
# draw the active namespace's 2-D nodes mapped by (ox, oy, scale). interactive: buttons react.
# returns the action of a clicked button ("" when none)
fn hud_draw(t, ox, oy, scale, interactive) {
bind n = nd_count()
bind action = ""
bind i = 0.0
while i < n {
bind kind = nd_get(i, "kind")
if nd_get(i, "vis") > 0.5 && nd_is_2d(kind) && hud_parent_visible(i) {
bind r = hud_rect(i, t)
bind x = ox + list_get(r, 0) * scale
bind y = oy + list_get(r, 1) * scale
bind w = list_get(r, 2) * scale
bind h = list_get(r, 3) * scale
bind c = nd_get(i, "col")
bind a = nd_val(i, "a", t)
if kind == "panel" {
g_rrect_a(x, y, w, h, 6.0 * scale, c, clampf(a, 0.0, 1.0))
g_rframe(x, y, w, h, 6.0 * scale, c_mix(c, C_WHITE, 0.25))
} else if kind == "label" {
bind txt = nd_get(i, "txt")
bind tag = nd_get(i, "tag")
if tag != "" && st_has("gv_" + tag) > 0.5 { bind txt = str_replace(txt, "{}", num_str(floor(st_get("gv_" + tag, 0.0)))) }
bind txt = hud_subst(txt)
bind size = max(6.0, a * scale)
bind anc = nd_get(i, "anc")
bind fx = anchor_fx(anc)
bind shown = true
if nd_ex(i, "blink", "0") == "1" { bind shown = floor(time_now() * 2.0) % 2.0 < 0.5 }
if shown { hud_text_wrap(hud_font(nd_ex(i, "font", "")), x, y, w, size, c, txt, fx) }
} else if kind == "button" {
bind hot = interactive && u_in(x, y, w, h)
bind size = max(6.0, a * scale)
bind selected = st_get("menu_sel", 0.0) == nd_id(i)
if hot && interactive && (u_dx() != 0.0 || u_dy() != 0.0) { st_set("menu_sel", nd_id(i)) bind selected = true }
if nd_ex(i, "style", "") == "text" {
# the original's menu rows: plain text, the selected row brighter with an ink cursor
bind fnt = hud_font(nd_ex(i, "font", ""))
bind txt = nd_get(i, "txt")
bind tw = font_width(fnt, size, txt)
bind hi = nd_ex(i, "hi", "")
bind tc = sel(selected || hot, sel(hi == "", C_WHITE, to_number(hi)), c)
bind tx = x + (w - tw) * 0.5
px_text(fnt, tx, y + (h - size) * 0.5 - 1.0, size, tc, txt)
if selected || hot {
bind pulse = 0.6 + 0.4 * sin(time_now() * 6.0)
g_diamond(tx - size * 0.9, y + h * 0.5, size * 0.28 * pulse + 2.0, tc)
px_hline(tx - size * 0.6, y + h * 0.5, size * 0.4, tc, 0.7)
}
} else {
px_rect(x, y, w, h, c_mix(C_BG_PANEL, 0.0, 0.35))
ds_hatch(x + 1.0, y + 1.0, w - 2.0, h - 2.0, c, 45.0, 5.0, sel(hot || selected, 0.35, 0.2))
px_outline(x, y, w, h, c, 0.95)
ds_brackets(x, y, w, h, min(6.0, h * 0.3), c)
g_text_c(x + w * 0.5, y + (h - size) * 0.5 - 1.0, size, sel(selected, C_WHITE, C_TEXT_STRONG), nd_get(i, "txt"))
}
if hot && u_pressed() { bind action = nd_get(i, "scr") st_set("hud_action_node", nd_id(i)) }
if interactive && st_get("hud_key_fire", 0.0) > 0.5 && selected { bind action = nd_get(i, "scr") st_set("hud_action_node", nd_id(i)) st_set("hud_key_fire", 0.0) }
} else if kind == "image" {
bind hnd = image_handle(nd_get(i, "src"))
if hnd >= 0.0 { plat_image_draw(hnd, x, y, w, h) }
else {
g_frame(x, y, w, h, c_mix(c, C_BG_DEEP, 0.5))
g_line(x, y, x + w, y + h, c_mix(c, C_BG_DEEP, 0.5))
g_line(x + w, y, x, y + h, c_mix(c, C_BG_DEEP, 0.5))
g_text_fit(x + 4.0, y + 4.0, w - 8.0, 10.0 * scale, C_TEXT_DIM, p_base(nd_get(i, "src")))
}
} else if kind == "bar" {
bind v = hud_bound_value(i, a)
bind mx = max(0.001, nd_val(i, "b", t))
g_rrect(x, y, w, h, 3.0 * scale, C_BG_DEEP)
g_rrect(x + 1.0, y + 1.0, max(0.0, (w - 2.0) * clampf(v / mx, 0.0, 1.0)), h - 2.0, 2.0 * scale, c)
g_rframe(x, y, w, h, 3.0 * scale, c_mix(c, C_WHITE, 0.3))
}
}
bind i = i + 1.0
}
return action
}
fn hud_parent_visible(i) {
bind p = nd_get(i, "par")
bind guard = 0.0
while p > 0.0 && guard < 64.0 {
bind pi = nd_find(p)
if pi < 0.0 { return true }
if nd_get(pi, "vis") < 0.5 { return false }
bind p = nd_get(pi, "par")
bind guard = guard + 1.0
}
return true
}
# fit the design canvas into (x, y, w, h); returns [ox, oy, scale]
fn hud_fit(x, y, w, h) {
bind cw = sc_get("cw", 1280.0)
bind ch = sc_get("ch", 720.0)
bind s = min(w / cw, h / ch)
return [x + (w - cw * s) * 0.5, y + (h - ch * s) * 0.5, s]
}
# "{$key}" in a label shows the stored string gs_<key> (set by store:key=value actions and the game)
fn hud_subst(txt) {
bind out = txt
bind guard = 0.0
bind at = str_find(out, "{$")
while at >= 0.0 && guard < 8.0 {
bind rest = str_slice(out, at + 2.0, str_len(out) - at - 2.0)
bind close = str_find(rest, "}")
if close < 0.0 { return out }
bind key = str_slice(rest, 0.0, close)
bind out = str_slice(out, 0.0, at) + st_get("gs_" + key, "") + str_slice(rest, close + 1.0, str_len(rest) - close - 1.0)
bind at = str_find(out, "{$")
bind guard = guard + 1.0
}
return out
}
# text in a box `w` wide: words wrap onto new lines, each line aligned by fx (0 left, 0.5 centre, 1 right)
fn hud_text_wrap(fnt, x, y, w, size, c, txt, fx) {
if font_width(fnt, size, txt) <= w + 1.0 {
px_text(fnt, x + (w - font_width(fnt, size, txt)) * fx, y, size, c, txt)
return 0.0
}
bind words = str_split(txt, " ")
bind line = ""
bind ly = y
bind i = 0.0
while i < len(words) {
bind wd = list_get(words, i)
bind trial = sel(line == "", wd, line + " " + wd)
if line != "" && font_width(fnt, size, trial) > w {
px_text(fnt, x + (w - font_width(fnt, size, line)) * fx, ly, size, c, line)
bind ly = ly + size * 1.35
bind line = wd
} else { bind line = trial }
bind i = i + 1.0
}
if line != "" { px_text(fnt, x + (w - font_width(fnt, size, line)) * fx, ly, size, c, line) }
return 0.0
}