灵符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/project.ling
# project.ling - projects (lingdev.toml), editor settings, recent list, selection, undo, scenes on disk.

# ── settings (settings.txt next to LingDev) ─────────────────────────────────
fn settings_path() { return p_join(st_get("app_root", "."), "settings.txt") }
fn settings_load() {
    st_lclear("recent")
    bind t = plat_read(settings_path())
    if t == "" { return 0.0 }
    bind rec = cfg_all(t, "recent")
    bind i = 0.0
    while i < len(rec) && i < 10.0 {
        st_lpush("recent", list_get(rec, i))
        bind i = i + 1.0
    }
    st_set("native_dialogs", to_number(cfg_get(t, "native_dialogs", "0")))
    st_set("theme_black", to_number(cfg_get(t, "theme_black", "0")))
    st_set("load_last", to_number(cfg_get(t, "load_last", "1")))
    st_set("ui_sounds", to_number(cfg_get(t, "ui_sounds", "0")))
    st_set("grid_step", to_number(cfg_get(t, "grid_step", "1")))
    st_set("orbit_speed", to_number(cfg_get(t, "orbit_speed", "1")))
    st_set("autosave", to_number(cfg_get(t, "autosave", "3")))
    st_set("ling_exe", cfg_get(t, "ling_exe", ""))
    st_set("left_w", to_number(cfg_get(t, "left_w", "260")))
    st_set("insp_w", to_number(cfg_get(t, "insp_w", "300")))
    st_set("dock_h", to_number(cfg_get(t, "dock_h", "240")))
    recent_menu_rebuild()
    return 0.0
}
fn settings_save() {
    if !plat_can_write() { return 0.0 }
    bind s = "# LingDev settings\n"
    bind i = 0.0
    while i < st_llen("recent") {
        bind s = s + "recent=" + st_lget("recent", i) + "\n"
        bind i = i + 1.0
    }
    bind s = s + "native_dialogs=" + num_str(st_get("native_dialogs", 0.0)) + "\n"
    bind s = s + "theme_black=" + num_str(st_get("theme_black", 0.0)) + "\n"
    bind s = s + "load_last=" + num_str(st_get("load_last", 1.0)) + "\n"
    bind s = s + "ui_sounds=" + num_str(st_get("ui_sounds", 0.0)) + "\n"
    bind s = s + "grid_step=" + num_str(st_get("grid_step", 1.0)) + "\n"
    bind s = s + "orbit_speed=" + num_str(st_get("orbit_speed", 1.0)) + "\n"
    bind s = s + "autosave=" + num_str(st_get("autosave", 3.0)) + "\n"
    bind s = s + "ling_exe=" + st_get("ling_exe", "") + "\n"
    bind s = s + "left_w=" + num_str(st_get("left_w", 260.0)) + "\n"
    bind s = s + "insp_w=" + num_str(st_get("insp_w", 300.0)) + "\n"
    bind s = s + "dock_h=" + num_str(st_get("dock_h", 240.0)) + "\n"
    plat_write(settings_path(), s)
    return 0.0
}
fn recent_add(path) {
    bind keep = [path]
    bind i = 0.0
    while i < st_llen("recent") && len(keep) < 10.0 {
        bind r = st_lget("recent", i)
        if r != path { bind keep = list_push(keep, r) }
        bind i = i + 1.0
    }
    sl_from_list("recent", keep)
    recent_menu_rebuild()
    settings_save()
}
fn recent_menu_rebuild() {
    bind items = []
    bind i = 0.0
    while i < st_llen("recent") {
        bind r = st_lget("recent", i)
        bind items = list_push(items, p_base(p_dir(r)) + "|project.recent:" + num_str(i) + "||folder")
        bind i = i + 1.0
    }
    if len(items) < 0.5 { bind items = ["(empty)|none||"] }
    st_set("recent_menu", items)
}

