Add shared clear animation sizing and shatter support
This commit is contained in:
@@ -30,6 +30,7 @@
|
|||||||
endScale: 1.8,
|
endScale: 1.8,
|
||||||
startOpacity: 0.95,
|
startOpacity: 0.95,
|
||||||
endOpacity: 0,
|
endOpacity: 0,
|
||||||
|
sizeScale: 1,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
messages: { ...defaultMessageConfig },
|
messages: { ...defaultMessageConfig },
|
||||||
|
|||||||
@@ -25,6 +25,14 @@
|
|||||||
rope: true,
|
rope: true,
|
||||||
renderType: "line",
|
renderType: "line",
|
||||||
maxLengthMultiplier: 3.2,
|
maxLengthMultiplier: 3.2,
|
||||||
|
clearAnimation: {
|
||||||
|
type: "pop",
|
||||||
|
durationMs: 220,
|
||||||
|
startScale: 0.9,
|
||||||
|
endScale: 4.4,
|
||||||
|
startOpacity: 0.66,
|
||||||
|
endOpacity: 0,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
createBodies: (w, h) => {
|
createBodies: (w, h) => {
|
||||||
|
|||||||
@@ -55,6 +55,11 @@
|
|||||||
rope: true,
|
rope: true,
|
||||||
renderType: "line",
|
renderType: "line",
|
||||||
maxLengthMultiplier: 5.8,
|
maxLengthMultiplier: 5.8,
|
||||||
|
clearAnimation: {
|
||||||
|
type: "shatter",
|
||||||
|
durationMs: 380,
|
||||||
|
sizeScale: 1.2,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
createBodies: (w, h) => {
|
createBodies: (w, h) => {
|
||||||
|
|||||||
@@ -36,6 +36,11 @@
|
|||||||
rope: true,
|
rope: true,
|
||||||
renderType: "line",
|
renderType: "line",
|
||||||
maxLengthMultiplier: 2.5,
|
maxLengthMultiplier: 2.5,
|
||||||
|
clearAnimation: {
|
||||||
|
type: "shatter",
|
||||||
|
durationMs: 380,
|
||||||
|
sizeScale: 2.2,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
createBodies: (w, h) => {
|
createBodies: (w, h) => {
|
||||||
|
|||||||
@@ -62,12 +62,13 @@
|
|||||||
maxLinkLength: null, // optional absolute pixel cap for link reach
|
maxLinkLength: null, // optional absolute pixel cap for link reach
|
||||||
clearAnimation: {
|
clearAnimation: {
|
||||||
// Visual “bling” when a chain clears. Defaults to a quick pop/fade.
|
// Visual “bling” when a chain clears. Defaults to a quick pop/fade.
|
||||||
type: "pop", // built-in default animation type
|
type: "pop", // built-in animation type ("pop" | "shatter")
|
||||||
durationMs: 320, // total animation time
|
durationMs: 320, // total animation time
|
||||||
startScale: 1, // initial scale multiplier
|
startScale: 1, // initial scale multiplier
|
||||||
endScale: 1.8, // final scale multiplier
|
endScale: 1.8, // final scale multiplier
|
||||||
startOpacity: 0.95, // starting opacity
|
startOpacity: 0.95, // starting opacity
|
||||||
endOpacity: 0, // ending opacity
|
endOpacity: 0, // ending opacity
|
||||||
|
sizeScale: 1, // multiplies the base size (radius) used for the visual
|
||||||
render: null, // optional function({ targets, config, scene, ui }) to fully override rendering
|
render: null, // optional function({ targets, config, scene, ui }) to fully override rendering
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
78
src/ui.js
78
src/ui.js
@@ -281,21 +281,20 @@
|
|||||||
activeMessages.push(el);
|
activeMessages.push(el);
|
||||||
};
|
};
|
||||||
|
|
||||||
const spawnClearEffects = (items = [], options = {}) => {
|
const createPopEffect = (items, options) => {
|
||||||
if (!clearFxEl || !Array.isArray(items) || items.length === 0) return;
|
|
||||||
const {
|
const {
|
||||||
type = "pop",
|
|
||||||
durationMs = 320,
|
durationMs = 320,
|
||||||
startScale = 1,
|
startScale = 1,
|
||||||
endScale = 1.8,
|
endScale = 1.8,
|
||||||
startOpacity = 0.95,
|
startOpacity = 0.95,
|
||||||
endOpacity = 0,
|
endOpacity = 0,
|
||||||
|
sizeScale = 1,
|
||||||
} = options;
|
} = options;
|
||||||
items.forEach((item) => {
|
items.forEach((item) => {
|
||||||
if (!item) return;
|
if (!item) return;
|
||||||
const size = Math.max(4, (item.radius || 18) * 2);
|
const size = Math.max(4, (item.radius || 18) * 2 * sizeScale);
|
||||||
const el = document.createElement("div");
|
const el = document.createElement("div");
|
||||||
el.className = `clear-effect clear-effect--${type}`;
|
el.className = "clear-effect clear-effect--pop";
|
||||||
el.style.width = `${size}px`;
|
el.style.width = `${size}px`;
|
||||||
el.style.height = `${size}px`;
|
el.style.height = `${size}px`;
|
||||||
el.style.left = `${item.x}px`;
|
el.style.left = `${item.x}px`;
|
||||||
@@ -314,6 +313,75 @@
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const createShatterEffect = (items, options) => {
|
||||||
|
const {
|
||||||
|
durationMs = 360,
|
||||||
|
sizeScale = 1,
|
||||||
|
shardCount = 6,
|
||||||
|
startOpacity = 0.94,
|
||||||
|
} = options;
|
||||||
|
items.forEach((item) => {
|
||||||
|
if (!item) return;
|
||||||
|
const base = Math.max(8, (item.radius || 18) * sizeScale);
|
||||||
|
for (let i = 0; i < shardCount; i += 1) {
|
||||||
|
const piece = document.createElement("div");
|
||||||
|
piece.className = "clear-effect clear-effect--shatter";
|
||||||
|
piece.style.width = `${base * 0.6}px`;
|
||||||
|
piece.style.height = `${base * 0.6}px`;
|
||||||
|
piece.style.left = `${item.x}px`;
|
||||||
|
piece.style.top = `${item.y}px`;
|
||||||
|
piece.style.background = item.color || "#38bdf8";
|
||||||
|
piece.style.opacity = startOpacity;
|
||||||
|
piece.style.borderRadius = "14%";
|
||||||
|
piece.style.transform =
|
||||||
|
"translate(-50%, -50%) scale(0.9) rotate(0deg)";
|
||||||
|
clearFxEl.appendChild(piece);
|
||||||
|
const spread = base * 1.5;
|
||||||
|
const dx = (Math.random() - 0.5) * spread;
|
||||||
|
const dy = (Math.random() - 0.3) * spread;
|
||||||
|
const rot = (Math.random() - 0.5) * 260;
|
||||||
|
const scale = 0.9 + Math.random() * 0.5;
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
piece.style.transition = `transform ${durationMs}ms ease-out, opacity ${durationMs}ms ease-out`;
|
||||||
|
piece.style.transform = `translate(${dx}px, ${dy}px) scale(${scale}) rotate(${rot}deg)`;
|
||||||
|
piece.style.opacity = "0";
|
||||||
|
});
|
||||||
|
setTimeout(() => piece.remove(), durationMs + 80);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const spawnClearEffects = (items = [], options = {}) => {
|
||||||
|
if (!clearFxEl || !Array.isArray(items) || items.length === 0) return;
|
||||||
|
const {
|
||||||
|
type = "pop",
|
||||||
|
durationMs = 320,
|
||||||
|
startScale = 1,
|
||||||
|
endScale = 1.8,
|
||||||
|
startOpacity = 0.95,
|
||||||
|
endOpacity = 0,
|
||||||
|
sizeScale = 1,
|
||||||
|
shardCount = 6,
|
||||||
|
} = options;
|
||||||
|
if (type === "shatter") {
|
||||||
|
createShatterEffect(items, {
|
||||||
|
durationMs,
|
||||||
|
sizeScale,
|
||||||
|
shardCount,
|
||||||
|
startOpacity,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
createPopEffect(items, {
|
||||||
|
durationMs,
|
||||||
|
startScale,
|
||||||
|
endScale,
|
||||||
|
startOpacity,
|
||||||
|
endOpacity,
|
||||||
|
sizeScale,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const api = {
|
const api = {
|
||||||
sceneEl,
|
sceneEl,
|
||||||
updateHud,
|
updateHud,
|
||||||
|
|||||||
Reference in New Issue
Block a user