From dd702e0a2c562b558311037f98bc7b29eec4386d Mon Sep 17 00:00:00 2001 From: Daddy32 Date: Sat, 13 Dec 2025 10:23:08 +0100 Subject: [PATCH] Make scene scaling responsive --- main.js | 72 +++++++++++++-- scenes.js | 268 +++++++++++++++++++++++++++++++++++++----------------- 2 files changed, 251 insertions(+), 89 deletions(-) diff --git a/main.js b/main.js index eb06589..7d8cd03 100644 --- a/main.js +++ b/main.js @@ -36,6 +36,7 @@ let width = sceneEl.clientWidth; let height = sceneEl.clientHeight; + const BALL_BASELINE = 680; // reference height used for relative ball sizing const engine = Engine.create(); engine.gravity.y = config.gravity; @@ -122,8 +123,10 @@ const next = scenes.find((s) => s.id === sceneId) || scenes[0]; currentScene = next; ui.setSceneSelection(next.id); + const prevRadius = config.ballRadius; Object.assign(config, next.config); config.link = { ...next.config.link }; + updateBallRadius(prevRadius); engine.gravity.y = config.gravity; highScore = loadHighScore(next.id); rebuildSceneBodies(); @@ -432,17 +435,73 @@ ui.buildLegend(config.palette); }; + const computeBallRadius = () => { + const baseRadius = + (currentScene && currentScene.config && currentScene.config.ballRadius) || + config.ballRadius || + 18; + const dim = Math.max(1, Math.min(width, height)); + const scaled = (baseRadius * dim) / BALL_BASELINE; + return Math.round(Math.max(12, Math.min(52, scaled))); + }; + + const updateBallRadius = (prevRadius) => { + const nextRadius = computeBallRadius(); + if ( + Number.isFinite(prevRadius) && + prevRadius > 0 && + nextRadius !== prevRadius + ) { + const scale = nextRadius / prevRadius; + balls.forEach((ball) => { + Body.scale(ball, scale, scale); + ball.circleRadius = nextRadius; + }); + } + config.ballRadius = nextRadius; + }; + + const clampBodiesIntoView = (prevWidth, prevHeight) => { + const scaleX = width / (prevWidth || width); + const scaleY = height / (prevHeight || height); + const margin = config.ballRadius * 1.2; + balls.forEach((ball) => { + const nextX = Math.min( + Math.max(ball.position.x * scaleX, margin), + Math.max(margin, width - margin), + ); + const nextY = Math.min( + ball.position.y * scaleY, + Math.max(height - margin, margin), + ); + Body.setPosition(ball, { x: nextX, y: nextY }); + Body.setVelocity(ball, { x: 0, y: 0 }); + }); + resetChainVisuals(); + }; + const handleResize = () => { + const prevWidth = width; + const prevHeight = height; + const prevRadius = config.ballRadius; width = sceneEl.clientWidth; height = sceneEl.clientHeight; - render.canvas.width = width; - render.canvas.height = height; + const pixelRatio = window.devicePixelRatio || 1; render.options.width = width; render.options.height = height; - Render.lookAt(render, { - min: { x: 0, y: 0 }, - max: { x: width, y: height }, - }); + render.options.pixelRatio = pixelRatio; + render.canvas.style.width = `${width}px`; + render.canvas.style.height = `${height}px`; + render.canvas.width = width * pixelRatio; + render.canvas.height = height * pixelRatio; + Render.setPixelRatio(render, pixelRatio); + render.bounds.min.x = 0; + render.bounds.min.y = 0; + render.bounds.max.x = width; + render.bounds.max.y = height; + Render.lookAt(render, render.bounds); + updateBallRadius(prevRadius); + clampBodiesIntoView(prevWidth, prevHeight); rebuildSceneBodies(); }; @@ -537,5 +596,6 @@ scenes, (currentScene && currentScene.id) || defaultSceneId, ); + updateBallRadius(); applyScene((currentScene && currentScene.id) || defaultSceneId); })(); diff --git a/scenes.js b/scenes.js index c31950e..ee40299 100644 --- a/scenes.js +++ b/scenes.js @@ -21,33 +21,71 @@ maxLengthMultiplier: 3.2, }, }, - createBodies: (w, h) => [ - Bodies.rectangle(w / 2, h + 40, w, 80, { - isStatic: true, - restitution: 0.8, - render: { fillStyle: "#0ea5e9", strokeStyle: "#0ea5e9" }, - }), - Bodies.rectangle(-40, h / 2, 80, h * 2, { - isStatic: true, - render: { fillStyle: "#f97316", strokeStyle: "#f97316" }, - }), - Bodies.rectangle(w + 40, h / 2, 80, h * 2, { - isStatic: true, - render: { fillStyle: "#f97316", strokeStyle: "#f97316" }, - }), - Bodies.rectangle(w * 0.25, h * 0.55, 160, 20, { - isStatic: true, - angle: -0.3, - render: { fillStyle: "#22c55e", strokeStyle: "#22c55e" }, - plugin: { rotSpeed: 0.1 }, - }), - Bodies.rectangle(w * 0.7, h * 0.4, 220, 24, { - isStatic: true, - angle: 0.26, - render: { fillStyle: "#a855f7", strokeStyle: "#a855f7" }, - plugin: { rotSpeed: -0.08 }, - }), - ], + createBodies: (w, h) => { + const floorHeight = Math.max(60, h * 0.12); + const wallThickness = Math.max(32, w * 0.05); + const wallHeight = h * 1.6; + const platformHeight = Math.max(14, h * 0.025); + const smallPlatformWidth = Math.max(140, w * 0.18); + const largePlatformWidth = Math.max(180, w * 0.22); + return [ + Bodies.rectangle( + w / 2, + h + floorHeight / 2, + w + wallThickness * 2, + floorHeight, + { + isStatic: true, + restitution: 0.8, + render: { fillStyle: "#0ea5e9", strokeStyle: "#0ea5e9" }, + }, + ), + Bodies.rectangle( + -wallThickness / 2, + h / 2, + wallThickness, + wallHeight, + { + isStatic: true, + render: { fillStyle: "#f97316", strokeStyle: "#f97316" }, + }, + ), + Bodies.rectangle( + w + wallThickness / 2, + h / 2, + wallThickness, + wallHeight, + { + isStatic: true, + render: { fillStyle: "#f97316", strokeStyle: "#f97316" }, + }, + ), + Bodies.rectangle( + w * 0.25, + h * 0.55, + smallPlatformWidth, + platformHeight, + { + isStatic: true, + angle: -0.3, + render: { fillStyle: "#22c55e", strokeStyle: "#22c55e" }, + plugin: { rotSpeed: 0.1 }, + }, + ), + Bodies.rectangle( + w * 0.7, + h * 0.4, + largePlatformWidth, + platformHeight * 1.2, + { + isStatic: true, + angle: 0.26, + render: { fillStyle: "#a855f7", strokeStyle: "#a855f7" }, + plugin: { rotSpeed: -0.08 }, + }, + ), + ]; + }, }, { id: "scene2", @@ -68,43 +106,70 @@ maxLengthMultiplier: 4.7, }, }, - createBodies: (w, h) => [ - Bodies.rectangle(w / 2, h + 50, w, 100, { - isStatic: true, - restitution: 0.9, - render: { fillStyle: "#0ea5e9", strokeStyle: "#0ea5e9" }, - }), - Bodies.rectangle(-50, h / 2, 100, h * 2, { - isStatic: true, - render: { fillStyle: "#7c3aed", strokeStyle: "#7c3aed" }, - }), - Bodies.rectangle(w + 50, h / 2, 100, h * 2, { - isStatic: true, - render: { fillStyle: "#7c3aed", strokeStyle: "#7c3aed" }, - }), - Bodies.rectangle(w * 0.2, h * 0.45, 200, 18, { - isStatic: true, - angle: 0.08, - render: { fillStyle: "#f97316", strokeStyle: "#f97316" }, - }), - Bodies.rectangle(w * 0.5, h * 0.6, 260, 18, { - isStatic: true, - angle: -0.04, - render: { fillStyle: "#14b8a6", strokeStyle: "#14b8a6" }, - }), - Bodies.rectangle(w * 0.8, h * 0.42, 180, 18, { - isStatic: true, - angle: 0.14, - render: { fillStyle: "#c084fc", strokeStyle: "#c084fc" }, - }), - ], + createBodies: (w, h) => { + const floorHeight = Math.max(70, h * 0.12); + const wallThickness = Math.max(32, w * 0.05); + const wallHeight = h * 1.8; + const ledgeHeight = Math.max(14, h * 0.022); + const leftWidth = Math.max(160, w * 0.18); + const midWidth = Math.max(190, w * 0.26); + const rightWidth = Math.max(150, w * 0.18); + return [ + Bodies.rectangle( + w / 2, + h + floorHeight / 2, + w + wallThickness * 2, + floorHeight, + { + isStatic: true, + restitution: 0.9, + render: { fillStyle: "#0ea5e9", strokeStyle: "#0ea5e9" }, + }, + ), + Bodies.rectangle( + -wallThickness / 2, + h / 2, + wallThickness, + wallHeight, + { + isStatic: true, + render: { fillStyle: "#7c3aed", strokeStyle: "#7c3aed" }, + }, + ), + Bodies.rectangle( + w + wallThickness / 2, + h / 2, + wallThickness, + wallHeight, + { + isStatic: true, + render: { fillStyle: "#7c3aed", strokeStyle: "#7c3aed" }, + }, + ), + Bodies.rectangle(w * 0.2, h * 0.45, leftWidth, ledgeHeight, { + isStatic: true, + angle: 0.08, + render: { fillStyle: "#f97316", strokeStyle: "#f97316" }, + }), + Bodies.rectangle(w * 0.5, h * 0.6, midWidth, ledgeHeight, { + isStatic: true, + angle: -0.04, + render: { fillStyle: "#14b8a6", strokeStyle: "#14b8a6" }, + }), + Bodies.rectangle(w * 0.8, h * 0.42, rightWidth, ledgeHeight, { + isStatic: true, + angle: 0.14, + render: { fillStyle: "#c084fc", strokeStyle: "#c084fc" }, + }), + ]; + }, }, { id: "scene3", name: "Fast drop maze", config: { gravity: 1.25, - spawnIntervalMs: 420, + spawnIntervalMs: 220, minChain: 3, palette: ["#e879f9", "#38bdf8", "#f97316", "#22c55e"], ballRadius: 16, @@ -119,26 +184,51 @@ }, }, createBodies: (w, h) => { + const floorHeight = Math.max(60, h * 0.1); + const wallThickness = Math.max(28, w * 0.045); + const wallHeight = h * 1.5; + const pegRadius = Math.max(10, Math.min(22, Math.min(w, h) * 0.03)); + const platformHeight = Math.max(12, h * 0.02); + const leftPlatformWidth = Math.max(120, w * 0.16); + const rightPlatformWidth = Math.max(130, w * 0.18); const bodies = [ - Bodies.rectangle(w / 2, h + 40, w, 80, { - isStatic: true, - restitution: 0.75, - render: { fillStyle: "#14b8a6", strokeStyle: "#14b8a6" }, - }), - Bodies.rectangle(-40, h / 2, 80, h * 2, { - isStatic: true, - render: { fillStyle: "#f472b6", strokeStyle: "#f472b6" }, - }), - Bodies.rectangle(w + 40, h / 2, 80, h * 2, { - isStatic: true, - render: { fillStyle: "#f472b6", strokeStyle: "#f472b6" }, - }), + Bodies.rectangle( + w / 2, + h + floorHeight / 2, + w + wallThickness * 2, + floorHeight, + { + isStatic: true, + restitution: 0.75, + render: { fillStyle: "#14b8a6", strokeStyle: "#14b8a6" }, + }, + ), + Bodies.rectangle( + -wallThickness / 2, + h / 2, + wallThickness, + wallHeight, + { + isStatic: true, + render: { fillStyle: "#f472b6", strokeStyle: "#f472b6" }, + }, + ), + Bodies.rectangle( + w + wallThickness / 2, + h / 2, + wallThickness, + wallHeight, + { + isStatic: true, + render: { fillStyle: "#f472b6", strokeStyle: "#f472b6" }, + }, + ), ]; for (let i = 0; i < 5; i += 1) { const x = (w * (i + 1)) / 6; - const y = h * 0.35 + (i % 2 === 0 ? 40 : -30); + const y = h * 0.35 + (i % 2 === 0 ? h * 0.06 : -h * 0.05); bodies.push( - Bodies.circle(x, y, 18, { + Bodies.circle(x, y, pegRadius, { isStatic: true, restitution: 0.9, render: { fillStyle: "#facc15", strokeStyle: "#facc15" }, @@ -146,16 +236,28 @@ ); } bodies.push( - Bodies.rectangle(w * 0.3, h * 0.55, 140, 16, { - isStatic: true, - angle: -0.3, - render: { fillStyle: "#8b5cf6", strokeStyle: "#8b5cf6" }, - }), - Bodies.rectangle(w * 0.7, h * 0.58, 160, 16, { - isStatic: true, - angle: 0.28, - render: { fillStyle: "#10b981", strokeStyle: "#10b981" }, - }), + Bodies.rectangle( + w * 0.3, + h * 0.55, + leftPlatformWidth, + platformHeight, + { + isStatic: true, + angle: -0.3, + render: { fillStyle: "#8b5cf6", strokeStyle: "#8b5cf6" }, + }, + ), + Bodies.rectangle( + w * 0.7, + h * 0.58, + rightPlatformWidth, + platformHeight * 1.05, + { + isStatic: true, + angle: 0.28, + render: { fillStyle: "#10b981", strokeStyle: "#10b981" }, + }, + ), ); return bodies; },