diff --git a/README.md b/README.md index e8ea673..3988666 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,7 @@ Physilinks is a browser-based physics linking game built with Matter.js. Match a - `styles.css`: Styling for canvas, HUD, overlays, and score popups. - `src/scenes/`: Scene presets split per file (`scene-*.js`) plus `index.js` that registers them to `window.PhysilinksScenes` (e.g., zero-G grid, balanced, low-G, fast drop, lava drift). - `src/scenes/scene-template.js`: Reference-only template documenting every scene config option; not loaded by default. +- `src/config.js`: Base game config defaults (gravity, spawn timing, link settings, palettes, message defaults). - `src/decomp-setup.js`: Registers `poly-decomp` with Matter to allow concave shapes (stars, blobs) built via `Bodies.fromVertices`. - `src/ui.js`: DOM access, HUD updates, overlays, popups, and control/selector wiring. - `src/spawn.js`: Spawner utilities (intervals, batch/column/grid spawns), ball creation (shapes/blobs), radius scaling, and blob cleanup. diff --git a/index.html b/index.html index 775de82..074299f 100644 --- a/index.html +++ b/index.html @@ -105,6 +105,7 @@ + diff --git a/src/config.js b/src/config.js new file mode 100644 index 0000000..0d181f5 --- /dev/null +++ b/src/config.js @@ -0,0 +1,37 @@ +(() => { + const create = () => { + const defaultMessageConfig = { + durationMs: 4200, + position: { xPercent: 50, yPercent: 10 }, + text: null, + colors: null, + }; + + const config = { + gravity: 1, + spawnIntervalMs: 520, + autoSpawn: true, + minChain: 3, + palette: ["#ff595e", "#ffca3a", "#8ac926", "#1982c4", "#6a4c93"], + ballRadius: 18, + ballShape: "circle", + link: { + stiffness: 0.85, + lengthScale: 1.05, // max stretch factor; slack below this + damping: 0.08, + lineWidth: 3, + rope: true, + renderType: "line", + maxLengthMultiplier: 3.1, + }, + messages: { ...defaultMessageConfig }, + }; + + return { + defaultMessageConfig: { ...defaultMessageConfig }, + config: { ...config, link: { ...config.link }, messages: { ...config.messages } }, + }; + }; + + window.PhysilinksConfig = { create }; +})(); diff --git a/src/main.js b/src/main.js index 45c84bf..f7c7304 100644 --- a/src/main.js +++ b/src/main.js @@ -2,6 +2,24 @@ const { Engine, Render, Runner, World, Body, Constraint, Events, Vector } = Matter; + const { config: baseConfig, defaultMessageConfig } = window.PhysilinksConfig + ?.create + ? window.PhysilinksConfig.create() + : { + config: {}, + defaultMessageConfig: { + durationMs: 4200, + position: { xPercent: 50, yPercent: 10 }, + text: null, + colors: null, + }, + }; + const config = { + ...baseConfig, + link: { ...(baseConfig?.link || {}) }, + messages: { ...(baseConfig?.messages || {}) }, + }; + const { scenes = [], defaultSceneId, @@ -10,33 +28,6 @@ const { getSceneById, getSceneIdFromUrl, setSceneIdInUrl, getNextSceneId } = window.PhysilinksSceneRegistry || {}; - const defaultMessageConfig = { - durationMs: 4200, - position: { xPercent: 50, yPercent: 10 }, - text: null, - colors: null, - }; - - const config = { - gravity: 1, - spawnIntervalMs: 520, - autoSpawn: true, - minChain: 3, - palette: ["#ff595e", "#ffca3a", "#8ac926", "#1982c4", "#6a4c93"], - ballRadius: 18, - ballShape: "circle", - link: { - stiffness: 0.85, - lengthScale: 1.05, // max stretch factor; slack below this - damping: 0.08, - lineWidth: 3, - rope: true, - renderType: "line", - maxLengthMultiplier: 3.1, - }, - messages: { ...defaultMessageConfig }, - }; - const ui = window.PhysilinksUI.create(); const { sceneEl } = ui; @@ -87,10 +78,13 @@ let rotators = []; let oscillators = []; const initialSceneId = - getSceneIdFromUrl() || - (getSceneById(defaultSceneId) ? defaultSceneId : null) || + (getSceneIdFromUrl && getSceneIdFromUrl(scenes)) || + (getSceneById && getSceneById(scenes, defaultSceneId) + ? defaultSceneId + : null) || scenes[0]?.id; - let currentScene = getSceneById(initialSceneId) || scenes[0] || null; + let currentScene = + (getSceneById && getSceneById(scenes, initialSceneId)) || scenes[0] || null; if (currentScene && currentScene.config) { Object.assign(config, currentScene.config); @@ -128,7 +122,7 @@ } = window.PhysilinksStorage || {}; const applyScene = (sceneId) => { - const next = getSceneById(sceneId) || scenes[0]; + const next = (getSceneById && getSceneById(scenes, sceneId)) || scenes[0]; if (!next) return; currentScene = next; ui.setSceneSelection(next.id); diff --git a/src/scene-registry.js b/src/scene-registry.js index 14c4f74..35c48d4 100644 --- a/src/scene-registry.js +++ b/src/scene-registry.js @@ -1,8 +1,11 @@ (() => { - const getSceneById = (scenes, sceneId) => - scenes.find((candidate) => candidate.id === sceneId) || null; + const getSceneById = (scenes, sceneId) => { + if (!Array.isArray(scenes)) return null; + return scenes.find((candidate) => candidate.id === sceneId) || null; + }; const getSceneIdFromUrl = (scenes) => { + if (!Array.isArray(scenes)) return null; try { const params = new URLSearchParams(window.location.search); const urlScene = params.get("scene"); @@ -24,8 +27,10 @@ }; const getNextSceneId = (scenes, order, currentScene) => { + if (!Array.isArray(scenes)) return null; + const ordered = Array.isArray(order) ? order : []; const currentId = currentScene?.id; - const orderedExisting = order.filter((id) => + const orderedExisting = ordered.filter((id) => scenes.some((s) => s.id === id), ); if (orderedExisting.length > 0 && currentId) {