# ── selection ────────────────────────────────────────────────────────────────
fn sel_main() {
    if st_llen("sel_ids") < 0.5 { return 0.0 }
    return st_lget("sel_ids", 0.0)
}
fn sel_count() { return st_llen("sel_ids") }
fn sel_has(id) {
    bind i = 0.0
    while i < st_llen("sel_ids") {
        if st_lget("sel_ids", i) == id { return true }
        bind i = i + 1.0
    }
    return false
}
fn sel_set(id) {
    st_lclear("sel_ids")
    if id > 0.0 { st_lpush("sel_ids", id) }
}
fn sel_add(id) {
    if id > 0.0 && !sel_has(id) { st_lpush("sel_ids", id) }
}
fn sel_toggle(id) {
    if sel_has(id) {
        bind i = 0.0
        while i < st_llen("sel_ids") {
            if st_lget("sel_ids", i) == id { st_ldel("sel_ids", i) }
            bind i = i + 1.0
        }
    } else { sel_add(id) }
}
fn sel_clear() { st_lclear("sel_ids") }
fn sel_ids() { return sl_to_list("sel_ids") }
# drop ids that no longer exist
fn sel_prune() {
    bind i = st_llen("sel_ids") - 1.0
    while i >= 0.0 {
        if nd_find(st_lget("sel_ids", i)) < 0.0 { st_ldel("sel_ids", i) }
        bind i = i - 1.0
    }
}
fn sel_index() {
    bind id = sel_main()
    if id <= 0.0 { return 0.0 - 1.0 }
    return nd_find(id)
}

# ── undo / redo (whole-scene snapshots) ──────────────────────────────────────
fn undo_push() {
    st_lpush("undo", sc_serialize())
    while st_llen("undo") > 60.0 { st_ldel("undo", 0.0) }
    st_lclear("redo")
    mark_dirty()
}
fn undo_do() {
    bind n = st_llen("undo")
    if n < 0.5 { status("Nothing to undo")  return 0.0 }
    st_lpush("redo", sc_serialize())
    bind path = sc_get("path", "")
    sc_parse(st_lget("undo", n - 1.0))
    sc_set("path", path)
    st_ldel("undo", n - 1.0)
    sel_prune()
    mark_dirty()
    status("Undo")
    return 0.0
}
fn redo_do() {
    bind n = st_llen("redo")
    if n < 0.5 { status("Nothing to redo")  return 0.0 }
    st_lpush("undo", sc_serialize())
    bind path = sc_get("path", "")
    sc_parse(st_lget("redo", n - 1.0))
    sc_set("path", path)
    st_ldel("redo", n - 1.0)
    sel_prune()
    mark_dirty()
    status("Redo")
    return 0.0
}
fn mark_dirty() { st_set("dirty", 1.0) }
fn status(msg) { st_set("status", msg) }

# ── project file (lingdev.toml: key=value lines) ─────────────────────────────
fn proj_root() { return st_get("proj_root", "") }
fn proj_abs(rel) {
    if rel == "" { return "" }
    if p_is_abs(rel) { return p_norm(rel) }
    return p_join(proj_root(), rel)
}
fn proj_rel(path) {
    if proj_root() == "" { return p_norm(path) }
    return p_rel(proj_root(), path)
}
fn proj_file() { return p_join(proj_root(), "lingdev.toml") }

