灵符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/app.ling
# app.ling - startup, the frame loop, the layout of panels, keyboard shortcuts and play mode.

fn app_main(platform) {
    bind win_w = 1360.0
    bind win_h = 820.0
    bind args = plat_args()
    bind i = 0.0
    while i < len(args) {
        if list_get(args, i) == "--size" && i + 1.0 < len(args) {
            bind p = str_split(list_get(args, i + 1.0), "x")
            if len(p) > 1.5 { bind win_w = max(640.0, to_number(list_get(p, 0)))  bind win_h = max(480.0, to_number(list_get(p, 1))) }
        }
        bind i = i + 1.0
    }
    open_window(win_w, win_h, "LingDev", 1.0)
    px_flat()
    st_set("app_root", app_find_root())
    bind f = font_load(p_join(st_get("app_root", "."), "assets/font.ttf"))
    if f < 0.0 { bind f = font_load("assets/font.ttf") }
    if f < 0.0 { bind f = font_load("../assets/font.ttf") }
    st_set("font", f)
    st_set("ling_version", "2030.1")
    # defaults
    st_set("show_hier", 1.0)
    st_set("show_assets", 1.0)
    st_set("show_insp", 1.0)
    st_set("show_dock", 1.0)
    st_set("dock_tab", 0.0)
    st_set("tool", "move")
    st_set("view_solid", 1.0)
    st_set("view_grid", 1.0)
    st_set("view_gizmos", 1.0)
    st_set("snap_on", 1.0)
    st_set("tl_loop", 1.0)
    st_set("tl_zoom", 60.0)
    st_set("left_split", 0.5)
    st_set("status", "Ready")
    settings_load()
    sc_new("Untitled", "3d")
    view_reset_camera()
    # command line: a project folder / lingdev.toml / .lscene, --shot N, --name, --drive, --project
    bind shot_at = 0.0 - 1.0
    bind opened = false
    bind i = 0.0
    while i < len(args) {
        bind a = list_get(args, i)
        if a == "--shot" && i + 1.0 < len(args) { bind shot_at = to_number(list_get(args, i + 1.0)) }
        if a == "--name" && i + 1.0 < len(args) { st_set("shot_name", list_get(args, i + 1.0)) }
        if a == "--tab" && i + 1.0 < len(args) { st_set("dock_tab", to_number(list_get(args, i + 1.0))) }
        if a == "--play" { st_set("auto_play", 1.0) }
        if a == "--modal" && i + 1.0 < len(args) { st_set("auto_modal", list_get(args, i + 1.0)) }
        if a == "--select" && i + 1.0 < len(args) { st_set("auto_select", list_get(args, i + 1.0)) }
        if a == "--scene" && i + 1.0 < len(args) { st_set("auto_scene", list_get(args, i + 1.0)) }
        if a == "--time" && i + 1.0 < len(args) { st_set("auto_time", to_number(list_get(args, i + 1.0))) }
        if a == "--drive" && i + 1.0 < len(args) {
            st_set("fake_on", 1.0)
            bind evs = str_split(list_get(args, i + 1.0), ";")
            bind ei = 0.0
            while ei < len(evs) {
                bind f4 = str_split(list_get(evs, ei), ",")
                if len(f4) >= 4.0 {
                    st_lpush("dv_f", to_number(list_get(f4, 0)))
                    st_lpush("dv_x", to_number(list_get(f4, 1)))
                    st_lpush("dv_y", to_number(list_get(f4, 2)))
                    st_lpush("dv_d", to_number(list_get(f4, 3)))
                }
                bind ei = ei + 1.0
            }
        }
        if a == "--project" && i + 1.0 < len(args) { bind opened = project_open_path(list_get(args, i + 1.0)) }
        if str_ends_with(str_lower(a), ".lscene") || str_ends_with(str_lower(a), ".lcut") { bind opened = open_scene(a) }
        if str_ends_with(str_lower(a), "lingdev.toml") { bind opened = project_open_path(a) }
        bind i = i + 1.0
    }
    if !opened && st_get("load_last", 1.0) > 0.5 && st_llen("recent") > 0.5 && file_exists(st_lget("recent", 0.0)) { bind opened = project_open_path(st_lget("recent", 0.0)) }
    if !opened { new_scene("3d")  st_set("dirty", 0.0)  status("Welcome to LingDev - File > New Project to begin") }
    # test / automation switches: open a scene, select a node, set the playhead, open a dialog, start playing
    if st_get("auto_scene", "") != "" { open_scene(proj_abs(st_get("auto_scene", ""))) }
    if st_get("auto_select", "") != "" {
        bind ni = nd_find_name(st_get("auto_select", ""))
        if ni >= 0.0 { sel_set(nd_id(ni)) }
    }
    if st_has("auto_time") > 0.5 { st_set("tl_t", st_get("auto_time", 0.0)) }
    if st_get("auto_modal", "") != "" {
        bind m = st_get("auto_modal", "")
        if m == "picker" { picker_open("open", "Open scene", "lscene,lcut", "scene.open_file", p_join(proj_root(), "scenes"), "") }
        else if m == "clip" { st_set("clip_sel", 0.0)  modal_open("clip") }
        else { modal_open(m) }
    }
    if st_get("auto_play", 0.0) > 0.5 { play_start(true) }
    bind frame = 0.0
    bind t_prev = time_now()
    while window_is_open() && st_get("quit", 0.0) < 0.5 {
        st_set("frame", frame)
        px_theme(st_get("theme_black", 0.0))
        u_begin()
        bind overlay = st_get("menu_open", 0.0 - 1.0) >= 0.0 || st_get("ctx_open", 0.0) > 0.5 || st_get("modal", "") != "" || st_get("cb_open", "") != ""
        st_set("blk", sel(overlay, 1.0, 0.0))
        app_tick()
        draw_frame()
        st_set("blk", sel(st_get("ctx_open", 0.0) > 0.5 || st_get("modal", "") != "", 1.0, 0.0))
        menu_bar()
        draw_floating()
        st_set("blk", 0.0)
        bind act = menu_overlay()
        if act != "" { do_action(act) }
        modal_overlay()
        ctx_overlay()
        combo_overlay()
        if !overlay { keyboard() }
        autosave_tick()
        u_end()
        present()
        bind now = time_now()
        st_set("frame_ms", (now - t_prev) * 1000.0)
        bind t_prev = now
        bind frame = frame + 1.0
        if shot_at > 0.0 && frame == shot_at {
            plat_screenshot(st_get("shot_name", "lingdev"))
            st_set("quit", 1.0)
        }
    }
    settings_save()
}

