灵符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/assets.ling
# assets.ling - the asset browser: every file under the project, filtered by kind, searchable,
# draggable into the viewport. Double-click opens scenes / scripts or adds the asset to the scene.

fn assets_mark_stale() { st_set("as_stale", 1.0) }
fn assets_skip_dir(name) {
    return name == "rt" || name == "dist" || name == ".autosave" || name == ".ling-build" || name == "target" || name == "cache" || name == ".git" || name == "node_modules" || name == "lingos" || name == "AST"
}
fn assets_scan() {
    st_lclear("as_path")
    st_lclear("as_rel")
    st_lclear("as_ext")
    bind root = proj_root()
    if root == "" { bind root = p_join(st_get("app_root", "."), "projects") }
    assets_walk(root, root, 0.0)
    # an imported game keeps its big asset folders outside the project
    bind src = st_get("proj_source", "")
    if src != "" && fs_is_dir(src) { assets_walk(src, src, 0.0) }
    st_set("as_stale", 0.0)
    st_set("as_scan_t", st_get("now", 0.0))
}
fn assets_walk(root, dir, depth) {
    if depth > 5.0 || st_llen("as_path") > 4000.0 { return 0.0 }
    bind items = plat_list(dir)
    bind n = len(items) / 3.0
    bind i = 0.0
    while i < n {
        bind nm = list_get(items, i * 3.0)
        bind isd = is_true(list_get(items, i * 3.0 + 1.0))
        bind full = p_join(dir, nm)
        if isd {
            if !assets_skip_dir(nm) && !(depth == 0.0 && nm == "scripts" && false) { assets_walk(root, full, depth + 1.0) }
        } else {
            bind e = p_ext(nm)
            if assets_kind(e) != "" {
                st_lpush("as_path", full)
                st_lpush("as_rel", p_rel(root, full))
                st_lpush("as_ext", e)
            }
        }
        bind i = i + 1.0
    }
    return 0.0
}
fn assets_kind(e) {
    if e == "lscene" || e == "lcut" { return "scene" }
    if e == "glb" || e == "gltf" || e == "lmesh" || e == "blend" { return "model" }
    if e == "png" || e == "jpg" || e == "jpeg" || e == "bmp" || e == "svg" { return "image" }
    if e == "wav" || e == "ogg" || e == "mp3" || e == "flac" || e == "aac" { return "audio" }
    if e == "mid" || e == "midi" { return "midi" }
    if e == "ling" { return "script" }
    if e == "ttf" || e == "otf" { return "font" }
    if e == "txt" || e == "md" || e == "toml" || e == "cfg" || e == "lrc" { return "text" }
    return ""
}

