Split ball spawner helpers
This commit is contained in:
@@ -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
181
src/spawn-balls.js
Normal 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 };
|
||||||
|
})();
|
||||||
173
src/spawn.js
173
src/spawn.js
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user