Files
Physilinks/scenes/scene-lowg.js
2025-12-13 19:59:39 +01:00

176 lines
5.6 KiB
JavaScript

(() => {
const { Bodies, Body } = Matter;
const scenes = (window.PhysilinksSceneDefs =
window.PhysilinksSceneDefs || []);
scenes.push({
id: "low-g-terraces",
name: "Low-G terraces",
config: {
gravity: 0.65,
spawnIntervalMs: 600,
minChain: 3,
palette: ["#fb7185", "#fbbf24", "#34d399", "#38bdf8"],
ballRadius: 22,
winCondition: {
type: "score",
target: 50000,
nextSceneId: "fast-drop-maze",
onWin: { shoveBalls: true },
},
link: {
stiffness: 0.6,
lengthScale: 1,
damping: 0.01,
lineWidth: 4,
rope: false,
renderType: "spring",
maxLengthMultiplier: 4.7,
},
},
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 cogRadius = Math.max(40, Math.min(w, h) * 0.085);
const cogRadiusSmall = Math.max(30, Math.min(w, h) * 0.065);
const bumperRadius = Math.max(20, Math.min(w, h) * 0.05);
const stickyWidth = Math.max(90, w * 0.12);
const stickyHeight = Math.max(14, h * 0.02);
const stickyAmplitude = w * 0.06;
const bumperAmplitude = h * 0.08;
const makeGear = (cx, cy, outerRadius, teeth, color, rotSpeed) => {
const coreRadius = outerRadius * 1.5;
const toothLength = outerRadius * 0.5;
const toothWidth = Math.max(outerRadius * 0.14, 10);
const parts = [
Bodies.circle(cx, cy, coreRadius, {
isStatic: true,
render: { fillStyle: color, strokeStyle: color },
}),
];
const step = (Math.PI * 2) / teeth;
for (let i = 0; i < teeth; i += 1) {
const angle = step * i;
const tx = cx + Math.cos(angle) * (coreRadius + toothLength / 2);
const ty = cy + Math.sin(angle) * (coreRadius + toothLength / 2);
parts.push(
Bodies.rectangle(tx, ty, toothWidth, toothLength, {
isStatic: true,
angle,
render: { fillStyle: color, strokeStyle: color },
}),
);
}
const gear = Body.create({
isStatic: true,
parts,
plugin: { rotSpeed },
});
return gear;
};
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" },
},
),
// Rotating cogs with teeth
makeGear(w * 0.32, h * 0.38, cogRadius, 10, "#f97316", 1.2),
makeGear(w * 0.64, h * 0.5, cogRadiusSmall, 12, "#a855f7", -1.6),
makeGear(w * 0.5, h * 0.32, cogRadius * 0.85, 14, "#fb7185", 1.9),
// Oscillating bumpers
Bodies.circle(w * 0.2, h * 0.46, bumperRadius, {
isStatic: true,
restitution: 1.08,
friction: 0.01,
render: { fillStyle: "#14b8a6", strokeStyle: "#14b8a6" },
plugin: {
oscillate: { axis: "y", amplitude: bumperAmplitude, speed: 1.4 },
},
}),
Bodies.circle(w * 0.5, h * 0.62, bumperRadius * 0.9, {
isStatic: true,
restitution: 1.08,
friction: 0.01,
render: { fillStyle: "#fbbf24", strokeStyle: "#fbbf24" },
plugin: {
oscillate: { axis: "x", amplitude: stickyAmplitude, speed: 1.1 },
},
}),
Bodies.circle(w * 0.8, h * 0.38, bumperRadius * 1.05, {
isStatic: true,
restitution: 1.08,
friction: 0.01,
render: { fillStyle: "#38bdf8", strokeStyle: "#38bdf8" },
plugin: {
oscillate: {
axis: "y",
amplitude: bumperAmplitude * 0.7,
speed: 1.8,
},
},
}),
// Sticky moving pads
Bodies.rectangle(w * 0.32, h * 0.72, stickyWidth, stickyHeight, {
isStatic: true,
angle: -0.08,
friction: 1.3,
render: { fillStyle: "#0ea5e9", strokeStyle: "#0ea5e9" },
plugin: {
oscillate: { axis: "x", amplitude: stickyAmplitude, speed: 0.9 },
},
}),
Bodies.rectangle(w * 0.72, h * 0.7, stickyWidth * 0.9, stickyHeight, {
isStatic: true,
angle: 0.06,
friction: 1.3,
render: { fillStyle: "#f472b6", strokeStyle: "#f472b6" },
plugin: {
oscillate: {
axis: "x",
amplitude: stickyAmplitude * 0.8,
speed: 1.3,
},
},
}),
// Random polygon obstacles
Bodies.polygon(w * 0.18, h * 0.28, 5, bumperRadius * 0.9, {
isStatic: true,
angle: 0.2,
render: { fillStyle: "#22c55e", strokeStyle: "#22c55e" },
plugin: { rotSpeed: -0.6 },
}),
Bodies.polygon(w * 0.86, h * 0.62, 7, bumperRadius * 0.95, {
isStatic: true,
angle: -0.25,
render: { fillStyle: "#c084fc", strokeStyle: "#c084fc" },
plugin: { rotSpeed: 0.9 },
}),
];
},
});
})();