diff --git a/src/main.js b/src/main.js index 04d972c..35a80e4 100644 --- a/src/main.js +++ b/src/main.js @@ -636,6 +636,83 @@ rebuildSceneBodies(); }; + const runSceneBeforeUpdateHook = () => { + if ( + state.levelWon || + typeof currentScene?.config?.onBeforeUpdate !== "function" + ) { + return; + } + currentScene.config.onBeforeUpdate({ + engine, + width: state.width, + height: state.height, + }); + }; + + const stepRopeConstraints = () => { + chain.constraints.forEach((c) => { + if (!c.plugin || !c.plugin.rope) return; + const current = Vector.magnitude( + Vector.sub(c.bodyA.position, c.bodyB.position), + ); + const maxLen = c.plugin.maxLength ?? c.length; + if (current <= maxLen) { + c.length = current; + c.stiffness = 0; + } else { + c.length = maxLen; + c.stiffness = c.plugin.baseStiffness ?? c.stiffness; + } + }); + }; + + const stepRotators = (dt, timeScale) => { + state.rotators.forEach((b) => { + const speed = b.plugin.rotSpeed || 0; + if (speed !== 0) { + Body.rotate(b, speed * ((dt * timeScale) / 1000)); + } + }); + }; + + const stepOscillators = () => { + state.oscillators.forEach((b) => { + const osc = b.plugin.oscillate; + if (!osc) return; + if (!osc.base) { + osc.base = { x: b.position.x, y: b.position.y }; + } + const now = (engine.timing.timestamp || 0) / 1000; + const amplitude = osc.amplitude ?? 0; + const speed = osc.speed ?? 1; + const phase = osc.phase ?? 0; + const offset = Math.sin(now * speed + phase) * amplitude; + const target = + osc.axis === "x" + ? { x: osc.base.x + offset, y: osc.base.y } + : { x: osc.base.x, y: osc.base.y + offset }; + Body.setPosition(b, target); + Body.setVelocity(b, { x: 0, y: 0 }); + }); + }; + + const stepTimer = () => { + if (!state.timerEndMs) return; + const winCond = currentScene?.config?.winCondition; + const duration = winCond?.durationSec ?? 120; + const now = Date.now(); + const remainingMs = Math.max(0, state.timerEndMs - now); + const remainingSec = Math.ceil(remainingMs / 1000); + if (state.lastTimerDisplay !== remainingSec) { + state.lastTimerDisplay = remainingSec; + updateHud(); + } + if (remainingMs <= 0 && !state.levelWon) { + checkWinCondition(); + } + }; + Events.on(engine, "afterUpdate", () => { // Keep stray balls within the play area horizontally. state.balls.forEach((ball) => { @@ -674,72 +751,15 @@ Events.on(engine, "beforeUpdate", () => { // Rope-like constraint handling: allow shortening without push-back, tension when stretched. - if ( - !state.levelWon && - typeof currentScene?.config?.onBeforeUpdate === "function" - ) { - currentScene.config.onBeforeUpdate({ - engine, - width: state.width, - height: state.height, - }); - } - chain.constraints.forEach((c) => { - if (!c.plugin || !c.plugin.rope) return; - const current = Vector.magnitude( - Vector.sub(c.bodyA.position, c.bodyB.position), - ); - const maxLen = c.plugin.maxLength ?? c.length; - if (current <= maxLen) { - c.length = current; - c.stiffness = 0; - } else { - c.length = maxLen; - c.stiffness = c.plugin.baseStiffness ?? c.stiffness; - } - }); - // Rotate any scene rotators slowly. + runSceneBeforeUpdateHook(); + stepRopeConstraints(); const dt = (engine.timing && engine.timing.delta) || 16; const timeScale = engine.timing?.timeScale ?? 1; if (state.paused || state.gameOver || timeScale === 0) return; - state.rotators.forEach((b) => { - const speed = b.plugin.rotSpeed || 0; - if (speed !== 0) { - Body.rotate(b, speed * ((dt * timeScale) / 1000)); - } - }); - state.oscillators.forEach((b) => { - const osc = b.plugin.oscillate; - if (!osc) return; - if (!osc.base) { - osc.base = { x: b.position.x, y: b.position.y }; - } - const now = (engine.timing.timestamp || 0) / 1000; - const amplitude = osc.amplitude ?? 0; - const speed = osc.speed ?? 1; - const phase = osc.phase ?? 0; - const offset = Math.sin(now * speed + phase) * amplitude; - const target = - osc.axis === "x" - ? { x: osc.base.x + offset, y: osc.base.y } - : { x: osc.base.x, y: osc.base.y + offset }; - Body.setPosition(b, target); - Body.setVelocity(b, { x: 0, y: 0 }); - }); - if (state.timerEndMs) { - const winCond = currentScene?.config?.winCondition; - const duration = winCond?.durationSec ?? 120; - const now = Date.now(); - const remainingMs = Math.max(0, state.timerEndMs - now); - const remainingSec = Math.ceil(remainingMs / 1000); - if (state.lastTimerDisplay !== remainingSec) { - state.lastTimerDisplay = remainingSec; - updateHud(); - } - if (remainingMs <= 0 && !state.levelWon) { - checkWinCondition(); - } - } + // Rotate any scene rotators slowly. + stepRotators(dt, timeScale); + stepOscillators(); + stepTimer(); }); Events.on(render, "afterRender", () => {