fn proj_close() {
    st_set("proj_root", "")
    st_set("proj_name", "")
    st_set("proj_start", "")
    st_set("proj_title", "")
    st_set("proj_w", 1280.0)
    st_set("proj_h", 720.0)
    st_set("proj_fps", 60.0)
    st_set("proj_source", "")
    st_set("proj_icon", "")
    st_set("proj_version", "0.1.0")
    st_lclear("proj_scenes")
}
fn proj_load(file) {
    bind t = plat_read(file)
    if t == "" { return false }
    proj_close()
    bind root = p_dir(p_norm(file))
    if !p_is_abs(root) { bind root = p_join(p_norm(fs_cwd()), root) }
    st_set("proj_root", root)
    st_set("proj_name", cfg_get(t, "name", p_base(p_dir(file))))
    st_set("proj_start", cfg_get(t, "start", ""))
    st_set("proj_title", cfg_get(t, "title", st_get("proj_name", "")))
    bind win = str_split(cfg_get(t, "window", "1280x720"), "x")
    st_set("proj_w", to_number(list_get(win, 0)))
    st_set("proj_h", sel(len(win) > 1.0, to_number(list_get(win, 1)), 720.0))
    st_set("proj_fps", to_number(cfg_get(t, "fps", "60")))
    st_set("proj_source", cfg_get(t, "source_root", ""))
    st_set("proj_icon", cfg_get(t, "icon", ""))
    st_set("proj_version", cfg_get(t, "version", "0.1.0"))
    st_set("proj_author", cfg_get(t, "author", ""))
    sl_from_list("proj_scenes", cfg_all(t, "scene"))
    recent_add(p_norm(file))
    status("Opened project " + st_get("proj_name", ""))
    return true
}
fn proj_save() {
    if proj_root() == "" { return false }
    bind s = "# LingDev project\n"
    bind s = s + "name=" + st_get("proj_name", "") + "\n"
    bind s = s + "title=" + st_get("proj_title", "") + "\n"
    bind s = s + "version=" + st_get("proj_version", "0.1.0") + "\n"
    bind s = s + "author=" + st_get("proj_author", "") + "\n"
    bind s = s + "start=" + st_get("proj_start", "") + "\n"
    bind s = s + "window=" + num_str(st_get("proj_w", 1280.0)) + "x" + num_str(st_get("proj_h", 720.0)) + "\n"
    bind s = s + "fps=" + num_str(st_get("proj_fps", 60.0)) + "\n"
    bind s = s + "icon=" + st_get("proj_icon", "") + "\n"
    bind s = s + "source_root=" + st_get("proj_source", "") + "\n"
    bind s = s + "assets=assets\nscripts=scripts\n"
    bind i = 0.0
    while i < st_llen("proj_scenes") {
        bind s = s + "scene=" + st_lget("proj_scenes", i) + "\n"
        bind i = i + 1.0
    }
    return plat_write(proj_file(), s)
}
fn proj_add_scene(rel) {
    bind i = 0.0
    while i < st_llen("proj_scenes") {
        if st_lget("proj_scenes", i) == rel { return 0.0 }
        bind i = i + 1.0
    }
    st_lpush("proj_scenes", rel)
    if st_get("proj_start", "") == "" { st_set("proj_start", rel) }
    proj_save()
    return 0.0
}
fn proj_remove_scene(rel) {
    bind i = st_llen("proj_scenes") - 1.0
    while i >= 0.0 {
        if st_lget("proj_scenes", i) == rel { st_ldel("proj_scenes", i) }
        bind i = i - 1.0
    }
    proj_save()
}

