Make portals appear earlier with fade-in visuals and SFX

This commit is contained in:
OpenClaw Engineer 2026-03-14 20:49:27 -05:00
parent 2f07d74c78
commit 94d371d901

View File

@ -317,6 +317,7 @@ function playSfx(type) {
ptero_spawn: { f0: 520, f1: 460, dur: 0.1, wave: 'sawtooth', vol: 0.05 }, ptero_spawn: { f0: 520, f1: 460, dur: 0.1, wave: 'sawtooth', vol: 0.05 },
meteor_spawn: { f0: 300, f1: 200, dur: 0.14, wave: 'sawtooth', vol: 0.06 }, meteor_spawn: { f0: 300, f1: 200, dur: 0.14, wave: 'sawtooth', vol: 0.06 },
meteor_land: { f0: 140, f1: 70, dur: 0.18, wave: 'square', vol: 0.08 }, meteor_land: { f0: 140, f1: 70, dur: 0.18, wave: 'square', vol: 0.08 },
portal_fade: { f0: 240, f1: 480, dur: 0.28, wave: 'triangle', vol: 0.05 },
hit: { f0: 260, f1: 140, dur: 0.1, wave: 'square', vol: 0.06 } hit: { f0: 260, f1: 140, dur: 0.1, wave: 'square', vol: 0.06 }
}; };
@ -409,7 +410,10 @@ function createInitialState(config = gameConfig) {
worldCleared: false, worldCleared: false,
portal: { portal: {
active: false, active: false,
visible: false,
entered: false, entered: false,
fadeAlpha: 0,
fadeInSfxPlayed: false,
// Keep portal close to the world edge so forward motion naturally carries // Keep portal close to the world edge so forward motion naturally carries
// the player into it once progression is unlocked. // the player into it once progression is unlocked.
x: worldWidth - 60, x: worldWidth - 60,
@ -721,7 +725,7 @@ function drawMeteors() {
} }
function drawPortal() { function drawPortal() {
if (!state.portal.active) return; if (!state.portal.visible || state.portal.fadeAlpha <= 0.01) return;
const portal = state.portal; const portal = state.portal;
const sx = portal.x - state.cameraX; const sx = portal.x - state.cameraX;
@ -732,7 +736,10 @@ function drawPortal() {
const glowHeight = portal.height + 16; const glowHeight = portal.height + 16;
ctx.save(); ctx.save();
ctx.globalAlpha = 0.2 + pulse * 0.2; ctx.globalAlpha = portal.fadeAlpha;
ctx.save();
ctx.globalAlpha *= 0.2 + pulse * 0.2;
ctx.fillStyle = '#84c7ff'; ctx.fillStyle = '#84c7ff';
ctx.fillRect(sx - 10, portal.y - 8, glowWidth, glowHeight); ctx.fillRect(sx - 10, portal.y - 8, glowWidth, glowHeight);
ctx.restore(); ctx.restore();
@ -756,6 +763,7 @@ function drawPortal() {
ctx.fillStyle = '#0f0f0f'; ctx.fillStyle = '#0f0f0f';
ctx.font = 'bold 14px Arial'; ctx.font = 'bold 14px Arial';
ctx.fillText('PORTAL', sx - 8, portal.y - 10); ctx.fillText('PORTAL', sx - 8, portal.y - 10);
ctx.restore();
} }
function resetWorldForDifficulty(nextDifficulty) { function resetWorldForDifficulty(nextDifficulty) {
@ -775,11 +783,14 @@ function resetWorldForDifficulty(nextDifficulty) {
} }
function updatePortal(dt) { function updatePortal(dt) {
state.portal.pulse += dt; const portal = state.portal;
portal.pulse += dt;
const nextDifficulty = getNextDifficulty(gameConfig.difficulty); const nextDifficulty = getNextDifficulty(gameConfig.difficulty);
if (!nextDifficulty) { if (!nextDifficulty) {
state.portal.active = false; portal.active = false;
portal.visible = false;
portal.fadeAlpha = Math.max(0, portal.fadeAlpha - dt * 3.2);
return; return;
} }
@ -787,26 +798,40 @@ function updatePortal(dt) {
const worldEndX = worldWidth - p.width - 8; const worldEndX = worldWidth - p.width - 8;
// Activate slightly before the hard clamp at world end so the player can // Activate slightly before the hard clamp at world end so the player can
// continue moving right and enter the portal without reversing direction. // continue moving right and enter the portal without reversing direction.
const portalActivationX = Math.max(0, state.portal.x - p.width - 24); const portalActivationX = Math.max(0, portal.x - p.width - 24);
const reachedProgressionPoint = p.x >= portalActivationX; // Reveal portal much earlier, while keeping its fixed world position.
const portalRevealX = Math.max(0, portal.x - canvas.width * 1.6);
if (reachedProgressionPoint) { portal.visible = p.x >= portalRevealX;
state.portal.active = true; portal.active = p.x >= portalActivationX;
if (portal.visible) {
portal.fadeAlpha = Math.min(1, portal.fadeAlpha + dt * 2.8);
if (!portal.fadeInSfxPlayed) {
playSfx('portal_fade');
portal.fadeInSfxPlayed = true;
}
} else {
portal.fadeAlpha = Math.max(0, portal.fadeAlpha - dt * 3.2);
portal.fadeInSfxPlayed = false;
}
if (portal.active) {
state.worldCleared = p.x >= worldEndX; state.worldCleared = p.x >= worldEndX;
} }
if (!state.portal.active || state.portal.entered) return; if (!portal.active || portal.entered || portal.fadeAlpha < 0.9) return;
const portalHitbox = { const portalHitbox = {
x: state.portal.x, x: portal.x,
y: state.portal.y, y: portal.y,
width: state.portal.width, width: portal.width,
height: state.portal.height height: portal.height
}; };
if (intersects(p, portalHitbox)) { if (intersects(p, portalHitbox)) {
state.portal.entered = true; portal.entered = true;
addFloatingText(state.portal.x + state.portal.width / 2, state.portal.y - 14, `${nextDifficulty.toUpperCase()}!`, '#9fe6ff'); addFloatingText(portal.x + portal.width / 2, portal.y - 14, `${nextDifficulty.toUpperCase()}!`, '#9fe6ff');
resetWorldForDifficulty(nextDifficulty); resetWorldForDifficulty(nextDifficulty);
} }
} }