# where LingDev lives (settings.txt, assets/, projects/): the folder above ling/main.ling
fn app_find_root() {
    bind cwd = fs_cwd()
    if file_exists(p_join(cwd, "assets/font.ttf")) && fs_is_dir(p_join(cwd, "ling")) { return p_norm(cwd) }
    if file_exists(p_join(cwd, "../assets/font.ttf")) { return p_norm(fs_parent(cwd)) }
    return p_norm(cwd)
}

# advance the playhead and the play mode
fn app_tick() {
    bind dt = u_dt()
    if st_get("tl_playing", 0.0) > 0.5 && st_get("playing", 0.0) < 0.5 {
        bind t = st_get("tl_t", 0.0) + dt
        bind ln = max(0.1, sc_get("len", 10.0))
        if t > ln {
            if st_get("tl_loop", 1.0) > 0.5 { bind t = t - ln } else { bind t = ln  st_set("tl_playing", 0.0) }
        }
        st_set("tl_t", t)
    }
}

# ── layout ───────────────────────────────────────────────────────────────────
fn draw_frame() {
    bind W = get_width()
    bind H = get_height()
    g_rect(0.0, 0.0, W, H, C_BG_APP)
    bind top = MENU_H + TABS_H + TOOL_H
    bind bottom = H - STATUS_H
    if st_get("playing", 0.0) > 0.5 {
        play_frame(0.0, top, W, bottom - top)
        view_tabs()
        toolbar()
        status_bar()
        return 0.0
    }
    bind left = 0.0
    if st_get("show_hier", 1.0) > 0.5 || st_get("show_assets", 1.0) > 0.5 {
        bind lw = clampf(st_get("left_w", 260.0), 180.0, W * 0.4)
        bind both = st_get("show_hier", 1.0) > 0.5 && st_get("show_assets", 1.0) > 0.5
        bind split = clampf(st_get("left_split", 0.5), 0.2, 0.8)
        bind hh = sel(both, floor((bottom - top) * split), bottom - top)
        if st_get("show_hier", 1.0) > 0.5 { hier_panel(0.0, top, lw, hh) }
        if st_get("show_assets", 1.0) > 0.5 { assets_panel(0.0, sel(both, top + hh, top), lw, sel(both, bottom - top - hh, bottom - top)) }
        if both {
            bind sy = top + hh
            bind hot = u_hot(0.0, sy - 3.0, lw, 6.0)
            if hot && u_pressed() { u_set_active("left_split") }
            if u_active() == "left_split" && u_down() { st_set("left_split", clampf((u_my() - top) / (bottom - top), 0.2, 0.8)) }
            if hot || u_active() == "left_split" { g_rect(0.0, sy - 1.0, lw, 2.0, C_SEL) }
        }
        bind hot = u_hot(lw - 3.0, top, 6.0, bottom - top)
        if hot && u_pressed() { u_set_active("left_resize") }
        if u_active() == "left_resize" && u_down() { st_set("left_w", clampf(u_mx(), 180.0, W * 0.4)) }
        if hot || u_active() == "left_resize" { g_rect(lw - 1.0, top, 2.0, bottom - top, C_SEL) }
        bind left = lw
    }
    bind right = W
    if st_get("show_insp", 1.0) > 0.5 {
        bind iw = clampf(st_get("insp_w", 300.0), 220.0, W * 0.4)
        bind right = W - iw
        inspector_panel(right, top, iw, bottom - top)
        bind hot = u_hot(right - 3.0, top, 6.0, bottom - top)
        if hot && u_pressed() { u_set_active("insp_resize") }
        if u_active() == "insp_resize" && u_down() { st_set("insp_w", clampf(W - u_mx(), 220.0, W * 0.4)) }
        if hot || u_active() == "insp_resize" { g_rect(right - 1.0, top, 2.0, bottom - top, C_SEL) }
    }
    bind vbottom = bottom
    if st_get("show_dock", 1.0) > 0.5 {
        bind dh = clampf(st_get("dock_h", 240.0), 120.0, (bottom - top) * 0.7)
        bind vbottom = bottom - dh
        dock_panel(left, vbottom, right - left, dh)
        bind hot = u_hot(left, vbottom - 3.0, right - left, 6.0)
        if hot && u_pressed() { u_set_active("dock_resize") }
        if u_active() == "dock_resize" && u_down() { st_set("dock_h", clampf(bottom - u_my(), 120.0, (bottom - top) * 0.7)) }
        if hot || u_active() == "dock_resize" { g_rect(left, vbottom - 1.0, right - left, 2.0, C_SEL) }
    }
    bind vw = right - left
    bind vh = vbottom - top
    if vw > 40.0 && vh > 40.0 {
        if sc_get("kind", "3d") == "2d" { view2d(left, top, vw, vh) } else { view3d(left, top, vw, vh) }
    }
    view_tabs()
    toolbar()
    status_bar()
    return 0.0
}