# ── scenes on disk ───────────────────────────────────────────────────────────
fn open_scene(path) {
    if path == "" { return false }
    if !sc_load(path) {
        status("Could not read " + path)
        return false
    }
    st_lclear("undo")
    st_lclear("redo")
    sel_clear()
    st_set("dirty", 0.0)
    st_set("tl_t", 0.0)
    st_set("tl_playing", 0.0)
    view_restore_camera()
    if proj_root() != "" { proj_add_scene(proj_rel(path)) }
    assets_mark_stale()
    status("Opened " + p_base(path))
    return true
}
fn save_scene(path) {
    if path == "" { return false }
    view_store_camera()
    if !sc_save(path) {
        status("Could not write " + path + sel(plat_can_write(), "", " (this runtime is read-only)"))
        return false
    }
    st_set("dirty", 0.0)
    if proj_root() != "" { proj_add_scene(proj_rel(path)) }
    assets_mark_stale()
    status("Saved " + p_base(path))
    return true
}
fn new_scene(kind) {
    sc_new(sel(kind == "2d", "Menu", sel(kind == "cutscene", "Cutscene", "Level")), kind)
    if kind == "3d" || kind == "cutscene" {
        bind f = nd_add("plane", "Ground", 0.0)
        nd_set(f, "tag", "solid")
        bind l = nd_add("light", "Sun", 0.0)
        nd_set(l, "px", 4.0)
        nd_set(l, "py", 0.0 - 8.0)
        nd_set(l, "pz", 0.0 - 4.0)
        bind c = nd_add("camera", "Camera", 0.0)
        if kind == "3d" {
            bind s = nd_add("spawn", "Spawn", 0.0)
            nd_set(s, "py", 0.0)
        } else {
            sc_set("len", 20.0)
            clip_add("fx", 0.0, 1.5, "fade_in", "", "", "", 0.0, "linear")
            clip_add("camera", 0.0, 8.0, "orbit", "", "", "0,-1,0,6,0.25", 0.0, "linear")
            clip_add("dialog", 1.0, 4.0, "say", "Narrator", "A new cutscene.", "", 0.0, "linear")
        }
    } else {
        bind p = nd_add("panel", "Panel", 0.0)
        nd_set(p, "px", 440.0)
        nd_set(p, "py", 180.0)
        nd_set(p, "w", 400.0)
        nd_set(p, "h", 360.0)
        bind t = nd_add("label", "Title", nd_id(p))
        nd_set(t, "px", 40.0)
        nd_set(t, "py", 30.0)
        nd_set(t, "w", 320.0)
        nd_set(t, "a", 28.0)
        nd_set(t, "txt", "My Game")
        bind b = nd_add("button", "Play", nd_id(p))
        nd_set(b, "px", 120.0)
        nd_set(b, "py", 140.0)
        nd_set(b, "txt", "Play")
        nd_set(b, "scr", "goto:scenes/level.lscene")
        bind q = nd_add("button", "Quit", nd_id(p))
        nd_set(q, "px", 120.0)
        nd_set(q, "py", 200.0)
        nd_set(q, "txt", "Quit")
        nd_set(q, "scr", "quit")
    }
    st_lclear("undo")
    st_lclear("redo")
    sel_clear()
    st_set("dirty", 1.0)
    st_set("tl_t", 0.0)
    view_reset_camera()
    status("New " + kind + " scene (unsaved)")
}
fn view_store_camera() {
    sc_set("camx", st_get("ed_tx", 0.0))
    sc_set("camy", st_get("ed_ty", 0.0))
    sc_set("camz", st_get("ed_tz", 0.0))
    sc_set("camyaw", st_get("ed_yaw", 0.0))
    sc_set("campitch", st_get("ed_pitch", 0.3))
    sc_set("camzd", st_get("ed_dist", 14.0))
}
fn view_restore_camera() {
    st_set("ed_tx", sc_get("camx", 0.0))
    st_set("ed_ty", sc_get("camy", 0.0))
    st_set("ed_tz", sc_get("camz", 0.0))
    st_set("ed_yaw", sc_get("camyaw", 0.0))
    st_set("ed_pitch", sc_get("campitch", 0.3))
    st_set("ed_dist", max(1.0, sc_get("camzd", 14.0)))
    st_set("c2_zoom", 1.0)
    st_set("c2_ox", 0.0)
    st_set("c2_oy", 0.0)
}
fn view_reset_camera() {
    st_set("ed_tx", 0.0)
    st_set("ed_ty", 0.0 - 1.0)
    st_set("ed_tz", 0.0)
    st_set("ed_yaw", 0.6)
    st_set("ed_pitch", 0.42)
    st_set("ed_dist", 16.0)
    st_set("c2_zoom", 1.0)
    st_set("c2_ox", 0.0)
    st_set("c2_oy", 0.0)
}

