灵符lingfu

lingdev

↓ 0 downloads

LingDev: 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).

Windows · x86-64Linux · x86-64LingOS · x86-64LingOS · Pi 5 ARM64Web · WASM
Keywords game-engine, editor, 3d, 2d, cutscene, timeline, ling, lingos, raspberry-pi
lingfu install lingdev
LicenseApache-2.0 OR MIT
Authorsling-lin
READMECode / FilesVersionsDependency GraphDependentsSecurityBuildsSettings

Code / 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
}

← Back to index