import { FormaRuntime } from "./runtime.ts"; import { validateProject, type Project } from "./schema.ts"; import "./player.css"; const canvas = document.getElementById("game") as HTMLCanvasElement; const ui = document.getElementById("game-ui")!; ui.innerHTML = '
Загрузка проекта…
'; async function boot() { const response = await fetch("./project.forma.json"); if (!response.ok) throw Error(`Project: HTTP ${response.status}`); const project: Project = await response.json(); validateProject(project); if (project.settings.presentation) { ui.innerHTML = ""; const runtime = new FormaRuntime(canvas, { log: (level, message) => { if (level === "error") { const error = document.createElement("div"); error.id = "error"; error.textContent = message; ui.replaceChildren(error); } }, }); window.addEventListener("pagehide", () => runtime.dispose(), { once: true, }); await runtime.play(project); return; } ui.innerHTML = `
`; document.getElementById("title")!.textContent = project.name; let firstPerson = false; const runtime = new FormaRuntime(canvas, { stats: (stats) => { firstPerson = stats.firstPerson; document.getElementById("hint")!.textContent = firstPerson ? "Клик · захват мыши / Esc · отпустить / WASD · ввод движения" : stats.twoD ? "WASD / стрелки · движение, пробел · прыжок" : "WASD и кнопки действий передаются скриптам проекта"; }, log: (level, message) => { if (level !== "error") return; const error = document.getElementById("error")!; error.hidden = false; error.textContent = message; }, }); const pause = document.getElementById("pause")!; const setPaused = (value: boolean) => { runtime.paused = value; runtime.releaseInput(); pause.textContent = value ? "Продолжить" : "Пауза"; if (value && document.pointerLockElement === canvas) document.exitPointerLock(); }; pause.onclick = () => setPaused(!runtime.paused); const restart = document.getElementById("restart") as HTMLButtonElement; restart.onclick = async () => { restart.disabled = true; try { await runtime.stop(project); await runtime.play(project); setPaused(false); document.getElementById("error")!.hidden = true; } catch (error) { const panel = document.getElementById("error")!; panel.hidden = false; panel.textContent = String(error); } finally { restart.disabled = false; } }; const stick = document.getElementById("stick")!; const knob = stick.querySelector("i")!; let stickId: number | null = null; const move = (event: PointerEvent) => { if (event.pointerId !== stickId || runtime.paused) return; const bounds = stick.getBoundingClientRect(); const x = (event.clientX - bounds.left - bounds.width / 2) / 35; const y = (event.clientY - bounds.top - bounds.height / 2) / 35; const length = Math.max(1, Math.hypot(x, y)); runtime.touch.x = x / length; runtime.touch.z = -y / length; knob.style.transform = `translate(${(x / length) * 28}px,${(y / length) * 28}px)`; }; stick.onpointerdown = (event) => { stickId = event.pointerId; stick.setPointerCapture(event.pointerId); move(event); }; stick.onpointermove = move; stick.onpointerup = stick.onpointercancel = () => { stickId = null; runtime.touch.x = runtime.touch.z = 0; knob.style.transform = ""; }; for (const [id, input] of [ ["jump", "jump"], ["action", "attack"], ] as const) { const button = document.getElementById(id)!; button.onpointerdown = (event) => { if (runtime.paused) return; button.setPointerCapture(event.pointerId); runtime.touch[input] = true; if (input === "jump") runtime.requestAction("jump"); }; button.onpointerup = button.onpointercancel = () => { runtime.touch[input] = false; }; } const release = () => { stickId = null; runtime.releaseInput(); knob.style.transform = ""; }; window.addEventListener("blur", release); document.addEventListener("visibilitychange", () => { release(); if (document.hidden) setPaused(true); }); window.addEventListener("pagehide", () => runtime.dispose(), { once: true }); await runtime.play(project); } boot().catch((error) => { ui.textContent = "Не удалось открыть проект: " + String(error); });