fn assets_panel(x, y, w, h) {
    g_rect(x, y, w, h, C_BG_SIDE)
    u_vsep(x + w - 1.0, y, h)
    u_hsep(x, y, w)
    ds_word(x + 10.0, y + 8.0, 10.0, "ASSETS", ds_col("lit"), true)
    if u_hot(x + w - 54.0, y + 5.0, 22.0, 20.0) { u_tip("Rescan the project folder") }
    if u_icon_button("as_refresh", x + w - 54.0, y + 5.0, 22.0, 20.0, "refresh", 11.0, C_TEXT_STRONG, false) { assets_mark_stale() }
    if u_hot(x + w - 30.0, y + 5.0, 22.0, 20.0) { u_tip("Import a file into assets/ (Ctrl+I)") }
    if u_icon_button("as_import", x + w - 30.0, y + 5.0, 22.0, 20.0, "plus", 11.0, C_TEXT_STRONG, false) { do_action("asset.import") }
    if st_get("as_stale", 1.0) > 0.5 || st_get("now", 0.0) - st_get("as_scan_t", 0.0) > 8.0 { assets_scan() }
    # filters
    bind fy = y + 30.0
    bind filters = ["ALL", "SCN", "MDL", "IMG", "SND", "MID", "SCR"]
    bind fsel = ds_selector("as_filter", x + 8.0, fy, 10.0, filters, st_get("as_fi", 0.0), ds_col("lit"), "Show")
    if fsel >= 0.0 { st_set("as_fi", fsel)  st_set("as_scroll", 0.0) }
    bind qy = fy + 16.0
    bind q = u_textfield("as_q", x + 8.0, qy, w - 18.0, 22.0, st_get("as_q", ""))
    st_set("as_q", q)
    if q == "" && st_get("focus", "") != "as_q" { g_text(x + 16.0, qy + 5.0, 11.0, C_TEXT_DIM, "search assets") }
    bind top = qy + 28.0
    bind vh = h - (top - y) - 4.0
    # rows
    bind fi = st_get("as_fi", 0.0)
    bind kinds = ["", "scene", "model", "image", "audio", "midi", "script"]
    bind want = list_get(kinds, fi)
    bind ql = str_lower(q)
    bind rows = []
    bind n = st_llen("as_path")
    bind i = 0.0
    while i < n {
        bind e = st_lget("as_ext", i)
        bind k = assets_kind(e)
        if (want == "" || k == want) && (ql == "" || str_find(str_lower(st_lget("as_rel", i)), ql) >= 0.0) { bind rows = list_push(rows, i) }
        bind i = i + 1.0
    }
    bind rh = 20.0
    bind total = len(rows) * rh + 8.0
    bind off = u_scroll_y("as_sb", x, top, w - 1.0, vh, total, st_get("as_scroll", 0.0))
    st_set("as_scroll", off)
    u_clip(x, top, w - 1.0, vh)
    bind r = 0.0
    while r < len(rows) {
        bind i = list_get(rows, r)
        bind ry = top + 4.0 + r * rh - off
        if ry + rh >= top && ry <= top + vh {
            bind path = st_lget("as_path", i)
            bind rel = st_lget("as_rel", i)
            bind e = st_lget("as_ext", i)
            bind selected = st_get("asset_sel", "") == path
            bind hot = u_hot(x, ry, w - 12.0, rh)
            if selected { g_rect(x, ry, w - 1.0, rh, C_SEL_ROW) } else if hot { g_rect(x, ry, w - 1.0, rh, C_HOVER_ROW) }
            icon_draw(ext_icon(e), x + 16.0, ry + rh * 0.5, 12.0, ext_col(e))
            bind dir = p_dir(rel)
            bind name = p_base(rel)
            if dir != "" {
                bind dw = g_text_w(10.0, dir + "/")
                if dw < (w - 60.0) * 0.5 { g_text(x + 30.0, ry + 5.0, 10.0, C_TEXT_DIM, dir + "/")  g_text_fit(x + 30.0 + dw, ry + 4.0, w - 44.0 - dw, 11.0, sel(selected, C_TEXT_STRONG, C_TEXT), name) }
                else { g_text_fit(x + 30.0, ry + 4.0, w - 44.0, 11.0, sel(selected, C_TEXT_STRONG, C_TEXT), name) }
            } else { g_text_fit(x + 30.0, ry + 4.0, w - 44.0, 11.0, sel(selected, C_TEXT_STRONG, C_TEXT), name) }
            if hot {
                u_tip(rel)
                if u_pressed() {
                    st_set("asset_sel", path)
                    st_set("drag_path", path)
                    st_set("drag_x", u_mx())
                    st_set("drag_y", u_my())
                    st_set("drag_live", 0.0)
                    if u_double() { asset_activate(path) }
                }
                if u_rpressed() { st_set("asset_sel", path)  ctx_open("asset", path, u_mx(), u_my()) }
            }
        }
        bind r = r + 1.0
    }
    if len(rows) < 0.5 { g_text_wrap(x + 12.0, top + 8.0, w - 24.0, 11.0, C_TEXT_DIM, sel(proj_root() == "", "Open a project to browse its files.", "No files match. Import with the + button, or drop files into assets/.")) }
    u_unclip()
}

