diff --git a/index.html b/index.html index dc7d676..b789be7 100644 --- a/index.html +++ b/index.html @@ -122,6 +122,7 @@ + diff --git a/src/spawn-balls.js b/src/spawn-balls.js new file mode 100644 index 0000000..6079741 --- /dev/null +++ b/src/spawn-balls.js @@ -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 }; +})(); diff --git a/src/spawn.js b/src/spawn.js index aeed323..b4d9022 100644 --- a/src/spawn.js +++ b/src/spawn.js @@ -1,5 +1,5 @@ (() => { - const { World, Bodies, Composites, Query, Body } = Matter; + const { World, Bodies, Query, Body } = Matter; const create = ({ config, @@ -117,173 +117,10 @@ config.ballRadius = nextRadius; }; - 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); - }; + const createBallBodies = window.PhysilinksSpawnBalls?.create({ + config, + getCurrentScene, + })?.createBallBodies; const spawnBall = () => { if (isGameOver()) return;