fn dock_panel(x, y, w, h) {
    g_rect(x, y, w, h, C_BG_PANEL)
    u_hsep(x, y, w)
    bind tab = st_get("dock_tab", 0.0)
    if tab == 0.0 { timeline_panel(x, y + 1.0, w, h - 1.0) }
    else if tab == 1.0 { cutscene_panel(x, y + 1.0, w, h - 1.0) }
    else if tab == 2.0 { script_panel(x, y + 1.0, w, h - 1.0) }
    else if tab == 3.0 { build_panel(x, y + 1.0, w, h - 1.0) }
    else { console_panel(x, y + 1.0, w, h - 1.0) }
}

# play mode: the game runs embedded in the editor window
fn play_frame(x, y, w, h) {
    st_set("game_embedded", 1.0)
    bind now = time_now()
    bind dt = clampf(now - st_get("game_now", now), 0.0, 0.05)
    st_set("game_now", now)
    st_set("game_dt", dt)
    st_set("game_t", st_get("game_t", 0.0) + dt)
    st_set("game_scene_t", st_get("game_scene_t", 0.0) + dt)
    game_update(dt)
    u_clip(x, y, w, h)
    game_render(x, y, w, h)
    u_unclip()
    if st_get("game_quit", 0.0) > 0.5 { play_stop() }
    g_text(x + 12.0, y + 10.0, 11.0, C_TEXT_DIM, "PLAY  " + st_get("game_scene", sc_get("name", "")) + "   Esc stops   " + num_str(round(st_get("frame_ms", 0.0))) + " ms")
}

