Add lava lamp scene with soft-body blobs
This commit is contained in:
@@ -23,7 +23,7 @@ Physilinks is a browser-based physics linking game built with Matter.js. Match a
|
|||||||
## File structure
|
## File structure
|
||||||
- `index.html`: Shell layout and HUD overlays; loads Matter.js plus game scripts.
|
- `index.html`: Shell layout and HUD overlays; loads Matter.js plus game scripts.
|
||||||
- `styles.css`: Styling for canvas, HUD, overlays, and score popups.
|
- `styles.css`: Styling for canvas, HUD, overlays, and score popups.
|
||||||
- `scenes/`: Scene presets split per file (`scene-*.js`) plus `index.js` that registers them to `window.PhysilinksScenes`.
|
- `scenes/`: Scene presets split per file (`scene-*.js`) plus `index.js` that registers them to `window.PhysilinksScenes` (e.g., zero-G grid, balanced, low-G, fast drop, lava drift).
|
||||||
- `ui.js`: DOM access, HUD updates, overlays, popups, and control/selector wiring.
|
- `ui.js`: DOM access, HUD updates, overlays, popups, and control/selector wiring.
|
||||||
- `main.js`: Physics setup, state machine, chain interaction, spawning, scene application, and pause/restart logic.
|
- `main.js`: Physics setup, state machine, chain interaction, spawning, scene application, and pause/restart logic.
|
||||||
|
|
||||||
|
|||||||
@@ -96,6 +96,7 @@
|
|||||||
<script src="./scenes/scene-balanced.js"></script>
|
<script src="./scenes/scene-balanced.js"></script>
|
||||||
<script src="./scenes/scene-lowg.js"></script>
|
<script src="./scenes/scene-lowg.js"></script>
|
||||||
<script src="./scenes/scene-fastdrop.js"></script>
|
<script src="./scenes/scene-fastdrop.js"></script>
|
||||||
|
<script src="./scenes/scene-lavalamp.js"></script>
|
||||||
<script src="./scenes/index.js"></script>
|
<script src="./scenes/index.js"></script>
|
||||||
<script src="./ui.js"></script>
|
<script src="./ui.js"></script>
|
||||||
<script src="./main.js"></script>
|
<script src="./main.js"></script>
|
||||||
|
|||||||
122
main.js
122
main.js
@@ -78,6 +78,7 @@
|
|||||||
// Static boundaries and scene-specific obstacles.
|
// Static boundaries and scene-specific obstacles.
|
||||||
let boundaries = [];
|
let boundaries = [];
|
||||||
let rotators = [];
|
let rotators = [];
|
||||||
|
let oscillators = [];
|
||||||
let currentScene =
|
let currentScene =
|
||||||
scenes.find((s) => s.id === defaultSceneId) || scenes[0] || null;
|
scenes.find((s) => s.id === defaultSceneId) || scenes[0] || null;
|
||||||
|
|
||||||
@@ -90,6 +91,7 @@
|
|||||||
boundaries.forEach((b) => World.remove(world, b));
|
boundaries.forEach((b) => World.remove(world, b));
|
||||||
boundaries = currentScene.createBodies(width, height);
|
boundaries = currentScene.createBodies(width, height);
|
||||||
rotators = boundaries.filter((b) => b.plugin && b.plugin.rotSpeed);
|
rotators = boundaries.filter((b) => b.plugin && b.plugin.rotSpeed);
|
||||||
|
oscillators = boundaries.filter((b) => b.plugin && b.plugin.oscillate);
|
||||||
World.add(world, boundaries);
|
World.add(world, boundaries);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -176,18 +178,18 @@
|
|||||||
config.ballRadius + 10,
|
config.ballRadius + 10,
|
||||||
Math.min(width - config.ballRadius - 10, Math.random() * width),
|
Math.min(width - config.ballRadius - 10, Math.random() * width),
|
||||||
);
|
);
|
||||||
const y = -config.ballRadius * 2;
|
const spawnFromBottom = currentScene?.config?.spawnFrom === "bottom";
|
||||||
const ball = Bodies.circle(x, y, config.ballRadius, {
|
const y = spawnFromBottom
|
||||||
restitution: 0.72,
|
? height + config.ballRadius * 2
|
||||||
friction: 0.01,
|
: -config.ballRadius * 2;
|
||||||
frictionAir: 0.015,
|
const ball = createBallBody(x, y, color);
|
||||||
render: {
|
ball.plugin = {
|
||||||
fillStyle: color,
|
color,
|
||||||
strokeStyle: "#0b1222",
|
hasEntered: false,
|
||||||
lineWidth: 2,
|
entryCheckId: null,
|
||||||
},
|
squishX: 1,
|
||||||
});
|
squishY: 1,
|
||||||
ball.plugin = { color, hasEntered: false, entryCheckId: null };
|
};
|
||||||
balls.push(ball);
|
balls.push(ball);
|
||||||
World.add(world, ball);
|
World.add(world, ball);
|
||||||
ball.plugin.entryCheckId = setTimeout(() => {
|
ball.plugin.entryCheckId = setTimeout(() => {
|
||||||
@@ -352,6 +354,28 @@
|
|||||||
if (typeof winCond.onWin.setGravity === "number") {
|
if (typeof winCond.onWin.setGravity === "number") {
|
||||||
engine.gravity.y = winCond.onWin.setGravity;
|
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 = () => {
|
const checkWinCondition = () => {
|
||||||
@@ -578,6 +602,34 @@
|
|||||||
config.ballRadius = nextRadius;
|
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 getGoalState = () => {
|
||||||
const winCond = currentScene?.config?.winCondition;
|
const winCond = currentScene?.config?.winCondition;
|
||||||
if (!winCond) return null;
|
if (!winCond) return null;
|
||||||
@@ -669,11 +721,17 @@
|
|||||||
if (
|
if (
|
||||||
ball.position.x < -100 ||
|
ball.position.x < -100 ||
|
||||||
ball.position.x > width + 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);
|
cleanupBall(ball);
|
||||||
ball.plugin.hasEntered = true;
|
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 });
|
Matter.Body.setVelocity(ball, { x: 0, y: 0 });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -705,6 +763,42 @@
|
|||||||
Body.rotate(b, speed * ((dt * timeScale) / 1000));
|
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", () => {
|
Events.on(render, "afterRender", () => {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
(() => {
|
(() => {
|
||||||
const { Bodies } = Matter;
|
const { Bodies } = Matter;
|
||||||
const scenes =
|
const scenes = (window.PhysilinksSceneDefs =
|
||||||
(window.PhysilinksSceneDefs = window.PhysilinksSceneDefs || []);
|
window.PhysilinksSceneDefs || []);
|
||||||
|
|
||||||
scenes.push({
|
scenes.push({
|
||||||
id: "scene1",
|
id: "scene1",
|
||||||
@@ -12,6 +12,12 @@
|
|||||||
minChain: 3,
|
minChain: 3,
|
||||||
palette: ["#ff595e", "#ffca3a", "#8ac926", "#1982c4", "#6a4c93"],
|
palette: ["#ff595e", "#ffca3a", "#8ac926", "#1982c4", "#6a4c93"],
|
||||||
ballRadius: 38,
|
ballRadius: 38,
|
||||||
|
winCondition: {
|
||||||
|
type: "score",
|
||||||
|
target: 10000,
|
||||||
|
onWin: { shoveBalls: true },
|
||||||
|
nextSceneId: "scene2",
|
||||||
|
},
|
||||||
link: {
|
link: {
|
||||||
stiffness: 0.85,
|
stiffness: 0.85,
|
||||||
lengthScale: 1.05,
|
lengthScale: 1.05,
|
||||||
@@ -41,16 +47,10 @@
|
|||||||
render: { fillStyle: "#0ea5e9", strokeStyle: "#0ea5e9" },
|
render: { fillStyle: "#0ea5e9", strokeStyle: "#0ea5e9" },
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
Bodies.rectangle(
|
Bodies.rectangle(-wallThickness / 2, h / 2, wallThickness, wallHeight, {
|
||||||
-wallThickness / 2,
|
isStatic: true,
|
||||||
h / 2,
|
render: { fillStyle: "#f97316", strokeStyle: "#f97316" },
|
||||||
wallThickness,
|
}),
|
||||||
wallHeight,
|
|
||||||
{
|
|
||||||
isStatic: true,
|
|
||||||
render: { fillStyle: "#f97316", strokeStyle: "#f97316" },
|
|
||||||
},
|
|
||||||
),
|
|
||||||
Bodies.rectangle(
|
Bodies.rectangle(
|
||||||
w + wallThickness / 2,
|
w + wallThickness / 2,
|
||||||
h / 2,
|
h / 2,
|
||||||
|
|||||||
97
scenes/scene-lavalamp.js
Normal file
97
scenes/scene-lavalamp.js
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
(() => {
|
||||||
|
const { Bodies, Body } = Matter;
|
||||||
|
const scenes = (window.PhysilinksSceneDefs =
|
||||||
|
window.PhysilinksSceneDefs || []);
|
||||||
|
|
||||||
|
const makeCurveSegments = (cx, h, amp, thickness, segments) => {
|
||||||
|
const segs = [];
|
||||||
|
const stepY = h / segments;
|
||||||
|
let prevX = cx;
|
||||||
|
let prevY = 0;
|
||||||
|
for (let i = 0; i < segments; i += 1) {
|
||||||
|
const y = stepY * (i + 1);
|
||||||
|
const t = y / h;
|
||||||
|
const x = cx + Math.sin(t * Math.PI * 1.5) * amp;
|
||||||
|
const dx = x - prevX;
|
||||||
|
const dy = y - prevY;
|
||||||
|
const len = Math.max(1, Math.sqrt(dx * dx + dy * dy));
|
||||||
|
const angle = Math.atan2(dy, dx);
|
||||||
|
segs.push(
|
||||||
|
Bodies.rectangle((prevX + x) / 2, (prevY + y) / 2, thickness, len, {
|
||||||
|
isStatic: true,
|
||||||
|
angle,
|
||||||
|
render: { fillStyle: "#14213a", strokeStyle: "#14213a" },
|
||||||
|
plugin: { curve: true },
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
prevX = x;
|
||||||
|
prevY = y;
|
||||||
|
}
|
||||||
|
return segs;
|
||||||
|
};
|
||||||
|
|
||||||
|
scenes.push({
|
||||||
|
id: "scene-lava",
|
||||||
|
name: "Lava drift",
|
||||||
|
config: {
|
||||||
|
gravity: -0.1,
|
||||||
|
spawnIntervalMs: 180,
|
||||||
|
spawnFrom: "bottom",
|
||||||
|
autoSpawn: true,
|
||||||
|
minChain: 3,
|
||||||
|
palette: ["#f472b6", "#38bdf8", "#fbbf24", "#a855f7", "#22c55e"],
|
||||||
|
ballRadius: 26,
|
||||||
|
blobBalls: true,
|
||||||
|
winCondition: {
|
||||||
|
type: "score",
|
||||||
|
target: 25000,
|
||||||
|
onWin: { setGravity: -0.55, removeCurves: true },
|
||||||
|
nextSceneId: "scene-grid",
|
||||||
|
},
|
||||||
|
link: {
|
||||||
|
stiffness: 0.7,
|
||||||
|
lengthScale: 1.05,
|
||||||
|
damping: 0.12,
|
||||||
|
lineWidth: 3,
|
||||||
|
rope: true,
|
||||||
|
renderType: "line",
|
||||||
|
maxLengthMultiplier: 4.8,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
createBodies: (w, h) => {
|
||||||
|
const wallThickness = Math.max(24, w * 0.05);
|
||||||
|
const amp = Math.max(40, w * 0.08);
|
||||||
|
const segments = 12;
|
||||||
|
const curves = [
|
||||||
|
...makeCurveSegments(w * 0.33, h, amp, wallThickness, segments),
|
||||||
|
...makeCurveSegments(w * 0.67, h, amp * 0.95, wallThickness, segments),
|
||||||
|
];
|
||||||
|
|
||||||
|
// Gentle paddles that sway slightly
|
||||||
|
/*
|
||||||
|
const paddleWidth = Math.max(120, w * 0.18);
|
||||||
|
const paddleHeight = Math.max(12, h * 0.018);
|
||||||
|
const paddles = [
|
||||||
|
Bodies.rectangle(w * 0.45, h * 0.65, paddleWidth, paddleHeight, {
|
||||||
|
isStatic: true,
|
||||||
|
angle: -0.08,
|
||||||
|
render: { fillStyle: "#0ea5e9", strokeStyle: "#0ea5e9" },
|
||||||
|
plugin: {
|
||||||
|
oscillate: { axis: "x", amplitude: w * 0.05, speed: 0.7 },
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
Bodies.rectangle(w * 0.58, h * 0.4, paddleWidth * 0.85, paddleHeight, {
|
||||||
|
isStatic: true,
|
||||||
|
angle: 0.12,
|
||||||
|
render: { fillStyle: "#f59e0b", strokeStyle: "#f59e0b" },
|
||||||
|
plugin: {
|
||||||
|
oscillate: { axis: "x", amplitude: w * 0.04, speed: 1.0 },
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
*/
|
||||||
|
|
||||||
|
return [...curves];
|
||||||
|
},
|
||||||
|
});
|
||||||
|
})();
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
(() => {
|
(() => {
|
||||||
const { Bodies } = Matter;
|
const { Bodies, Body } = Matter;
|
||||||
const scenes =
|
const scenes = (window.PhysilinksSceneDefs =
|
||||||
(window.PhysilinksSceneDefs = window.PhysilinksSceneDefs || []);
|
window.PhysilinksSceneDefs || []);
|
||||||
|
|
||||||
scenes.push({
|
scenes.push({
|
||||||
id: "scene2",
|
id: "scene2",
|
||||||
@@ -12,6 +12,12 @@
|
|||||||
minChain: 3,
|
minChain: 3,
|
||||||
palette: ["#fb7185", "#fbbf24", "#34d399", "#38bdf8"],
|
palette: ["#fb7185", "#fbbf24", "#34d399", "#38bdf8"],
|
||||||
ballRadius: 22,
|
ballRadius: 22,
|
||||||
|
winCondition: {
|
||||||
|
type: "score",
|
||||||
|
target: 50000,
|
||||||
|
nextSceneId: "scene3",
|
||||||
|
onWin: { shoveBalls: true },
|
||||||
|
},
|
||||||
link: {
|
link: {
|
||||||
stiffness: 0.6,
|
stiffness: 0.6,
|
||||||
lengthScale: 1,
|
lengthScale: 1,
|
||||||
@@ -26,10 +32,45 @@
|
|||||||
const floorHeight = Math.max(70, h * 0.12);
|
const floorHeight = Math.max(70, h * 0.12);
|
||||||
const wallThickness = Math.max(32, w * 0.05);
|
const wallThickness = Math.max(32, w * 0.05);
|
||||||
const wallHeight = h * 1.8;
|
const wallHeight = h * 1.8;
|
||||||
const ledgeHeight = Math.max(14, h * 0.022);
|
const cogRadius = Math.max(40, Math.min(w, h) * 0.085);
|
||||||
const leftWidth = Math.max(160, w * 0.18);
|
const cogRadiusSmall = Math.max(30, Math.min(w, h) * 0.065);
|
||||||
const midWidth = Math.max(190, w * 0.26);
|
const bumperRadius = Math.max(20, Math.min(w, h) * 0.05);
|
||||||
const rightWidth = Math.max(150, w * 0.18);
|
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 [
|
return [
|
||||||
Bodies.rectangle(
|
Bodies.rectangle(
|
||||||
w / 2,
|
w / 2,
|
||||||
@@ -42,16 +83,10 @@
|
|||||||
render: { fillStyle: "#0ea5e9", strokeStyle: "#0ea5e9" },
|
render: { fillStyle: "#0ea5e9", strokeStyle: "#0ea5e9" },
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
Bodies.rectangle(
|
Bodies.rectangle(-wallThickness / 2, h / 2, wallThickness, wallHeight, {
|
||||||
-wallThickness / 2,
|
isStatic: true,
|
||||||
h / 2,
|
render: { fillStyle: "#7c3aed", strokeStyle: "#7c3aed" },
|
||||||
wallThickness,
|
}),
|
||||||
wallHeight,
|
|
||||||
{
|
|
||||||
isStatic: true,
|
|
||||||
render: { fillStyle: "#7c3aed", strokeStyle: "#7c3aed" },
|
|
||||||
},
|
|
||||||
),
|
|
||||||
Bodies.rectangle(
|
Bodies.rectangle(
|
||||||
w + wallThickness / 2,
|
w + wallThickness / 2,
|
||||||
h / 2,
|
h / 2,
|
||||||
@@ -62,20 +97,77 @@
|
|||||||
render: { fillStyle: "#7c3aed", strokeStyle: "#7c3aed" },
|
render: { fillStyle: "#7c3aed", strokeStyle: "#7c3aed" },
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
Bodies.rectangle(w * 0.2, h * 0.45, leftWidth, ledgeHeight, {
|
// 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,
|
isStatic: true,
|
||||||
angle: 0.08,
|
restitution: 1.08,
|
||||||
render: { fillStyle: "#f97316", strokeStyle: "#f97316" },
|
friction: 0.01,
|
||||||
}),
|
|
||||||
Bodies.rectangle(w * 0.5, h * 0.6, midWidth, ledgeHeight, {
|
|
||||||
isStatic: true,
|
|
||||||
angle: -0.04,
|
|
||||||
render: { fillStyle: "#14b8a6", strokeStyle: "#14b8a6" },
|
render: { fillStyle: "#14b8a6", strokeStyle: "#14b8a6" },
|
||||||
|
plugin: {
|
||||||
|
oscillate: { axis: "y", amplitude: bumperAmplitude, speed: 1.4 },
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
Bodies.rectangle(w * 0.8, h * 0.42, rightWidth, ledgeHeight, {
|
Bodies.circle(w * 0.5, h * 0.62, bumperRadius * 0.9, {
|
||||||
isStatic: true,
|
isStatic: true,
|
||||||
angle: 0.14,
|
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" },
|
render: { fillStyle: "#c084fc", strokeStyle: "#c084fc" },
|
||||||
|
plugin: { rotSpeed: 0.9 },
|
||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user