Add lava lamp scene with soft-body blobs
This commit is contained in:
122
main.js
122
main.js
@@ -78,6 +78,7 @@
|
||||
// Static boundaries and scene-specific obstacles.
|
||||
let boundaries = [];
|
||||
let rotators = [];
|
||||
let oscillators = [];
|
||||
let currentScene =
|
||||
scenes.find((s) => s.id === defaultSceneId) || scenes[0] || null;
|
||||
|
||||
@@ -90,6 +91,7 @@
|
||||
boundaries.forEach((b) => World.remove(world, b));
|
||||
boundaries = currentScene.createBodies(width, height);
|
||||
rotators = boundaries.filter((b) => b.plugin && b.plugin.rotSpeed);
|
||||
oscillators = boundaries.filter((b) => b.plugin && b.plugin.oscillate);
|
||||
World.add(world, boundaries);
|
||||
};
|
||||
|
||||
@@ -176,18 +178,18 @@
|
||||
config.ballRadius + 10,
|
||||
Math.min(width - config.ballRadius - 10, Math.random() * width),
|
||||
);
|
||||
const y = -config.ballRadius * 2;
|
||||
const ball = Bodies.circle(x, y, config.ballRadius, {
|
||||
restitution: 0.72,
|
||||
friction: 0.01,
|
||||
frictionAir: 0.015,
|
||||
render: {
|
||||
fillStyle: color,
|
||||
strokeStyle: "#0b1222",
|
||||
lineWidth: 2,
|
||||
},
|
||||
});
|
||||
ball.plugin = { color, hasEntered: false, entryCheckId: null };
|
||||
const spawnFromBottom = currentScene?.config?.spawnFrom === "bottom";
|
||||
const y = spawnFromBottom
|
||||
? height + config.ballRadius * 2
|
||||
: -config.ballRadius * 2;
|
||||
const ball = createBallBody(x, y, color);
|
||||
ball.plugin = {
|
||||
color,
|
||||
hasEntered: false,
|
||||
entryCheckId: null,
|
||||
squishX: 1,
|
||||
squishY: 1,
|
||||
};
|
||||
balls.push(ball);
|
||||
World.add(world, ball);
|
||||
ball.plugin.entryCheckId = setTimeout(() => {
|
||||
@@ -352,6 +354,28 @@
|
||||
if (typeof winCond.onWin.setGravity === "number") {
|
||||
engine.gravity.y = winCond.onWin.setGravity;
|
||||
}
|
||||
if (winCond.onWin.shoveBalls) {
|
||||
balls.forEach((ball) => {
|
||||
const angle = Math.random() * Math.PI * 2;
|
||||
const magnitude = 12 + Math.random() * 10;
|
||||
const force = {
|
||||
x: Math.cos(angle) * magnitude,
|
||||
y: Math.sin(angle) * magnitude,
|
||||
};
|
||||
Body.applyForce(ball, ball.position, force);
|
||||
});
|
||||
}
|
||||
if (winCond.onWin.removeCurves) {
|
||||
const remaining = [];
|
||||
boundaries.forEach((b) => {
|
||||
if (b.plugin && b.plugin.curve) {
|
||||
World.remove(world, b);
|
||||
} else {
|
||||
remaining.push(b);
|
||||
}
|
||||
});
|
||||
boundaries = remaining;
|
||||
}
|
||||
};
|
||||
|
||||
const checkWinCondition = () => {
|
||||
@@ -578,6 +602,34 @@
|
||||
config.ballRadius = nextRadius;
|
||||
};
|
||||
|
||||
const createBallBody = (x, y, color) => {
|
||||
const commonOpts = {
|
||||
restitution: 0.72,
|
||||
friction: 0.01,
|
||||
frictionAir: 0.015,
|
||||
render: {
|
||||
fillStyle: color,
|
||||
strokeStyle: "#0b1222",
|
||||
lineWidth: 2,
|
||||
},
|
||||
};
|
||||
if (currentScene?.config?.blobBalls) {
|
||||
const points = [];
|
||||
const segments = 12;
|
||||
for (let i = 0; i < segments; i += 1) {
|
||||
const angle = (i / segments) * Math.PI * 2;
|
||||
const variance = 0.75 + Math.random() * 0.35;
|
||||
const r = config.ballRadius * variance;
|
||||
points.push({
|
||||
x: x + Math.cos(angle) * r,
|
||||
y: y + Math.sin(angle) * r,
|
||||
});
|
||||
}
|
||||
return Bodies.fromVertices(x, y, [points], commonOpts);
|
||||
}
|
||||
return Bodies.circle(x, y, config.ballRadius, commonOpts);
|
||||
};
|
||||
|
||||
const getGoalState = () => {
|
||||
const winCond = currentScene?.config?.winCondition;
|
||||
if (!winCond) return null;
|
||||
@@ -669,11 +721,17 @@
|
||||
if (
|
||||
ball.position.x < -100 ||
|
||||
ball.position.x > width + 100 ||
|
||||
ball.position.y > height + 500
|
||||
(currentScene?.config?.spawnFrom === "bottom"
|
||||
? ball.position.y < -500
|
||||
: ball.position.y > height + 500)
|
||||
) {
|
||||
cleanupBall(ball);
|
||||
ball.plugin.hasEntered = true;
|
||||
Matter.Body.setPosition(ball, { x: Math.random() * width, y: -40 });
|
||||
const spawnFromBottom = currentScene?.config?.spawnFrom === "bottom";
|
||||
Matter.Body.setPosition(ball, {
|
||||
x: Math.random() * width,
|
||||
y: spawnFromBottom ? height + 40 : -40,
|
||||
});
|
||||
Matter.Body.setVelocity(ball, { x: 0, y: 0 });
|
||||
}
|
||||
});
|
||||
@@ -705,6 +763,42 @@
|
||||
Body.rotate(b, speed * ((dt * timeScale) / 1000));
|
||||
}
|
||||
});
|
||||
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 (currentScene?.config?.blobBalls) {
|
||||
balls.forEach((ball) => {
|
||||
if (!ball.plugin) return;
|
||||
const speed = Vector.magnitude(ball.velocity || { x: 0, y: 0 });
|
||||
const squeeze = Math.min(0.22, speed / 20);
|
||||
const targetX = 1 + squeeze;
|
||||
const targetY = Math.max(0.7, 1 - squeeze);
|
||||
const currX = ball.plugin.squishX || 1;
|
||||
const currY = ball.plugin.squishY || 1;
|
||||
const factorX = targetX / currX;
|
||||
const factorY = targetY / currY;
|
||||
if (Math.abs(factorX - 1) > 0.02 || Math.abs(factorY - 1) > 0.02) {
|
||||
Body.scale(ball, factorX, factorY);
|
||||
ball.plugin.squishX = targetX;
|
||||
ball.plugin.squishY = targetY;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
Events.on(render, "afterRender", () => {
|
||||
|
||||
Reference in New Issue
Block a user