灵符lingfu

lingdev

↓ 0 下载

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
关键词 game-engine, editor, 3d, 2d, cutscene, timeline, ling, lingos, raspberry-pi
lingfu install lingdev
许可证Apache-2.0 OR MIT
作者ling-lin
README代码 / 文件版本依赖关系图依赖此包的项目安全构建设置

代码 / 文件 (136)

ling/timeline.ling
# timeline.ling - the Flash-style timeline: layers (nodes with keys + the selection), a ruler in
# seconds and frames, keyframe diamonds, a draggable playhead and end marker. Right-click a key
# for its easing; drag keys in time; double-click a property row to add a key there.

fn tl_px(t, gx, gw) { return gx + (t - st_get("tl_off", 0.0)) * st_get("tl_zoom", 60.0) }
fn tl_time(px, gx) { return st_get("tl_off", 0.0) + (px - gx) / st_get("tl_zoom", 60.0) }
fn tl_snap_time(t) {
    bind fps = max(1.0, sc_get("fps", 30.0))
    return max(0.0, round(t * fps) / fps)
}

# the layer list: nodes with keys first, then selected nodes without keys
fn tl_layers() {
    bind out = []
    bind n = nd_count()
    bind i = 0.0
    while i < n {
        if key_node_has(nd_id(i)) { bind out = list_push(out, nd_id(i)) }
        bind i = i + 1.0
    }
    bind ids = sel_ids()
    bind j = 0.0
    while j < len(ids) {
        if !l_has(out, list_get(ids, j)) { bind out = list_push(out, list_get(ids, j)) }
        bind j = j + 1.0
    }
    return out
}