# ── keyboard ─────────────────────────────────────────────────────────────────
fn keyboard() {
    if st_get("focus", "") != "" { return 0.0 }
    bind ctrl = u_ctrl()
    bind shift = u_shift()
    if st_get("playing", 0.0) > 0.5 {
        if key_pressed("escape") && sc_get("kind", "3d") != "2d" { play_stop() }
        if key_pressed("f5") || key_pressed("f6") { play_stop() }
        return 0.0
    }
    if ctrl {
        if key_pressed("z") { do_action(sel(shift, "edit.redo", "edit.undo")) }
        if key_pressed("y") { do_action("edit.redo") }
        if key_pressed("s") { do_action(sel(shift, "scene.save_as", "scene.save")) }
        if key_pressed("o") { do_action(sel(shift, "project.open", "scene.open")) }
        if key_pressed("n") { do_action(sel(shift, "project.new", "scene.new:3d")) }
        if key_pressed("i") { do_action("asset.import") }
        if key_pressed("c") { do_action("edit.copy") }
        if key_pressed("x") { do_action("edit.cut") }
        if key_pressed("v") { do_action("edit.paste") }
        if key_pressed("d") { do_action("edit.dup") }
        if key_pressed("a") { do_action("edit.select_all") }
        if key_pressed("g") { do_action("node.group") }
        if key_pressed("p") { do_action("app.settings") }
        if key_pressed("b") { do_action("build.panel") }
        if key_pressed("q") { do_action("app.quit") }
        return 0.0
    }
    if key_pressed("f1") { do_action("help.shortcuts") }
    if key_pressed("f2") { do_action("node.rename") }
    if key_pressed("f5") { do_action("play.project") }
    if key_pressed("f6") { do_action("play.scene") }
    if key_pressed("f7") { st_set("dock_tab", 0.0)  st_set("show_dock", 1.0) }
    if key_pressed("f8") { st_set("dock_tab", 1.0)  st_set("show_dock", 1.0) }
    if key_pressed("f9") { st_set("dock_tab", 2.0)  st_set("show_dock", 1.0) }
    if key_pressed("f10") { st_set("dock_tab", 3.0)  st_set("show_dock", 1.0) }
    if key_pressed("f11") { st_set("dock_tab", 4.0)  st_set("show_dock", 1.0) }
    if key_pressed("delete") { do_action("edit.delete") }
    if key_pressed("escape") { do_action("edit.deselect") }
    if key_pressed("space") { do_action("tl.play") }
    if key_pressed("home") { do_action(sel(shift, "view.reset_cam", "tl.home")) }
    if key_pressed("end") { do_action("tl.end") }
    if key_pressed("comma") { do_action("tl.prev") }
    if key_pressed("period") { do_action("tl.next") }
    if key_pressed("k") { do_action(sel(shift, "tl.delkey", "tl.key")) }
    if key_pressed("l") { do_action("tl.loop") }
    if key_pressed("q") { st_set("tool", "select") }
    if key_pressed("w") && !mouse_down_right() { st_set("tool", "move") }
    if key_pressed("e") && !mouse_down_right() { st_set("tool", "rotate") }
    if key_pressed("r") { st_set("tool", "scale") }
    if key_pressed("f") { do_action("view.focus") }
    if key_pressed("g") { do_action("view.grid") }
    if key_pressed("z") { do_action("view.solid") }
    if key_pressed("h") { do_action("node.togglevis") }
    if key_pressed("numpad7") { do_action("view.top") }
    if key_pressed("numpad1") { do_action("view.front") }
    if key_pressed("numpad3") { do_action("view.side") }
    if shift && key_pressed("a") { do_action("node.palette") }
    if shift && key_pressed("p") { do_action("node.unparent") }
    return 0.0
}

← 返回索引