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).
关键词 game-engine, editor, 3d, 2d, cutscene, timeline, ling, lingos, raspberry-pi
lingfu install lingdev代码 / 文件 (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
}