fn timeline_panel(x, y, w, h) {
    bind lw = 190.0
    bind rulerh = 26.0
    bind rh = 22.0
    bind gx = x + lw
    bind gw = w - lw - 12.0
    bind t = st_get("tl_t", 0.0)
    bind ln = max(0.1, sc_get("len", 10.0))
    bind fps = max(1.0, sc_get("fps", 30.0))
    bind zoom = st_get("tl_zoom", 60.0)
    # header row (left)
    g_rect(x, y, lw, rulerh, C_BG_PANEL)
    ds_word(x + 8.0, y + 7.0, 10.0, "TIMELINE", ds_col("asm"), true)
    if u_hot(x + lw - 48.0, y + 3.0, 20.0, 20.0) { u_tip("Key the selected nodes at the playhead (K)") }
    if u_icon_button("tl_keybtn", x + lw - 48.0, y + 3.0, 20.0, 20.0, "key", 11.0, C_RED, false) { do_action("tl.key") }
    if u_hot(x + lw - 26.0, y + 3.0, 20.0, 20.0) { u_tip("Auto key") }
    if u_icon_button("tl_ak", x + lw - 26.0, y + 3.0, 20.0, 20.0, "rec", 9.0, sel(st_get("autokey", 0.0) > 0.5, C_RED, C_TEXT_DIM), st_get("autokey", 0.0) > 0.5) { do_action("tl.autokey") }
    # ruler
    g_rect(gx, y, gw + 12.0, rulerh, C_RULER)
    u_hsep(x, y + rulerh - 1.0, w)
    bind ruler_hot = u_hot(gx, y, gw, rulerh)
    # wheel: scroll / zoom
    if (ruler_hot || u_hot(gx, y, gw, h)) && u_scroll() != 0.0 && st_get("wheel_used", 0.0) < 0.5 {
        if u_ctrl() {
            bind mt = tl_time(u_mx(), gx)
            bind zoom = clampf(zoom * pow(1.15, u_scroll()), 8.0, 2000.0)
            st_set("tl_zoom", zoom)
            st_set("tl_off", max(0.0, mt - (u_mx() - gx) / zoom))
        } else if !u_shift() {
            st_set("tl_off", max(0.0, st_get("tl_off", 0.0) - u_scroll() * 30.0 / zoom))
        }
        st_set("wheel_used", 1.0)
    }
    # ruler ticks
    bind step = 1.0
    while step * zoom < 60.0 { bind step = step * 2.0 }
    while step * zoom > 240.0 && step > 1.0 / fps { bind step = step / 2.0 }
    bind t0 = floor(st_get("tl_off", 0.0) / step) * step
    bind tt = t0
    u_clip(gx, y, gw, h)
    while tl_px(tt, gx, gw) < gx + gw {
        bind px = tl_px(tt, gx, gw)
        if px >= gx {
            g_rect(px, y + rulerh - 8.0, 1.0, 8.0, C_TEXT_DIM)
            g_text(px + 3.0, y + 4.0, 10.0, C_TEXT_DIM, sel(step >= 1.0, num_str(tt) + "s", fmt2(tt)))
            g_rect(px, y + rulerh, 1.0, h - rulerh, C_GRID_MAJ)
        }
        # frame ticks when wide enough
        if zoom / fps > 6.0 {
            bind f = 1.0
            while f < step * fps {
                bind fx = tl_px(tt + f / fps, gx, gw)
                if fx > gx { g_rect(fx, y + rulerh - 4.0, 1.0, 4.0, C_LINE) }
                bind f = f + 1.0
            }
        }
        bind tt = tt + step
    }
    # end marker
    bind ex = tl_px(ln, gx, gw)
    g_rect(ex, y, 2.0, h, C_LOOP_EDGE)
    g_rect_a(ex, y + rulerh, gw, h - rulerh, 0.0, 0.35)
    bind end_hot = u_hot(ex - 6.0, y, 12.0, rulerh)
    if end_hot { u_tip("Scene length " + fmt1(ln) + " s (drag)") }
    if end_hot && u_pressed() { u_set_active("tl_end")  undo_push() }
    if u_active() == "tl_end" {
        if u_down() { sc_set("len", max(0.2, tl_snap_time(tl_time(u_mx(), gx)))) } else { u_set_active("") }
    }
    # playhead scrub from the ruler
    if ruler_hot && u_pressed() && !end_hot { u_set_active("tl_scrub") }
    if u_active() == "tl_scrub" {
        if u_down() { st_set("tl_t", clampf(tl_snap_time(tl_time(u_mx(), gx)), 0.0, ln)) } else { u_set_active("") }
    }
    # layers
    bind layers = tl_layers()
    bind top = y + rulerh
    bind rows = []
    bind i = 0.0
    while i < len(layers) {
        bind id = list_get(layers, i)
        bind rows = list_push(rows, "n:" + num_str(id))
        if st_get("tlx:" + num_str(id), 0.0) > 0.5 {
            bind props = key_props_of(id)
            bind p = 0.0
            while p < len(props) {
                bind rows = list_push(rows, "p:" + num_str(id) + ":" + list_get(props, p))
                bind p = p + 1.0
            }
        }
        bind i = i + 1.0
    }
    bind total = len(rows) * rh + 8.0
    bind vh = h - rulerh
    bind off = u_scroll_y("tl_sb", x, top, w, vh, total, st_get("tl_scroll", 0.0))
    st_set("tl_scroll", off)
    u_clip(x, top, w - 12.0, vh)
    bind r = 0.0
    while r < len(rows) {
        bind rec = list_get(rows, r)
        bind parts = str_split(rec, ":")
        bind id = to_number(list_get(parts, 1))
        bind ni = nd_find(id)
        bind ry = top + 2.0 + r * rh - off
        if ni >= 0.0 && ry + rh >= top && ry <= top + vh {
            bind is_prop = list_get(parts, 0) == "p"
            bind prop = sel(is_prop, list_get(parts, 2), "")
            bind selected = sel_has(id)
            # left cell
            g_rect(x, ry, lw, rh, sel(selected && !is_prop, C_SEL_ROW, sel(is_prop, C_BG_DEEP, C_BG_SIDE)))
            u_hsep(x, ry + rh - 1.0, lw)
            if !is_prop {
                bind open = st_get("tlx:" + num_str(id), 0.0) > 0.5
                icon_draw(sel(open, "chev_d", "chev_r"), x + 10.0, ry + rh * 0.5, 8.0, C_TEXT_DIM)
                if u_hot(x, ry, 18.0, rh) && u_pressed() { st_set("tlx:" + num_str(id), sel(open, 0.0, 1.0)) }
                icon_draw(kind_icon(nd_get(ni, "kind")), x + 26.0, ry + rh * 0.5, 11.0, kind_col(nd_get(ni, "kind")))
                g_text_fit(x + 38.0, ry + 5.0, lw - 46.0, 11.0, sel(selected, C_TEXT_STRONG, C_TEXT), nd_get(ni, "name"))
                if u_hot(x + 18.0, ry, lw - 18.0, rh) && u_pressed() { sel_set(id) }
            } else {
                g_text(x + 40.0, ry + 5.0, 11.0, C_TEXT_DIM, prop)
                g_text_r(x + lw - 8.0, ry + 5.0, 10.0, C_TEXT_DIM, fmt2(nd_val(ni, prop, t)))
            }
            # lane
            g_rect(gx, ry, gw, rh, sel(is_prop, 0.0, C_BG_DEEP))
            u_hsep(gx, ry + rh - 1.0, gw)
            bind lane_hot = u_hot(gx, ry, gw, rh)
            if is_prop {
                bind ks = key_list(id, prop)
                bind k = 0.0
                while k < len(ks) {
                    bind ki = list_get(ks, k)
                    bind kt = key_get(ki, "t")
                    bind kx = tl_px(kt, gx, gw)
                    if k > 0.0 {
                        bind prev = list_get(ks, k - 1.0)
                        px_line(tl_px(key_get(prev, "t"), gx, gw), ry + rh * 0.5, kx, ry + rh * 0.5, c_pal(l_index(nd_props_anim(), prop)), 0.5)
                    }
                    bind khot = lane_hot && abs(u_mx() - kx) < 6.0
                    bind ksel = st_get("key_sel", 0.0 - 1.0) == ki
                    g_diamond(kx, ry + rh * 0.5, sel(khot || ksel, 6.0, 5.0), sel(ksel, C_WHITE, sel(abs(kt - t) < 0.0005, C_YELLOW, c_pal(l_index(nd_props_anim(), prop)))))
                    if khot {
                        u_tip(prop + " = " + fmt2(key_get(ki, "v")) + " @ " + fmt2(kt) + " s  (" + key_get(ki, "e") + ")")
                        if u_pressed() { st_set("key_sel", ki)  u_set_active("tl_keydrag")  st_set("kd_idx", ki)  st_set("kd_t0", kt)  st_set("kd_x0", u_mx())  undo_push()  st_set("tl_t", kt) }
                        if u_rpressed() { st_set("key_sel", ki)  ctx_open("key", num_str(ki), u_mx(), u_my()) }
                    }
                    bind k = k + 1.0
                }
                if lane_hot && u_double() {
                    bind nt = tl_snap_time(tl_time(u_mx(), gx))
                    undo_push()
                    key_add(id, prop, nt, nd_val(ni, prop, nt), "cubic_in_out")
                }
            } else {
                bind times = key_times_of(id)
                bind k = 0.0
                while k < len(times) {
                    bind kt = list_get(times, k)
                    bind kx = tl_px(kt, gx, gw)
                    bind khot = lane_hot && abs(u_mx() - kx) < 6.0
                    g_diamond(kx, ry + rh * 0.5, sel(khot, 6.0, 5.0), sel(abs(kt - t) < 0.0005, C_YELLOW, sel(selected, C_TEXT_STRONG, C_TEXT_DIM)))
                    if khot {
                        u_tip("keys @ " + fmt2(kt) + " s  (drag to move; expand the layer for easing)")
                        if u_pressed() { u_set_active("tl_timedrag")  st_set("kd_id", id)  st_set("kd_t0", kt)  st_set("kd_x0", u_mx())  undo_push()  st_set("tl_t", kt)  sel_set(id) }
                    }
                    bind k = k + 1.0
                }
                if lane_hot && u_pressed() && u_active() == "" && !u_double() { st_set("tl_t", clampf(tl_snap_time(tl_time(u_mx(), gx)), 0.0, ln))  u_set_active("tl_scrub") }
            }
        }
        bind r = r + 1.0
    }
    if len(rows) < 0.5 { g_text(gx + 12.0, top + 10.0, 12.0, C_TEXT_DIM, "Select a node and press K (or the diamond in the Inspector) to add keyframes.") }
    u_unclip()
    # key drags
    if u_active() == "tl_keydrag" {
        bind ki = st_get("kd_idx", 0.0 - 1.0)
        if u_down() && ki >= 0.0 && ki < key_count() {
            bind nt = tl_snap_time(st_get("kd_t0", 0.0) + (u_mx() - st_get("kd_x0", 0.0)) / zoom)
            key_set(ki, "t", nt)
            st_set("tl_t", nt)
        } else { u_set_active("") }
    }
    if u_active() == "tl_timedrag" {
        bind id = st_get("kd_id", 0.0)
        if u_down() {
            bind nt = tl_snap_time(st_get("kd_t0", 0.0) + (u_mx() - st_get("kd_x0", 0.0)) / zoom)
            bind cur = st_get("kd_cur", st_get("kd_t0", 0.0))
            if abs(nt - cur) > 0.0001 {
                bind k = 0.0
                while k < key_count() {
                    if key_get(k, "node") == id && abs(key_get(k, "t") - cur) < 0.0005 { key_set(k, "t", nt) }
                    bind k = k + 1.0
                }
                st_set("kd_cur", nt)
                st_set("tl_t", nt)
            }
        } else { u_set_active("")  st_set("kd_cur", 0.0 - 1.0) }
        if u_pressed() { st_set("kd_cur", st_get("kd_t0", 0.0)) }
    }
    # playhead
    u_clip(gx, y, gw, h)
    bind px = tl_px(t, gx, gw)
    g_rect(px, y, 1.0, h, C_PLAYHEAD)
    g_tri(px - 6.0, y, px + 6.0, y, px, y + 8.0, C_PLAYHEAD)
    g_text(px + 4.0, y + rulerh - 12.0, 9.0, C_PLAYHEAD, "f" + num_str(floor(t * fps + 0.0001)))
    u_unclip()
    u_vsep(gx, y, h)
}

← 返回索引