Fix gravity reset and add spawn cap for swirl arena
This commit is contained in:
106
src/main.js
106
src/main.js
@@ -64,6 +64,8 @@
|
||||
const BALL_BASELINE = 680; // reference height used for relative ball sizing
|
||||
|
||||
const engine = Engine.create();
|
||||
const defaultGravityScale = engine.gravity.scale;
|
||||
const defaultTimeScale = engine.timing.timeScale || 1;
|
||||
engine.gravity.y = config.gravity;
|
||||
const world = engine.world;
|
||||
|
||||
@@ -125,6 +127,7 @@
|
||||
const balls = [];
|
||||
const blobConstraints = new Map();
|
||||
let spawnTimer = null;
|
||||
let spawnCount = 0;
|
||||
let score = 0;
|
||||
let highScore = 0;
|
||||
let clearedCount = 0;
|
||||
@@ -165,7 +168,16 @@
|
||||
const prevRadius = config.ballRadius;
|
||||
Object.assign(config, next.config);
|
||||
config.link = { ...next.config.link };
|
||||
engine.gravity.scale =
|
||||
typeof next.config.gravityScale === "number"
|
||||
? next.config.gravityScale
|
||||
: defaultGravityScale;
|
||||
engine.timing.timeScale =
|
||||
typeof next.config.timeScale === "number"
|
||||
? next.config.timeScale
|
||||
: defaultTimeScale;
|
||||
updateBallRadius(prevRadius);
|
||||
engine.gravity.x = 0;
|
||||
engine.gravity.y = config.gravity;
|
||||
clearedCount = 0;
|
||||
levelWon = false;
|
||||
@@ -221,16 +233,39 @@
|
||||
|
||||
const spawnBall = () => {
|
||||
if (gameOver) return;
|
||||
const spawnLimit = currentScene?.config?.spawnLimit;
|
||||
if (Number.isFinite(spawnLimit) && spawnCount >= spawnLimit) {
|
||||
stopSpawner();
|
||||
return;
|
||||
}
|
||||
const color =
|
||||
config.palette[Math.floor(Math.random() * config.palette.length)];
|
||||
const x = Math.max(
|
||||
config.ballRadius + 10,
|
||||
Math.min(width - config.ballRadius - 10, Math.random() * width),
|
||||
);
|
||||
const spawnOrigin = currentScene?.config?.spawnOrigin || "edge";
|
||||
const spawnJitter =
|
||||
currentScene?.config?.spawnJitter ?? config.ballRadius * 3;
|
||||
const centerSpawn =
|
||||
spawnOrigin === "center"
|
||||
? {
|
||||
x:
|
||||
width / 2 +
|
||||
(Math.random() - 0.5) * Math.max(spawnJitter, config.ballRadius),
|
||||
y:
|
||||
height / 2 +
|
||||
(Math.random() - 0.5) * Math.max(spawnJitter, config.ballRadius),
|
||||
}
|
||||
: null;
|
||||
const x =
|
||||
centerSpawn?.x ??
|
||||
Math.max(
|
||||
config.ballRadius + 10,
|
||||
Math.min(width - config.ballRadius - 10, Math.random() * width),
|
||||
);
|
||||
const spawnFromBottom = currentScene?.config?.spawnFrom === "bottom";
|
||||
const y = spawnFromBottom
|
||||
? height + config.ballRadius * 2
|
||||
: -config.ballRadius * 2;
|
||||
const y =
|
||||
centerSpawn?.y ??
|
||||
(spawnFromBottom
|
||||
? height + config.ballRadius * 2
|
||||
: -config.ballRadius * 2);
|
||||
const batchMin = currentScene?.config?.spawnBatchMin ?? 1;
|
||||
const batchMax = currentScene?.config?.spawnBatchMax ?? 1;
|
||||
const batchCount =
|
||||
@@ -276,6 +311,7 @@
|
||||
World.add(world, blob.constraints);
|
||||
}
|
||||
}
|
||||
spawnCount += batchCount;
|
||||
};
|
||||
|
||||
const startSpawner = () => {
|
||||
@@ -292,6 +328,14 @@
|
||||
}
|
||||
};
|
||||
|
||||
const spawnInitialBurst = () => {
|
||||
const initialCount = currentScene?.config?.initialSpawnCount || 0;
|
||||
if (!initialCount || initialCount <= 0) return;
|
||||
for (let i = 0; i < initialCount; i += 1) {
|
||||
spawnBall();
|
||||
}
|
||||
};
|
||||
|
||||
const cleanupBall = (ball) => {
|
||||
if (ball.plugin && ball.plugin.entryCheckId) {
|
||||
clearTimeout(ball.plugin.entryCheckId);
|
||||
@@ -371,6 +415,7 @@
|
||||
gameOver = false;
|
||||
isPaused = false;
|
||||
levelWon = false;
|
||||
spawnCount = 0;
|
||||
score = 0;
|
||||
clearedCount = 0;
|
||||
clearedByColor = {};
|
||||
@@ -393,6 +438,7 @@
|
||||
ui.hideGameOver();
|
||||
ui.hideWin();
|
||||
ui.setPauseState(false);
|
||||
engine.gravity.x = 0;
|
||||
engine.gravity.y = config.gravity;
|
||||
engine.timing.timeScale = 1;
|
||||
startRunner();
|
||||
@@ -400,6 +446,7 @@
|
||||
if (isGridScene()) {
|
||||
spawnGridBalls();
|
||||
} else {
|
||||
spawnInitialBurst();
|
||||
startSpawner();
|
||||
}
|
||||
};
|
||||
@@ -534,7 +581,20 @@
|
||||
const finishChain = (releasePoint) => {
|
||||
if (!chain.active || gameOver || isPaused) return;
|
||||
if (chain.bodies.length >= config.minChain) {
|
||||
const gain = 10 * Math.pow(chain.bodies.length, 2);
|
||||
const baseGain = 10 * Math.pow(chain.bodies.length, 2);
|
||||
const negativeColors = (
|
||||
currentScene?.config?.negativeScoreColors || []
|
||||
).map(normalizeColor);
|
||||
const negativeProgressColors = (
|
||||
currentScene?.config?.negativeProgressColors || []
|
||||
).map(normalizeColor);
|
||||
const isNegative =
|
||||
chain.color &&
|
||||
negativeColors.includes(normalizeColor(chain.color || ""));
|
||||
const isNegativeProgress =
|
||||
chain.color &&
|
||||
negativeProgressColors.includes(normalizeColor(chain.color || ""));
|
||||
const gain = isNegative ? -baseGain : baseGain;
|
||||
score += gain;
|
||||
clearedCount += chain.bodies.length;
|
||||
if (score > highScore) {
|
||||
@@ -576,6 +636,16 @@
|
||||
balls.splice(i, 1);
|
||||
}
|
||||
}
|
||||
if (isNegativeProgress) {
|
||||
const winCond = currentScene?.config?.winCondition;
|
||||
if (winCond?.type === "colorClear" && Array.isArray(winCond.targets)) {
|
||||
winCond.targets.forEach((target) => {
|
||||
const key = normalizeColor(target.color);
|
||||
const current = clearedByColor[key] || 0;
|
||||
clearedByColor[key] = Math.max(0, current - chain.bodies.length);
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
chain.constraints.forEach((c) => World.remove(world, c));
|
||||
chain.bodies.forEach((b) => setHighlight(b, false));
|
||||
@@ -886,10 +956,11 @@
|
||||
const totalTarget = targets.reduce((sum, t) => sum + t.count, 0);
|
||||
let totalAchieved = 0;
|
||||
const parts = targets.map((t) => {
|
||||
const got = Math.min(
|
||||
t.count,
|
||||
const achieved = Math.max(
|
||||
0,
|
||||
clearedByColor[normalizeColor(t.color)] || 0,
|
||||
);
|
||||
const got = Math.min(t.count, achieved);
|
||||
totalAchieved += got;
|
||||
const remaining = Math.max(0, t.count - got);
|
||||
return `${got}/${t.count} (${remaining} left)`;
|
||||
@@ -972,8 +1043,16 @@
|
||||
ball.plugin.hasEntered = true;
|
||||
const spawnFromBottom = currentScene?.config?.spawnFrom === "bottom";
|
||||
Matter.Body.setPosition(ball, {
|
||||
x: Math.random() * width,
|
||||
y: spawnFromBottom ? height + 40 : -40,
|
||||
x:
|
||||
currentScene?.config?.spawnOrigin === "center"
|
||||
? width / 2
|
||||
: Math.random() * width,
|
||||
y:
|
||||
currentScene?.config?.spawnOrigin === "center"
|
||||
? height / 2
|
||||
: spawnFromBottom
|
||||
? height + 40
|
||||
: -40,
|
||||
});
|
||||
Matter.Body.setVelocity(ball, { x: 0, y: 0 });
|
||||
}
|
||||
@@ -982,6 +1061,9 @@
|
||||
|
||||
Events.on(engine, "beforeUpdate", () => {
|
||||
// Rope-like constraint handling: allow shortening without push-back, tension when stretched.
|
||||
if (typeof currentScene?.config?.onBeforeUpdate === "function") {
|
||||
currentScene.config.onBeforeUpdate({ engine, width, height });
|
||||
}
|
||||
chain.constraints.forEach((c) => {
|
||||
if (!c.plugin || !c.plugin.rope) return;
|
||||
const current = Vector.magnitude(
|
||||
|
||||
Reference in New Issue
Block a user