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).
关键词 game-engine, editor, 3d, 2d, cutscene, timeline, ling, lingos, raspberry-pi
lingfu install lingdev代码 / 文件 (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)
}