Split ball spawner helpers

This commit is contained in:
Daddy32
2025-12-29 23:24:44 +01:00
parent bbea27a3f5
commit 96e8cd4f02
3 changed files with 187 additions and 168 deletions

View File

@@ -122,6 +122,7 @@
<script src="./src/scene-registry.js"></script> <script src="./src/scene-registry.js"></script>
<script src="./src/ui.js"></script> <script src="./src/ui.js"></script>
<script src="./src/storage.js"></script> <script src="./src/storage.js"></script>
<script src="./src/spawn-balls.js"></script>
<script src="./src/spawn.js"></script> <script src="./src/spawn.js"></script>
<script src="./src/goals.js"></script> <script src="./src/goals.js"></script>
<script src="./src/chain-controller.js"></script> <script src="./src/chain-controller.js"></script>

181
src/spawn-balls.js Normal file
View File

@@ -0,0 +1,181 @@
(() => {
const { Bodies, Composites, Body } = Matter;
const create = ({ config, getCurrentScene }) => {
const applyBallPlugin = (body, { color, shape, blobId } = {}) => {
body.plugin = {
color,
hasEntered: false,
entryCheckId: null,
};
if (shape) body.plugin.shape = shape;
if (blobId) body.plugin.blobId = blobId;
};
const getCommonBallOpts = (scene, color) => {
const ballPhysics = scene?.config?.ballPhysics || {};
return {
restitution: ballPhysics.restitution ?? 0.72,
friction: ballPhysics.friction ?? 0.01,
frictionAir: ballPhysics.frictionAir ?? 0.012,
frictionStatic: ballPhysics.frictionStatic ?? 0,
density: ballPhysics.density ?? 0.001,
render: {
fillStyle: color,
strokeStyle: "#0b1222",
lineWidth: 2,
},
};
};
const createSoftBlob = (x, y, color, commonOpts) => {
const cols = 3;
const rows = 2;
const radius = Math.max(10, config.ballRadius * 0.55);
const soft = Composites.softBody(
x - cols * radius * 1.2,
y - rows * radius * 1.2,
cols,
rows,
0,
0,
true,
radius,
commonOpts,
);
const blobId = `blob-${Date.now()}-${Math.random()
.toString(16)
.slice(2)}`;
soft.bodies.forEach((b) => applyBallPlugin(b, { color, blobId }));
soft.constraints.forEach((c) => {
c.plugin = { blobId, blobConstraint: true };
c.render = c.render || {};
c.render.type = "line";
});
return { bodies: soft.bodies, constraints: soft.constraints, blobId };
};
const createJaggedBall = (x, y, color, commonOpts) => {
const points = [];
const segments = 6;
for (let i = 0; i < segments; i += 1) {
const angle = Math.min((i / segments) * Math.PI * 2, Math.PI * 2);
const variance = 0.6 + Math.random() * 0.5;
const r = config.ballRadius * variance;
points.push({
x: x + Math.cos(angle) * r,
y: y + Math.sin(angle) * r,
});
}
const body = Bodies.fromVertices(x, y, [points], commonOpts, true);
applyBallPlugin(body, { color, shape: "jagged" });
return { bodies: [body], constraints: [], blobId: null };
};
const createGiftBall = (x, y, color, commonOpts, scene, debugSpawn) => {
const size = config.ballRadius * 2;
const ribbonSize = Math.max(4, config.ballRadius * 0.35);
const ribbonColor = scene?.config?.giftRibbonColor || "#f8fafc";
if (debugSpawn) {
console.log("Spawn gift", {
sceneId: scene?.id,
x,
y,
color,
size,
});
}
const base = Bodies.rectangle(x, y, size, size, {
...commonOpts,
chamfer: { radius: Math.max(2, config.ballRadius * 0.18) },
});
const ribbonRender = {
fillStyle: ribbonColor,
strokeStyle: "#0b1222",
lineWidth: 2,
};
const verticalRibbon = Bodies.rectangle(x, y, ribbonSize, size * 1.02, {
render: ribbonRender,
});
const horizontalRibbon = Bodies.rectangle(
x,
y,
size * 1.02,
ribbonSize,
{
render: ribbonRender,
},
);
const bow = Bodies.rectangle(
x,
y - size * 0.34,
ribbonSize * 1.4,
ribbonSize * 0.8,
{
render: ribbonRender,
},
);
const body = Body.create({
parts: [base, verticalRibbon, horizontalRibbon, bow],
});
Body.setPosition(body, { x, y });
body.restitution = commonOpts.restitution;
body.friction = commonOpts.friction;
body.frictionAir = commonOpts.frictionAir;
body.frictionStatic = commonOpts.frictionStatic;
body.density = commonOpts.density;
body.render = {
...body.render,
...commonOpts.render,
visible: true,
};
applyBallPlugin(body, { color, shape: "gift" });
return { bodies: [body], constraints: [], blobId: null };
};
const createRectBall = (x, y, color, commonOpts) => {
const side = config.ballRadius * 2;
const body = Bodies.rectangle(x, y, side, side, {
...commonOpts,
chamfer: 0,
});
applyBallPlugin(body, { color, shape: "rect" });
return { bodies: [body], constraints: [], blobId: null };
};
const createCircleBall = (x, y, color, commonOpts) => {
const body = Bodies.circle(x, y, config.ballRadius, commonOpts);
applyBallPlugin(body, { color, shape: "circle" });
return { bodies: [body], constraints: [], blobId: null };
};
const ballShapeFactories = {
gift: createGiftBall,
rect: createRectBall,
circle: createCircleBall,
};
const createBallBodies = (x, y, color) => {
const scene = getCurrentScene();
const debugSpawn = !!scene?.config?.debugSpawn;
const commonOpts = getCommonBallOpts(scene, color);
if (scene?.config?.blobBalls === "soft") {
return createSoftBlob(x, y, color, commonOpts);
}
if (scene?.config?.blobBalls === "jagged") {
return createJaggedBall(x, y, color, commonOpts);
}
const shape = scene?.config?.ballShape || "circle";
const factory = ballShapeFactories[shape] || createCircleBall;
return factory(x, y, color, commonOpts, scene, debugSpawn);
};
return {
createBallBodies,
applyBallPlugin,
getCommonBallOpts,
};
};
window.PhysilinksSpawnBalls = { create };
})();

