Add scene backdrop controls, FPS display, and toned backdrops
This commit is contained in:
@@ -64,6 +64,14 @@
|
||||
glowStrength: 0.55,
|
||||
gradientBlend: true,
|
||||
},
|
||||
backdrop: {
|
||||
enabled: true,
|
||||
colors: null, // optional array; defaults derived from palette
|
||||
opacity: 0.19,
|
||||
blur: 24,
|
||||
speedSec: 30,
|
||||
},
|
||||
showFps: true,
|
||||
};
|
||||
|
||||
return {
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
getMaxLinkDistance,
|
||||
updateHud,
|
||||
checkWinCondition,
|
||||
ui,
|
||||
}) => {
|
||||
const runSceneBeforeUpdateHook = () => {
|
||||
const currentScene = getCurrentScene();
|
||||
@@ -297,6 +298,7 @@
|
||||
Events.on(engine, "afterUpdate", keepBallsInBounds);
|
||||
Events.on(engine, "beforeUpdate", beforeUpdateStep);
|
||||
Events.on(render, "afterRender", drawLinkGlowAndSparkles);
|
||||
Events.on(render, "afterRender", () => ui.updateFps && ui.updateFps());
|
||||
};
|
||||
|
||||
window.PhysilinksLoop = { create };
|
||||
|
||||
25
src/main.js
25
src/main.js
@@ -73,8 +73,27 @@
|
||||
...goalEffects,
|
||||
});
|
||||
|
||||
const normalizeBackdrop = (
|
||||
backdrop = {},
|
||||
defaults = baseConfig.backdrop || {},
|
||||
) => ({
|
||||
...defaults,
|
||||
...backdrop,
|
||||
colors: Array.isArray(backdrop.colors)
|
||||
? [...backdrop.colors]
|
||||
: Array.isArray(defaults.colors)
|
||||
? [...defaults.colors]
|
||||
: null,
|
||||
});
|
||||
|
||||
const normalizeSceneConfig = (sceneConfig = {}, defaults = baseConfig) => {
|
||||
const { link = {}, messages = {}, goalEffects = {}, ...rest } = sceneConfig;
|
||||
const {
|
||||
link = {},
|
||||
messages = {},
|
||||
goalEffects = {},
|
||||
backdrop = {},
|
||||
...rest
|
||||
} = sceneConfig;
|
||||
const base = defaults || {};
|
||||
return {
|
||||
...base,
|
||||
@@ -85,6 +104,7 @@
|
||||
base.messages || defaultMessageConfig,
|
||||
),
|
||||
goalEffects: normalizeGoalEffects(goalEffects, base.goalEffects),
|
||||
backdrop: normalizeBackdrop(backdrop, base.backdrop),
|
||||
};
|
||||
};
|
||||
|
||||
@@ -216,6 +236,8 @@
|
||||
setSceneIdInUrl(next.id);
|
||||
const prevRadius = config.ballRadius;
|
||||
setConfigForScene(next.config);
|
||||
ui.setBackdrop(config.backdrop, config.palette);
|
||||
ui.setFpsVisibility(config.showFps);
|
||||
ui.setMessageDefaults(config.messages);
|
||||
resetEngineForScene(next.config, { prevRadius });
|
||||
state.clearedCount = 0;
|
||||
@@ -525,6 +547,7 @@
|
||||
getMaxLinkDistance,
|
||||
updateHud,
|
||||
checkWinCondition,
|
||||
ui,
|
||||
});
|
||||
|
||||
window.addEventListener("resize", handleResize);
|
||||
|
||||
@@ -149,6 +149,14 @@
|
||||
glowStrength: 0.55, // opacity multiplier for glow
|
||||
gradientBlend: true, // when colors are provided, blend them into the bar
|
||||
},
|
||||
backdrop: {
|
||||
enabled: true, // disable to turn off scene backdrop
|
||||
colors: null, // array of colors for backdrop blobs; defaults derive from palette
|
||||
opacity: 0.24, // base opacity
|
||||
blur: 24, // blur radius in px
|
||||
speedSec: 30, // drift speed in seconds
|
||||
},
|
||||
showFps: false, // set true to show FPS overlay for perf checking
|
||||
},
|
||||
createBodies: (w, h) => {
|
||||
// Return an array of Matter bodies that make up scene obstacles/boundaries.
|
||||
|
||||
107
src/ui.js
107
src/ui.js
@@ -423,6 +423,110 @@
|
||||
}
|
||||
};
|
||||
|
||||
const hexToRgb = (hex) => {
|
||||
if (typeof hex !== "string") return null;
|
||||
const match = hex
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
.match(/^#([0-9a-f]{3}|[0-9a-f]{6})$/);
|
||||
if (!match) return null;
|
||||
let value = match[1];
|
||||
if (value.length === 3) {
|
||||
value = value
|
||||
.split("")
|
||||
.map((c) => c + c)
|
||||
.join("");
|
||||
}
|
||||
const num = parseInt(value, 16);
|
||||
return {
|
||||
r: (num >> 16) & 255,
|
||||
g: (num >> 8) & 255,
|
||||
b: num & 255,
|
||||
};
|
||||
};
|
||||
|
||||
const deriveOppositeColors = (palette = []) => {
|
||||
const alphas = [0.4, 0.32, 0.24];
|
||||
return palette
|
||||
.slice(0, 3)
|
||||
.map((c, idx) => {
|
||||
const rgb = hexToRgb(c);
|
||||
if (!rgb) return null;
|
||||
const opp = {
|
||||
r: 255 - rgb.r,
|
||||
g: 255 - rgb.g,
|
||||
b: 255 - rgb.b,
|
||||
};
|
||||
return `rgba(${opp.r}, ${opp.g}, ${opp.b}, ${alphas[idx] ?? 0.1})`;
|
||||
})
|
||||
.filter(Boolean);
|
||||
};
|
||||
|
||||
const setBackdrop = (backdrop = {}, palette = []) => {
|
||||
if (!sceneEl) return;
|
||||
const derived =
|
||||
Array.isArray(backdrop.colors) && backdrop.colors.length > 0
|
||||
? backdrop.colors
|
||||
: deriveOppositeColors(palette);
|
||||
const colors =
|
||||
derived && derived.length > 0
|
||||
? derived
|
||||
: [
|
||||
"rgba(56, 189, 248, 0.08)",
|
||||
"rgba(167, 139, 250, 0.07)",
|
||||
"rgba(52, 211, 153, 0.06)",
|
||||
];
|
||||
if (backdrop.enabled === false) {
|
||||
sceneEl.style.removeProperty("--backdrop-opacity");
|
||||
sceneEl.style.removeProperty("--backdrop-blur");
|
||||
sceneEl.style.removeProperty("--backdrop-speed");
|
||||
sceneEl.style.removeProperty("--backdrop-color-a");
|
||||
sceneEl.style.removeProperty("--backdrop-color-b");
|
||||
sceneEl.style.removeProperty("--backdrop-color-c");
|
||||
sceneEl.classList.remove("backdrop-enabled");
|
||||
return;
|
||||
}
|
||||
const [
|
||||
c1 = "rgba(56, 189, 248, 0.16)",
|
||||
c2 = "rgba(167, 139, 250, 0.14)",
|
||||
c3 = "rgba(52, 211, 153, 0.12)",
|
||||
] = colors;
|
||||
sceneEl.style.setProperty(
|
||||
"--backdrop-opacity",
|
||||
`${backdrop.opacity ?? 0.24}`,
|
||||
);
|
||||
sceneEl.style.setProperty("--backdrop-blur", `${backdrop.blur ?? 24}px`);
|
||||
const speed = Math.max(8, backdrop.speedSec ?? 30);
|
||||
sceneEl.style.setProperty("--backdrop-speed", `${speed}s`);
|
||||
sceneEl.style.setProperty("--backdrop-color-a", c1);
|
||||
sceneEl.style.setProperty("--backdrop-color-b", c2);
|
||||
sceneEl.style.setProperty("--backdrop-color-c", c3);
|
||||
sceneEl.classList.add("backdrop-enabled");
|
||||
};
|
||||
|
||||
const fpsEl = document.createElement("div");
|
||||
fpsEl.className = "fps-counter";
|
||||
fpsEl.textContent = "FPS: —";
|
||||
if (sceneEl) sceneEl.appendChild(fpsEl);
|
||||
let fpsVisible = false;
|
||||
const setFpsVisibility = (on) => {
|
||||
fpsVisible = !!on;
|
||||
fpsEl.classList.toggle("visible", fpsVisible);
|
||||
};
|
||||
let lastFpsUpdate = 0;
|
||||
let frameCount = 0;
|
||||
const updateFps = () => {
|
||||
if (!fpsVisible) return;
|
||||
frameCount += 1;
|
||||
const now = performance.now();
|
||||
if (now - lastFpsUpdate >= 500) {
|
||||
const fps = Math.round((frameCount * 1000) / (now - lastFpsUpdate));
|
||||
fpsEl.textContent = `FPS: ${fps}`;
|
||||
frameCount = 0;
|
||||
lastFpsUpdate = now;
|
||||
}
|
||||
};
|
||||
|
||||
const api = {
|
||||
sceneEl,
|
||||
updateHud,
|
||||
@@ -441,6 +545,9 @@
|
||||
setMessageDefaults,
|
||||
clearMessages,
|
||||
spawnClearEffects,
|
||||
setBackdrop,
|
||||
setFpsVisibility,
|
||||
updateFps,
|
||||
};
|
||||
return api;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user