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_cut.ling
# rt_cut.ling - cutscene playback: the camera / dialog / anim / audio / fx / event tracks of the
# active scene evaluated at a time. Used by the editor's preview and by the game.
#
# clip columns: track, t, d, kind, a, b, c, node, e
# camera move c = "x,y,z,yaw,pitch,zd[;x,y,z,yaw,pitch,zd]" (one pose = cut to it, two = travel, eased)
# cut c = "x,y,z,yaw,pitch,zd"
# orbit c = "tx,ty,tz,dist,pitch[,turns]" node = target (overrides tx,ty,tz)
# follow c = "ox,oy,oz,pitch" node = followed node (behind it along its yaw)
# dialog say a = speaker, b = text, c = voice file; title b = text
# anim moveto c = "x,y,z" node; vis c = "0|1" node; clip a = clip index node (model animation)
# audio play b = file, c = volume; music b = file, c = volume; stop
# fx fade_in | fade_out | letterbox | flash | shake c = strength
# event scene b = path; call a = hook name; wait; end
fn cut_reset() {
st_set("cut_fade", 0.0)
st_set("cut_box", 0.0)
st_set("cut_flash", 0.0)
st_set("cut_shake", 0.0)
st_set("cut_event", "")
st_set("cut_event_arg", "")
st_set("cut_speaker", "")
st_set("cut_text", "")
st_set("cut_last_t", 0.0 - 1.0)
st_lclear("cut_fired")
}
fn cut_fired(i) {
bind k = 0.0
while k < st_llen("cut_fired") {
if st_lget("cut_fired", k) == i { return true }
bind k = k + 1.0
}
return false
}
# evaluate every track at time t. audio_on: start sounds / music when a clip begins
fn cut_tick(t, audio_on) {
bind last = st_get("cut_last_t", 0.0 - 1.0)
if t < last { st_lclear("cut_fired") }
st_set("cut_last_t", t)
# camera
bind ci = clip_active("camera", t)
if ci >= 0.0 { cut_apply_camera(ci, t) }
# anim
bind n = clip_count()
bind i = 0.0
while i < n {
if clip_get(i, "track") == "anim" {
bind c0 = clip_get(i, "t")
bind d = max(0.001, clip_get(i, "d"))
bind ni = nd_find(clip_get(i, "node"))
if ni >= 0.0 {
bind kind = clip_get(i, "kind")
if kind == "moveto" && t >= c0 {
bind u = ease(clip_get(i, "e"), clampf((t - c0) / d, 0.0, 1.0))
bind tgt = nums_parse(clip_get(i, "c"))
if len(tgt) >= 3.0 {
bind fkey = "cutfrom_" + num_str(i)
if t - c0 < 0.05 || st_has(fkey) < 0.5 { st_set(fkey, [nd_get(ni, "px"), nd_get(ni, "py"), nd_get(ni, "pz")]) }
bind from = st_get(fkey, [0.0, 0.0, 0.0])
if len(from) >= 3.0 {
nd_set(ni, "px", lerpf(list_get(from, 0), list_get(tgt, 0), u))
nd_set(ni, "py", lerpf(list_get(from, 1), list_get(tgt, 1), u))
nd_set(ni, "pz", lerpf(list_get(from, 2), list_get(tgt, 2), u))
}
}
} else if kind == "vis" && t >= c0 && t < c0 + d {
nd_set(ni, "vis", to_number(clip_get(i, "c")))
} else if kind == "clip" && t >= c0 && t < c0 + d {
nd_set(ni, "a", to_number(clip_get(i, "a")))
nd_set(ni, "c", 1.0)
}
}
}
bind i = i + 1.0
}
# fx
bind fade = 0.0
bind box = 0.0
bind flash = 0.0
bind shake = 0.0
bind i = 0.0
while i < n {
if clip_get(i, "track") == "fx" {
bind c0 = clip_get(i, "t")
bind d = max(0.001, clip_get(i, "d"))
bind kind = clip_get(i, "kind")
bind s = to_number(clip_get(i, "c"))
if s <= 0.0 { bind s = 1.0 }
if t >= c0 && t < c0 + d {
bind u = (t - c0) / d
if kind == "fade_in" { bind fade = max(fade, (1.0 - u) * s) }
else if kind == "fade_out" { bind fade = max(fade, u * s) }
else if kind == "letterbox" { bind box = max(box, min(1.0, min(u * 4.0, (1.0 - u) * 4.0)) * s) }
else if kind == "flash" { bind flash = max(flash, (1.0 - u) * s) }
else if kind == "shake" { bind shake = max(shake, (1.0 - u) * s) }
} else if kind == "fade_out" && t >= c0 + d { bind fade = max(fade, s) }
}
bind i = i + 1.0
}
st_set("cut_fade", clampf(fade, 0.0, 1.0))
st_set("cut_box", clampf(box, 0.0, 1.0))
st_set("cut_flash", clampf(flash, 0.0, 1.0))
st_set("cut_shake", clampf(shake, 0.0, 1.0))
if shake > 0.001 {
cam_set(cam_x() + sin(t * 61.0) * shake * 0.25, cam_y() + cos(t * 47.0) * shake * 0.2, cam_z(), cam_yaw(), cam_pitch(), cam_zd())
}
# dialog
bind di = clip_active("dialog", t)
if di >= 0.0 {
st_set("cut_speaker", clip_get(di, "a"))
st_set("cut_text", clip_get(di, "b"))
st_set("cut_dialog_kind", clip_get(di, "kind"))
st_set("cut_dialog_u", (t - clip_get(di, "t")) / max(0.001, clip_get(di, "d")))
if audio_on && !cut_fired(di) {
st_lpush("cut_fired", di)
bind vo = clip_get(di, "c")
if vo != "" && plat_has_audio() {
bind sid = plat_sample_load(cut_path(vo))
if sid >= 0.0 { plat_sample_play_flat(sid, 1.0, 0.0) }
}
}
} else {
st_set("cut_speaker", "")
st_set("cut_text", "")
}
# audio
bind i = 0.0
while i < n {
if clip_get(i, "track") == "audio" {
bind c0 = clip_get(i, "t")
if t >= c0 && t < c0 + max(0.05, clip_get(i, "d")) && !cut_fired(i) {
st_lpush("cut_fired", i)
if audio_on && plat_has_audio() {
bind kind = clip_get(i, "kind")
bind vol = to_number(clip_get(i, "c"))
if vol <= 0.0 { bind vol = 1.0 }
if kind == "play" {
bind sid = plat_sample_load(cut_path(clip_get(i, "b")))
if sid >= 0.0 { plat_sample_play_flat(sid, vol, 0.0) }
} else if kind == "music" {
bind mid = plat_music_load(cut_path(clip_get(i, "b")))
if mid >= 0.0 { plat_music_volume(vol) plat_music_play(mid) st_set("cut_music", mid) st_set("game_music_cur", "") }
} else if kind == "stop" { plat_music_stop() st_set("game_music_cur", "") }
}
}
}
bind i = i + 1.0
}
# events (fire once when the playhead passes the start)
st_set("cut_event", "")
bind i = 0.0
while i < n {
if clip_get(i, "track") == "event" {
bind c0 = clip_get(i, "t")
if t >= c0 && !cut_fired(i) && (last < 0.0 || last < c0 || t - c0 < 0.1) {
st_lpush("cut_fired", i)
st_set("cut_event", clip_get(i, "kind"))
st_set("cut_event_arg", sel(clip_get(i, "kind") == "call", clip_get(i, "a"), clip_get(i, "b")))
}
}
bind i = i + 1.0
}
}
fn cut_path(p) {
if p_is_abs(p) || st_get("proj_root", "") == "" { return p }
return p_join(st_get("proj_root", ""), p)
}
fn cut_apply_camera(ci, t) {
bind kind = clip_get(ci, "kind")
bind c0 = clip_get(ci, "t")
bind d = max(0.001, clip_get(ci, "d"))
bind u = clampf((t - c0) / d, 0.0, 1.0)
bind prm = clip_get(ci, "c")
bind ni = nd_find(clip_get(ci, "node"))
if kind == "cut" || kind == "move" {
bind poses = str_split(prm, ";")
bind a = nums_parse(list_get(poses, 0))
if len(a) < 6.0 { return 0.0 }
if len(poses) > 1.0 && kind == "move" {
bind b = nums_parse(list_get(poses, 1))
if len(b) >= 6.0 {
bind e = ease(clip_get(ci, "e"), u)
cam_set(lerpf(list_get(a, 0), list_get(b, 0), e), lerpf(list_get(a, 1), list_get(b, 1), e), lerpf(list_get(a, 2), list_get(b, 2), e), lerpf(list_get(a, 3), list_get(b, 3), e), lerpf(list_get(a, 4), list_get(b, 4), e), lerpf(list_get(a, 5), list_get(b, 5), e))
return 0.0
}
}
cam_set(list_get(a, 0), list_get(a, 1), list_get(a, 2), list_get(a, 3), list_get(a, 4), list_get(a, 5))
} else if kind == "orbit" {
bind a = nums_parse(prm)
bind tx = nums_get(a, 0, 0.0)
bind ty = nums_get(a, 1, 0.0 - 1.0)
bind tz = nums_get(a, 2, 0.0)
if ni >= 0.0 {
bind wp = nd_world_pos_t(ni, t)
bind tx = list_get(wp, 0)
bind ty = list_get(wp, 1) - nd_get(ni, "sy") * 0.5
bind tz = list_get(wp, 2)
}
bind dist = nums_get(a, 3, 8.0)
bind pitch = nums_get(a, 4, 0.25)
bind turns = nums_get(a, 5, 0.35)
cam_orbit(tx, ty, tz, (t - c0) / d * 6.2831853 * turns, pitch, dist, 2.0)
} else if kind == "follow" {
bind a = nums_parse(prm)
if ni >= 0.0 {
bind wp = nd_world_pos_t(ni, t)
bind yaw = nd_world_yaw_t(ni, t)
bind ox = nums_get(a, 0, 0.0)
bind oy = nums_get(a, 1, 0.0 - 1.5)
bind oz = nums_get(a, 2, 5.0)
bind pitch = nums_get(a, 3, 0.2)
bind cx = list_get(wp, 0) - sin(yaw) * oz + cos(yaw) * ox
bind cz = list_get(wp, 2) - cos(yaw) * oz - sin(yaw) * ox
cam_look_at(cx, list_get(wp, 1) + oy, cz, list_get(wp, 0), list_get(wp, 1) - 0.5, list_get(wp, 2), 2.0)
cam_set(cam_x(), cam_y(), cam_z(), cam_yaw(), pitch, 2.0)
}
}
return 0.0
}
# letterbox, fade, flash and subtitles over a viewport
fn cut_overlay(x, y, w, h) {
bind box = st_get("cut_box", 0.0)
if box > 0.001 {
bind bh = h * 0.12 * box
g_rect(x, y, w, bh, 0.0)
g_rect(x, y + h - bh, w, bh, 0.0)
}
bind flash = st_get("cut_flash", 0.0)
if flash > 0.001 { g_rect_a(x, y, w, h, C_WHITE, flash * 0.9) }
bind fade = st_get("cut_fade", 0.0)
if fade > 0.001 { g_rect_a(x, y, w, h, 0.0, fade) }
bind text = st_get("cut_text", "")
if text != "" {
bind speaker = st_get("cut_speaker", "")
bind kind = st_get("cut_dialog_kind", "say")
if kind == "title" {
bind size = max(14.0, h * 0.06)
g_text_c(x + w * 0.5 + 2.0, y + h * 0.42 + 2.0, size, 0.0, text)
g_text_c(x + w * 0.5, y + h * 0.42, size, C_TEXT_STRONG, text)
} else {
bind size = max(11.0, h * 0.028)
bind tw = g_text_w(size, text)
bind maxw = w * 0.8
bind lines = 1.0
if tw > maxw { bind lines = ceil(tw / maxw) }
bind bw = min(tw, maxw) + 40.0
bind bh = size * 1.6 * lines + sel(speaker != "", size + 6.0, 0.0) + 16.0
bind bx = x + (w - bw) * 0.5
bind by = y + h - bh - h * 0.12 * max(box, 0.35)
g_rrect_a(bx, by, bw, bh, 6.0, C_BG_DEEP, 0.75)
g_rframe(bx, by, bw, bh, 6.0, c_mix(C_LINE, speaker_col(speaker), 0.5))
bind ty = by + 8.0
if speaker != "" {
g_text(bx + 18.0, ty, size, speaker_col(speaker), speaker)
bind ty = ty + size + 6.0
}
g_text_wrap(bx + 18.0, ty, bw - 36.0, size, C_TEXT_STRONG, text)
}
}
}
fn speaker_col(s) {
bind l = str_lower(s)
if l == "narrator" { return C_TEXT_DIM }
if l == "king" { return C_YELLOW }
if l == "queen" { return C_VIOLET }
if l == "daemon" { return C_RED }
if l == "coda" { return C_GREEN }
if str_find(l, "water") >= 0.0 { return C_BLUE }
if str_find(l, "fire") >= 0.0 { return C_ORANGE }
return C_BLUE
}