View File

@@ -1,5 +1,5 @@
(() => { (() => {
const { World, Bodies, Composites, Query, Body } = Matter; const { World, Bodies, Query, Body } = Matter;
const create = ({ const create = ({
config, config,
@@ -117,173 +117,10 @@
config.ballRadius = nextRadius; config.ballRadius = nextRadius;
}; };
const applyBallPlugin = (body, { color, shape, blobId } = {}) => { const createBallBodies = window.PhysilinksSpawnBalls?.create({
body.plugin = { config,
color, getCurrentScene,
hasEntered: false, })?.createBallBodies;
entryCheckId: null,
};
if (shape) body.plugin.shape = shape;
if (blobId) body.plugin.blobId = blobId;
};
const getCommonBallOpts = (scene, color) => {
const ballPhysics = scene?.config?.ballPhysics || {};
return {
restitution: ballPhysics.restitution ?? 0.72,
friction: ballPhysics.friction ?? 0.01,
frictionAir: ballPhysics.frictionAir ?? 0.012,
frictionStatic: ballPhysics.frictionStatic ?? 0,
density: ballPhysics.density ?? 0.001,
render: {
fillStyle: color,
strokeStyle: "#0b1222",
lineWidth: 2,
},
};
};
const createSoftBlob = (x, y, color, commonOpts) => {
const cols = 3;
const rows = 2;
const radius = Math.max(10, config.ballRadius * 0.55);
const soft = Composites.softBody(
x - cols * radius * 1.2,
y - rows * radius * 1.2,
cols,
rows,
0,
0,
true,
radius,
commonOpts,
);
const blobId = `blob-${Date.now()}-${Math.random()
.toString(16)
.slice(2)}`;
soft.bodies.forEach((b) => applyBallPlugin(b, { color, blobId }));
soft.constraints.forEach((c) => {
c.plugin = { blobId, blobConstraint: true };
c.render = c.render || {};
c.render.type = "line";
});
return { bodies: soft.bodies, constraints: soft.constraints, blobId };
};
const createJaggedBall = (x, y, color, commonOpts) => {
const points = [];
const segments = 6;
for (let i = 0; i < segments; i += 1) {
const angle = Math.min((i / segments) * Math.PI * 2, Math.PI * 2);
const variance = 0.6 + Math.random() * 0.5;
const r = config.ballRadius * variance;
points.push({
x: x + Math.cos(angle) * r,
y: y + Math.sin(angle) * r,
});
}
const body = Bodies.fromVertices(x, y, [points], commonOpts, true);
applyBallPlugin(body, { color, shape: "jagged" });
return { bodies: [body], constraints: [], blobId: null };
};
const createGiftBall = (x, y, color, commonOpts, scene, debugSpawn) => {
const size = config.ballRadius * 2;
const ribbonSize = Math.max(4, config.ballRadius * 0.35);
const ribbonColor = scene?.config?.giftRibbonColor || "#f8fafc";
if (debugSpawn) {
console.log("Spawn gift", {
sceneId: scene?.id,
x,
y,
color,
size,
});
}
const base = Bodies.rectangle(x, y, size, size, {
...commonOpts,
chamfer: { radius: Math.max(2, config.ballRadius * 0.18) },
});
const ribbonRender = {
fillStyle: ribbonColor,
strokeStyle: "#0b1222",
lineWidth: 2,
};
const verticalRibbon = Bodies.rectangle(x, y, ribbonSize, size * 1.02, {
render: ribbonRender,
});
const horizontalRibbon = Bodies.rectangle(
x,
y,
size * 1.02,
ribbonSize,
{
render: ribbonRender,
},
);
const bow = Bodies.rectangle(
x,
y - size * 0.34,
ribbonSize * 1.4,
ribbonSize * 0.8,
{
render: ribbonRender,
},
);
const body = Body.create({
parts: [base, verticalRibbon, horizontalRibbon, bow],
});
Body.setPosition(body, { x, y });
body.restitution = commonOpts.restitution;
body.friction = commonOpts.friction;
body.frictionAir = commonOpts.frictionAir;
body.frictionStatic = commonOpts.frictionStatic;
body.density = commonOpts.density;
body.render = {
...body.render,
...commonOpts.render,
visible: true,
};
applyBallPlugin(body, { color, shape: "gift" });
return { bodies: [body], constraints: [], blobId: null };
};
const createRectBall = (x, y, color, commonOpts) => {
const side = config.ballRadius * 2;
const body = Bodies.rectangle(x, y, side, side, {
...commonOpts,
chamfer: 0,
});
applyBallPlugin(body, { color, shape: "rect" });
return { bodies: [body], constraints: [], blobId: null };
};
const createCircleBall = (x, y, color, commonOpts) => {
const body = Bodies.circle(x, y, config.ballRadius, commonOpts);
applyBallPlugin(body, { color, shape: "circle" });
return { bodies: [body], constraints: [], blobId: null };
};
const ballShapeFactories = {
gift: createGiftBall,
rect: createRectBall,
circle: createCircleBall,
};
const createBallBodies = (x, y, color) => {
const scene = getCurrentScene();
const debugSpawn = !!scene?.config?.debugSpawn;
const commonOpts = getCommonBallOpts(scene, color);
if (scene?.config?.blobBalls === "soft") {
return createSoftBlob(x, y, color, commonOpts);
}
if (scene?.config?.blobBalls === "jagged") {
return createJaggedBall(x, y, color, commonOpts);
}
const shape = scene?.config?.ballShape || "circle";
const factory = ballShapeFactories[shape] || createCircleBall;
return factory(x, y, color, commonOpts, scene, debugSpawn);
};
const spawnBall = () => { const spawnBall = () => {
if (isGameOver()) return; if (isGameOver()) return;