灵符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/gen.ling
# gen.ling - writes a project's runnable game: main.ling (player + scenes + scripts), Ling.toml,
# run / build scripts, a LingOS kernel manifest. `ling run main.ling` plays it; `ling build .` ships it.

fn gen_scripts() {
    bind out = []
    bind dir = p_join(proj_root(), "scripts")
    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))
        if !isd && p_ext(nm) == "ling" { bind out = list_push(out, p_stem(nm)) }
        bind i = i + 1.0
    }
    return out
}
fn gen_hooks() { return ["ready", "update", "draw", "input", "trigger", "hit", "net"] }
fn gen_hook_args(h) {
    if h == "ready" { return "(i)" }
    if h == "update" { return "(i, dt)" }
    if h == "draw" { return "(i)" }
    if h == "input" { return "(i, arg)" }
    if h == "trigger" { return "(i, arg)" }
    if h == "hit" { return "(i, arg)" }
    return "(i, arg)"
}

# main.ling for a platform layer ("desktop" or "os")
fn gen_main_text(plat) {
    bind s = "# Generated by LingDev for project \"" + st_get("proj_name", "") + "\". Edit scenes/ and scripts/, then rebuild.\n"
    bind s = s + "# Run:  ling run main.ling      Build:  ling build . --platform win|lin|mac|web|rpi|lingos\n\n"
    bind s = s + "use \"rt/plat_" + plat + ".ling\"\n"
    bind files = runtime_files()
    bind i = 0.0
    while i < len(files) {
        bind s = s + "use \"rt/" + p_base(list_get(files, i)) + "\"\n"
        bind i = i + 1.0
    }
    bind scripts = gen_scripts()
    bind i = 0.0
    while i < len(scripts) {
        bind s = s + "use \"scripts/" + list_get(scripts, i) + ".ling\"\n"
        bind i = i + 1.0
    }
    bind s = s + "\nfn user_scripts() { return ["
    bind i = 0.0
    while i < len(scripts) {
        if i > 0.0 { bind s = s + ", " }
        bind s = s + "\"" + list_get(scripts, i) + "\""
        bind i = i + 1.0
    }
    bind s = s + "] }\n\n"
    # dispatch: only hooks the script file defines
    bind s = s + "fn user_script(name, hook, i, dt, arg) {\n"
    bind hooks = gen_hooks()
    bind i = 0.0
    while i < len(scripts) {
        bind sn = list_get(scripts, i)
        bind src = plat_read(p_join(proj_root(), "scripts/" + sn + ".ling"))
        bind s = s + "    if name == \"" + sn + "\" {\n"
        bind h = 0.0
        while h < len(hooks) {
            bind hk = list_get(hooks, h)
            if str_find(src, "fn " + sn + "_" + hk + "(") >= 0.0 {
                bind call = sn + "_" + hk + sel(hk == "update", "(i, dt)", sel(hk == "ready" || hk == "draw", "(i)", "(i, arg)"))
                bind s = s + "        if hook == \"" + hk + "\" { " + call + "  return 1.0 }\n"
            }
            bind h = h + 1.0
        }
        bind s = s + "        return 0.0\n    }\n"
        bind i = i + 1.0
    }
    bind s = s + "    return 0.0\n}\n\n"
    bind start = st_get("proj_start", "")
    if start == "" && st_llen("proj_scenes") > 0.5 { bind start = st_lget("proj_scenes", 0.0) }
    bind s = s + "bind start = do {\n"
    bind s = s + "    game_boot(\"" + start + "\", \"" + str_replace(st_get("proj_title", st_get("proj_name", "Game")), "\"", "'") + "\", " + num_str(st_get("proj_w", 1280.0)) + ".0, " + num_str(st_get("proj_h", 720.0)) + ".0)\n"
    bind s = s + "    while window_is_open() && game_quit() < 0.5 {\n        game_frame()\n    }\n    game_shutdown()\n}\n"
    return s
}