# ── project creation (templates) ─────────────────────────────────────────────
fn runtime_files() {
    return ["theme.ling", "core.ling", "ds.ling", "icons.ling", "rt/rt_data.ling", "rt/rt_ease.ling", "rt/rt_cam.ling", "rt/rt_scene.ling", "rt/rt_sw3d.ling", "rt/rt_draw.ling", "rt/rt_cut.ling", "rt/rt_game.ling", "rt/rt_hud.ling", "rt/rt_ai.ling", "rt/rt_guard.ling", "rt/rt_fps.ling", "rt/rt_brick.ling", "rt/rt_soul.ling", "rt/rt_story.ling"]
}
# copy the runtime (and both platform layers) into <root>/rt
fn proj_install_runtime(root) {
    bind app = st_get("app_root", ".")
    plat_mkdir(p_join(root, "rt"))
    bind files = runtime_files()
    bind i = 0.0
    while i < len(files) {
        bind f = list_get(files, i)
        plat_copy(p_join(p_join(app, "ling"), f), p_join(p_join(root, "rt"), p_base(f)))
        bind i = i + 1.0
    }
    plat_copy(p_join(p_join(app, "ling"), "plat_desktop.ling"), p_join(p_join(root, "rt"), "plat_desktop.ling"))
    plat_copy(p_join(p_join(app, "ling"), "plat_os.ling"), p_join(p_join(root, "rt"), "plat_os.ling"))
    plat_mkdir(p_join(root, "assets/fonts"))
    if !file_exists(p_join(root, "assets/fonts/ui.ttf")) { plat_copy(p_join(app, "assets/font.ttf"), p_join(root, "assets/fonts/ui.ttf")) }
}
fn proj_create(dir, name, tpl, w, h) {
    bind root = p_join(dir, name)
    if !plat_mkdir(root) {
        status("Could not create " + root)
        return false
    }
    plat_mkdir(p_join(root, "scenes"))
    plat_mkdir(p_join(root, "scripts"))
    plat_mkdir(p_join(root, "assets"))
    plat_mkdir(p_join(root, "assets/models"))
    plat_mkdir(p_join(root, "assets/images"))
    plat_mkdir(p_join(root, "assets/audio"))
    plat_mkdir(p_join(root, "assets/music"))
    plat_mkdir(p_join(root, "assets/midi"))
    proj_close()
    st_set("proj_root", root)
    st_set("proj_name", name)
    st_set("proj_title", name)
    st_set("proj_w", w)
    st_set("proj_h", h)
    st_set("proj_fps", 60.0)
    st_set("proj_version", "0.1.0")
    proj_install_runtime(root)
    plat_write(p_join(root, "scripts/player.ling"), script_template("player"))
    plat_write(p_join(root, "scripts/README.md"), "# scripts\n\nEach .ling file here is a script. Name a node's Script field after the file (without .ling)\nand define hooks: <name>_ready(i), <name>_update(i, dt), <name>_draw(i), <name>_input(i, key),\n<name>_trigger(i, other), <name>_hit(i, other), <name>_net(i, msg). See Help > Scripting Reference.\n")
    # scenes by template
    if tpl == 0.0 {
        new_scene("3d")
        save_scene(p_join(root, "scenes/level.lscene"))
    } else if tpl == 1.0 {
        new_scene("3d")
        tpl_fill_level()
        sc_set("hud", "scenes/hud.lscene")
        save_scene(p_join(root, "scenes/level.lscene"))
        new_scene("2d")
        tpl_fill_hud()
        save_scene(p_join(root, "scenes/hud.lscene"))
        open_scene(p_join(root, "scenes/level.lscene"))
    } else if tpl == 2.0 {
        new_scene("3d")
        tpl_fill_level()
        save_scene(p_join(root, "scenes/level.lscene"))
        new_scene("2d")
        sc_set("name", "Main Menu")
        sc_set("ex", "bg3d=scenes/level.lscene")
        save_scene(p_join(root, "scenes/menu.lscene"))
        st_set("proj_start", "scenes/menu.lscene")
    } else if tpl == 3.0 {
        new_scene("cutscene")
        tpl_fill_cutscene()
        save_scene(p_join(root, "scenes/intro.lscene"))
        new_scene("3d")
        tpl_fill_level()
        save_scene(p_join(root, "scenes/level.lscene"))
        open_scene(p_join(root, "scenes/intro.lscene"))
        sc_set("next", "scenes/level.lscene")
        save_scene(p_join(root, "scenes/intro.lscene"))
        st_set("proj_start", "scenes/intro.lscene")
    } else {
        new_scene("3d")
        tpl_fill_rhythm()
        save_scene(p_join(root, "scenes/rhythm.lscene"))
    }
    proj_save()
    gen_write_all()
    recent_add(proj_file())
    status("Created project " + name)
    return true
}
fn tpl_fill_level() {
    bind g = nd_find_name("Ground")
    if g >= 0.0 { nd_set(g, "sx", 16.0)  nd_set(g, "sz", 16.0) }
    bind a = nd_add("actor", "Player", 0.0)
    nd_set(a, "py", 0.0 - 1.0)
    nd_set(a, "tag", "player")
    nd_set(a, "scr", "player")
    bind b = nd_add("box", "Crate", 0.0)
    nd_set(b, "px", 4.0)
    nd_set(b, "py", 0.0 - 1.0)
    nd_set(b, "pz", 3.0)
    nd_set(b, "tag", "solid")
    bind b2 = nd_add("box", "Wall", 0.0)
    nd_set(b2, "px", 0.0 - 6.0)
    nd_set(b2, "py", 0.0 - 1.5)
    nd_set(b2, "pz", 0.0 - 5.0)
    nd_set(b2, "sx", 4.0)
    nd_set(b2, "sy", 1.5)
    nd_set(b2, "sz", 0.5)
    nd_set(b2, "tag", "solid")
    nd_set(b2, "col", C_INDIGO)
    bind s = nd_add("sphere", "Orb", 0.0)
    nd_set(s, "px", 0.0 - 3.0)
    nd_set(s, "py", 0.0 - 2.5)
    nd_set(s, "pz", 4.0)
    nd_set(s, "sx", 0.6)
    nd_set(s, "sy", 0.6)
    nd_set(s, "sz", 0.6)
    key_add(nd_id(s), "py", 0.0, 0.0 - 2.5, "linear")
    key_add(nd_id(s), "py", 1.0, 0.0 - 3.5, "sine_in_out")
    key_add(nd_id(s), "py", 2.0, 0.0 - 2.5, "sine_in_out")
    key_add(nd_id(s), "ry", 0.0, 0.0, "linear")
    key_add(nd_id(s), "ry", 2.0, 6.2831853, "linear")
    sc_set("len", 2.0)
    bind t = nd_add("trigger", "Goal", 0.0)
    nd_set(t, "px", 8.0)
    nd_set(t, "py", 0.0 - 1.0)
    nd_set(t, "pz", 0.0 - 8.0)
    nd_set(t, "tag", "goal")
    nd_set(t, "txt", "say:You found the goal!")
    bind c = nd_find_name("Camera")
    if c >= 0.0 { nd_set(c, "b", 2.0)  nd_set(c, "txt", "Player")  nd_set(c, "d", 9.0) }
}
fn tpl_fill_hud() {
    sc_clear()
    sc_set("name", "HUD")
    sc_set("kind", "2d")
    sc_set("bg", 0.0)
    sc_set("cw", 1280.0)
    sc_set("ch", 720.0)
    sc_set("fps", 30.0)
    sc_set("len", 5.0)
    sc_set("ex", "overlay=1")
    bind hb = nd_add("bar", "Health", 0.0)
    nd_set(hb, "px", 24.0)
    nd_set(hb, "py", 24.0)
    nd_set(hb, "w", 220.0)
    nd_set(hb, "h", 18.0)
    nd_set(hb, "tag", "health")
    bind l = nd_add("label", "Score", 0.0)
    nd_set(l, "px", 1060.0)
    nd_set(l, "py", 22.0)
    nd_set(l, "w", 200.0)
    nd_set(l, "txt", "Score 0")
    nd_set(l, "tag", "score")
    nd_set(l, "a", 20.0)
    bind h = nd_add("label", "Hint", 0.0)
    nd_set(h, "px", 0.0)
    nd_set(h, "py", 0.0 - 60.0)
    nd_set(h, "w", 400.0)
    nd_set(h, "txt", "WASD move   Space jump   Esc stop")
    nd_set(h, "a", 14.0)
    nd_set(h, "col", C_TEXT_DIM)
    nd_set(h, "anc", "bc")
}
fn tpl_fill_cutscene() {
    bind c = nd_find_name("Camera")
    bind k = nd_add("box", "Hero", 0.0)
    nd_set(k, "py", 0.0 - 1.0)
    nd_set(k, "sx", 0.6)
    nd_set(k, "sy", 1.0)
    nd_set(k, "sz", 0.4)
    nd_set(k, "col", C_ORANGE)
    bind q = nd_add("sphere", "Spirit", 0.0)
    nd_set(q, "px", 3.0)
    nd_set(q, "py", 0.0 - 2.0)
    nd_set(q, "sx", 0.5)
    nd_set(q, "sy", 0.5)
    nd_set(q, "sz", 0.5)
    nd_set(q, "col", C_YELLOW)
    key_add(nd_id(q), "px", 0.0, 3.0, "linear")
    key_add(nd_id(q), "px", 6.0, 0.0 - 3.0, "sine_in_out")
    key_add(nd_id(q), "py", 0.0, 0.0 - 2.0, "linear")
    key_add(nd_id(q), "py", 3.0, 0.0 - 3.5, "sine_in_out")
    key_add(nd_id(q), "py", 6.0, 0.0 - 2.0, "sine_in_out")
    # clear the default clips from new_scene and lay out a short scene
    while clip_count() > 0.0 { clip_del(0.0) }
    sc_set("len", 16.0)
    clip_add("fx", 0.0, 1.5, "fade_in", "", "", "", 0.0, "linear")
    clip_add("fx", 0.0, 16.0, "letterbox", "", "", "", 0.0, "linear")
    clip_add("camera", 0.0, 6.0, "orbit", "", "", "0,-1.2,0,7,0.2", nd_id(k), "linear")
    clip_add("camera", 6.0, 5.0, "move", "", "", "4,-2,-6,-0.6,0.15,2;-4,-1.5,-6,0.6,0.1,2", 0.0, "cubic_in_out")
    clip_add("camera", 11.0, 5.0, "follow", "", "", "0,-1,4,0.3", nd_id(q), "linear")
    clip_add("dialog", 0.8, 3.5, "say", "Narrator", "Before form, there was vibration.", "", 0.0, "linear")
    clip_add("dialog", 4.6, 3.2, "say", "Hero", "Who is calling from the light?", "", nd_id(k), "linear")
    clip_add("dialog", 8.2, 3.6, "say", "Spirit", "The song remembers you. Follow.", "", nd_id(q), "linear")
    clip_add("anim", 11.0, 4.0, "moveto", "", "", "-6,-2,8", nd_id(q), "cubic_in_out")
    clip_add("fx", 14.5, 1.5, "fade_out", "", "", "", 0.0, "linear")
    clip_add("event", 16.0, 0.1, "end", "", "", "", 0.0, "linear")
}
fn tpl_fill_rhythm() {
    sc_set("name", "Rhythm")
    bind m = nd_add("midi", "Melody", 0.0)
    nd_set(m, "py", 0.0 - 3.0)
    nd_set(m, "src", "assets/midi/song.mid")
    nd_set(m, "b", 1.0)
    nd_set(m, "scr", "rhythm")
    bind a = nd_add("actor", "Player", 0.0)
    nd_set(a, "py", 0.0 - 1.0)
    nd_set(a, "tag", "player")
    nd_set(a, "scr", "player")
    plat_write(p_join(proj_root(), "scripts/rhythm.ling"), script_template("rhythm"))
}

← 返回索引