灵符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_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
}

← Back to index