fn gen_ling_toml() {
    bind name = str_lower(str_replace(st_get("proj_name", "game"), " ", "-"))
    bind s = "# Ling.toml - read by `ling run .` / `ling build .` (line-parsed) and by lingfu (TOML).\n"
    bind s = s + "name = \"" + name + "\"\nversion = \"" + st_get("proj_version", "0.1.0") + "\"\nentry = \"main.ling\"\nkind = \"game\"\n"
    if st_get("proj_icon", "") != "" { bind s = s + "icon = \"" + st_get("proj_icon", "") + "\"\n" }
    bind s = s + "includes = [\"scenes/*.lscene\", \"scenes/*.lcut\", \"assets/**\", \"rt/*.ling\", \"scripts/*.ling\"]\n\n"
    bind s = s + "[package]\nname = \"" + name + "\"\nversion = \"" + st_get("proj_version", "0.1.0") + "\"\ndescription = \"" + str_replace(st_get("proj_title", ""), "\"", "'") + " - made with LingDev\"\nauthors = [\"" + st_get("proj_author", "") + "\"]\nkeywords = [\"game\", \"lingdev\"]\n"
    return s
}

fn gen_kernel_toml() {
    bind name = str_lower(str_replace(st_get("proj_name", "game"), " ", "-"))
    bind s = "# LingOS kernel target: this game as the Pi desktop's built-in app.\n"
    bind s = s + "# Copy this folder to ../LingOS/kernel/rpi-" + name + "/ and run:\n"
    bind s = s + "#   ling build kernel/rpi-" + name + " --platform rpi --out dist\n"
    bind s = s + "[project]\nname = \"lingos-" + name + "-rpi\"\nversion = \"" + st_get("proj_version", "0.1.0") + "\"\nentry = \"../../apps/lingdesk/main.ling\"\nkind = \"kernel\"\n"
    bind s = s + "bundle_app = \"" + name + ":<path-to-project>/main_os.ling\"\nembed_root = \"<path-to-project>\"\n"
    bind s = s + "includes = [\"assets/fonts/ui.ttf\", \"scenes/*.lscene\", \"assets/images/*.png\"]\n"
    return s
}

fn gen_write_all() {
    if proj_root() == "" { return 0.0 }
    proj_install_runtime(proj_root())
    plat_write(p_join(proj_root(), "main.ling"), gen_main_text("desktop"))
    plat_write(p_join(proj_root(), "main_os.ling"), gen_main_text("os"))
    plat_write(p_join(proj_root(), "Ling.toml"), gen_ling_toml())
    plat_mkdir(p_join(proj_root(), "lingos"))
    plat_write(p_join(proj_root(), "lingos/Ling.toml"), gen_kernel_toml())
    plat_write(p_join(proj_root(), "run.bat"), gen_run_bat())
    plat_write(p_join(proj_root(), "run.sh"), gen_run_sh())
    plat_write(p_join(proj_root(), "BUILD.md"), gen_build_md())
    return 0.0
}