fn asset_activate(path) {
    bind e = p_ext(path)
    if e == "lscene" || e == "lcut" { st_set("ctx_arg", path)  guard_unsaved("scene.open_path") }
    else if e == "ling" { script_open(path)  st_set("dock_tab", 2.0)  st_set("show_dock", 1.0) }
    else { asset_add_to_scene(path, false) }
}

# make a node for a file: models, sounds, MIDI, images (2-D scenes)
fn asset_add_to_scene(path, at_cursor) {
    bind e = p_ext(path)
    bind k = assets_kind(e)
    bind rel = proj_rel(path)
    bind name = p_stem(path)
    if k == "model" {
        bind i = add_node_quiet("model", name, at_cursor)
        nd_set(i, "src", rel)
    } else if k == "audio" {
        bind i = add_node_quiet("audio", name, at_cursor)
        nd_set(i, "src", rel)
    } else if k == "midi" {
        bind i = add_node_quiet("midi", name, at_cursor)
        nd_set(i, "src", rel)
    } else if k == "image" {
        if sc_get("kind", "3d") != "2d" { status("Images go into 2D scenes (menus / HUDs). Open or create one first.")  return 0.0 }
        bind i = add_node_quiet("image", name, at_cursor)
        nd_set(i, "src", rel)
        if at_cursor { nd_set(i, "px", st_get("drop_cx", 40.0))  nd_set(i, "py", st_get("drop_cy", 40.0)) }
    } else if k == "script" {
        bind sidx = sel_index()
        if sidx >= 0.0 { undo_push()  nd_set(sidx, "scr", p_stem(path))  status("Attached script " + p_stem(path) + " to " + nd_get(sidx, "name")) }
        else { status("Select a node, then drop the script on it (or double-click to edit)") }
        return 0.0
    } else if k == "scene" { st_set("ctx_arg", path)  guard_unsaved("scene.open_path")  return 0.0 }
    else { status("Nothing to add for ." + e)  return 0.0 }
    status("Added " + name)
    return 0.0
}
# add_node without a file picker, placed at the last viewport hit when asked
fn add_node_quiet(kind, name, at_cursor) {
    undo_push()
    bind i = nd_add(kind, name, 0.0)
    if !nd_is_2d(kind) {
        if at_cursor && st_get("hit_ok", 0.0) > 0.5 {
            nd_set(i, "px", st_get("hit_x", 0.0))
            nd_set(i, "py", st_get("hit_y", 0.0) - sel(kind == "model", 0.0, nd_get(i, "sy")))
            nd_set(i, "pz", st_get("hit_z", 0.0))
        } else {
            nd_set(i, "px", st_get("ed_tx", 0.0))
            nd_set(i, "py", 0.0 - nd_get(i, "sy"))
            nd_set(i, "pz", st_get("ed_tz", 0.0))
        }
        if kind == "audio" || kind == "midi" { nd_set(i, "py", nd_get(i, "py") - 1.0) }
    }
    sel_set(nd_id(i))
    return i
}
# copy a file into the project's assets/<kind>/
fn asset_import(path) {
    if proj_root() == "" { status("Open a project first")  return 0.0 }
    bind e = p_ext(path)
    bind k = assets_kind(e)
    bind sub = "assets/other"
    if k == "model" { bind sub = "assets/models" }
    else if k == "image" { bind sub = "assets/images" }
    else if k == "audio" { bind sub = "assets/audio" }
    else if k == "midi" { bind sub = "assets/midi" }
    else if k == "font" { bind sub = "assets/fonts" }
    else if k == "script" { bind sub = "scripts" }
    else if k == "scene" { bind sub = "scenes" }
    plat_mkdir(p_join(proj_root(), sub))
    bind dst = p_join(p_join(proj_root(), sub), p_base(path))
    if plat_copy(path, dst) { status("Imported " + p_base(path) + " into " + sub + sel(e == "blend", "  (run `ling convert` or export glb from Blender to draw it)", "")) }
    else { status("Could not copy " + path) }
    assets_mark_stale()
    return 0.0
}

← 返回索引