fn ling_exe_guess() {
    bind s = st_get("ling_exe", "")
    if s != "" { return s }
    bind app = st_get("app_root", ".")
    bind c1 = p_join(p_dir(app), "ling/target/release/ling.exe")
    if file_exists(c1) { return c1 }
    return "ling"
}
fn gen_run_bat() {
    bind s = "@echo off\r\nrem Runs the game with the ling runtime (interpreter mode, like LingStationX).\r\nsetlocal\r\ncd /d \"%~dp0\"\r\n"
    bind s = s + "set LING=" + str_replace(ling_exe_guess(), "/", "\\") + "\r\nif not exist \"%LING%\" set LING=ling\r\n"
    bind s = s + "\"%LING%\" run --interp main.ling %*\r\nif errorlevel 1 pause\r\n"
    return s
}
fn gen_run_sh() {
    return "#!/usr/bin/env sh\n# Runs the game with the ling runtime.\ncd \"$(dirname \"$0\")\"\nLING=${LING:-ling}\n\"$LING\" run --interp main.ling \"$@\"\n"
}
fn build_command(t) {
    if t == "rpi" { return "ling build . --platform rpi --out dist" }
    if t == "lingos" { return "ling build . --platform lingos --out dist" }
    if t == "web" { return "ling build . --platform web --out dist" }
    return "ling build . --platform " + t + " --pack --out dist"
}
# writes build_<target>.bat / .sh next to main.ling and returns its path
fn gen_build_script(t) {
    bind exe = ling_exe_guess()
    bind cmd = build_command(t)
    bind bat = p_join(proj_root(), "build_" + t + ".bat")
    bind s = "@echo off\r\nrem " + cmd + "\r\nsetlocal\r\ncd /d \"%~dp0\"\r\nset LING=" + str_replace(exe, "/", "\\") + "\r\nif not exist \"%LING%\" set LING=ling\r\n"
    if t == "rpi" || t == "lingos" {
        bind s = s + "rem The Pi / LingOS targets compile main_os.ling (software renderer, no audio) with the in-kernel runtime.\r\n"
        bind s = s + "copy /y main_os.ling main.ling.os.tmp >nul\r\n"
    }
    bind s = s + "\"%LING%\" " + str_replace(cmd, "ling ", "") + "\r\nif errorlevel 1 (echo Build failed & pause & exit /b 1)\r\necho Build finished: dist\\\r\nexplorer dist\r\n"
    plat_write(bat, s)
    bind sh = p_join(proj_root(), "build_" + t + ".sh")
    plat_write(sh, "#!/usr/bin/env sh\n# " + cmd + "\ncd \"$(dirname \"$0\")\"\nLING=${LING:-ling}\n\"$LING\" " + str_replace(cmd, "ling ", "") + "\n")
    return bat
}
fn gen_build_md() {
    bind s = "# Building " + st_get("proj_name", "") + "\n\nGenerated by LingDev. All commands run from this folder.\n\n"
    bind s = s + "| Target | Command | Result |\n|---|---|---|\n"
    bind s = s + "| Run from source | `ling run --interp main.ling` | plays with the installed ling runtime |\n"
    bind s = s + "| Windows x86-64 | `ling build . --platform win --pack --out dist` | `dist/windows/<name>.exe`, assets embedded |\n"
    bind s = s + "| Linux x86-64 | `ling build . --platform lin --pack --out dist` | `dist/linux/<name>` |\n"
    bind s = s + "| macOS | `ling build . --platform mac --pack --out dist` | `dist/macos/<name>` |\n"
    bind s = s + "| Web | `ling build . --platform web --out dist` | `dist/web/` (WASM + WebGL2) |\n"
    bind s = s + "| Raspberry Pi (bare metal kernel) | `ling build . --platform rpi --out dist` | `dist/rpi/kernel8.img`, boots from an SD card (see ../LingOS/live/build-sdcard-rpi.sh) |\n"
    bind s = s + "| LingOS app | `ling build . --platform lingos --out dist` | `dist/apps/<name>.elf` |\n"
    bind s = s + "| LingOS desktop (Pi) built-in app | see `lingos/Ling.toml` | the game in the LingOS start menu next to LingStationX |\n\n"
    bind s = s + "The Pi / LingOS targets have no native 3-D or audio yet: they use `main_os.ling` (the same scenes and scripts, drawn by the software renderer in rt/rt_sw3d.ling). Pi 5 note: LingOS's aarch64 kernel targets the BCM2837/BCM2711 memory map today; BCM2712 (Pi 5) needs the HAL follow-up described in LingOS/live/build-sdcard-rpi.sh.\n"
    return s
}

# a starter script
fn script_template(name) {
    if name == "player" {
        return "# player.ling - the player actor. The built-in controller already moves an actor tagged `player`\n# (WASD / arrows / gamepad, Space jumps, collides with `solid` boxes). These hooks add behaviour.\n\nfn player_ready(i) {\n    game_set(\"score\", 0.0)\n}\n\nfn player_update(i, dt) {\n    # fall off the world: respawn\n    if nd_get(i, \"py\") > 40.0 { game_respawn(i) }\n}\n\nfn player_trigger(i, other) {\n    bind tag = nd_get(other, \"tag\")\n    if tag == \"goal\" { game_set(\"score\", game_var(\"score\") + 100.0) }\n}\n\nfn player_input(i, key) {\n    if key == \"e\" { game_say(\"Player\", \"Hello from a script hook.\", 2.0) }\n}\n"
    }
    if name == "rhythm" {
        return "# rhythm.ling - a MIDI-driven node: flashes on every note onset (desktop runtime loads the .mid).\n\nfn rhythm_update(i, dt) {\n    if game_on_beat(i, game_time()) { nd_set(i, \"col\", 16242758.0) } else { nd_set(i, \"col\", 10114006.0) }\n}\n"
    }
    return "# " + name + ".ling - attach by typing `" + name + "` in a node's Script field.\n\nfn " + name + "_ready(i) {\n}\n\nfn " + name + "_update(i, dt) {\n    # nd_get(i, \"px\") / nd_set(i, \"py\", v) read and write the node; game_* helpers are in rt/rt_game.ling\n}\n"
}

← 返回索引