refine 2048 animations and enhance Dot Link sound effects
2048: - Improve tile slide animations with distance-proportional timing - Refactor merge transitions: scale down source tiles before showing merged result - Extract _paintTile helper to reduce code duplication - Simplify spawnTile logic by removing unnecessary mergedAt/moves fields Dot Link: - Add dynamic pitch variation to path-drawing sounds using plink/plonk SFX - Rate scales with path length for more engaging audio feedback - Preload new scifi-plink and scifi-plonk audio assets
This commit is contained in:
parent
24678602f4
commit
4b78bf047f
|
|
@ -420,6 +420,7 @@ export default class Game2048 extends Phaser.Scene {
|
||||||
|
|
||||||
this._animateMove(this.state, newState, () => {
|
this._animateMove(this.state, newState, () => {
|
||||||
this.state = newState;
|
this.state = newState;
|
||||||
|
this._redrawAllTiles(this.state);
|
||||||
this._updateScore(newState.score);
|
this._updateScore(newState.score);
|
||||||
|
|
||||||
if (!this.wonAlready && newState.won) {
|
if (!this.wonAlready && newState.won) {
|
||||||
|
|
@ -438,27 +439,52 @@ export default class Game2048 extends Phaser.Scene {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_createFloatingTile(x, y, value) {
|
||||||
|
const CS = this.cellSize - 6;
|
||||||
|
const level = getTileLevel(value);
|
||||||
|
const color = this.tilePalette[level];
|
||||||
|
const textColor = this.tileTextColors[level];
|
||||||
|
|
||||||
|
const g = this.add.graphics().setDepth(D.tiles + 2);
|
||||||
|
g.fillStyle(0x000000, 0.35);
|
||||||
|
g.fillRoundedRect(-CS / 2 + 3, -CS / 2 + 3, CS, CS, 8);
|
||||||
|
g.fillStyle(color, 1);
|
||||||
|
g.fillRoundedRect(-CS / 2, -CS / 2, CS, CS, 8);
|
||||||
|
const sheen = this._mixInts(color, 0xffffff, 0.22);
|
||||||
|
g.fillStyle(sheen, 0.3);
|
||||||
|
g.fillRoundedRect(-CS / 2, -CS / 2, CS, Math.floor(CS * 0.45), 8);
|
||||||
|
|
||||||
|
const log = Math.round(Math.log2(value));
|
||||||
|
const fsMultiplier = (FS_LEVELS[log] ?? 18) / 100;
|
||||||
|
const t = this.add.text(0, 0, String(value), {
|
||||||
|
fontFamily: FONT_TITLE,
|
||||||
|
fontSize: `${Math.floor(this.cellSize * fsMultiplier)}px`,
|
||||||
|
color: textColor,
|
||||||
|
}).setOrigin(0.5).setDepth(D.tiles + 3);
|
||||||
|
|
||||||
|
return this.add.container(x, y, [g, t]).setDepth(D.tiles + 2);
|
||||||
|
}
|
||||||
|
|
||||||
_animateMove(oldState, newState, onDone) {
|
_animateMove(oldState, newState, onDone) {
|
||||||
const SLIDE_MS = 95;
|
const SPAWN_MS = 160;
|
||||||
const MERGE_MS = 140;
|
|
||||||
const SPAWN_MS = 115;
|
|
||||||
|
|
||||||
// Phase 1: slide tiles to their new positions
|
const movingEntries = newState.moves.filter(m => m.fromIdx !== m.toIdx);
|
||||||
const movesToAnimate = newState.moves.filter(m => m.fromIdx !== m.toIdx);
|
const mergeSet = new Set(newState.mergedAt);
|
||||||
|
|
||||||
// For merges, two source tiles go to the same destination.
|
// Hide stationary merge participants so the old value doesn't show while the
|
||||||
// We only animate the "first" of a pair (visually one tile slides).
|
// other tile slides in.
|
||||||
const seen = new Set();
|
for (const mv of newState.moves) {
|
||||||
const uniqueMoves = [];
|
if (mv.fromIdx === mv.toIdx && mergeSet.has(mv.toIdx)) {
|
||||||
for (const m of movesToAnimate) {
|
const r = Math.floor(mv.toIdx / 4), c = mv.toIdx % 4;
|
||||||
const key = `${m.fromIdx}-${m.toIdx}`;
|
this.tileContainers[r][c].setAlpha(0);
|
||||||
if (!seen.has(key)) { seen.add(key); uniqueMoves.push(m); }
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const spawnPhase = () => {
|
const spawnPhase = () => {
|
||||||
if (newState.spawned === null) { onDone(); return; }
|
if (newState.spawned === null) { onDone(); return; }
|
||||||
const r = Math.floor(newState.spawned / 4), c = newState.spawned % 4;
|
const r = Math.floor(newState.spawned / 4), c = newState.spawned % 4;
|
||||||
const cont = this.tileContainers[r][c];
|
const cont = this.tileContainers[r][c];
|
||||||
|
this._paintTile(r, c, newState.grid[newState.spawned]);
|
||||||
cont.setAlpha(1).setScale(0);
|
cont.setAlpha(1).setScale(0);
|
||||||
this.tweens.add({
|
this.tweens.add({
|
||||||
targets: cont, scaleX: 1, scaleY: 1,
|
targets: cont, scaleX: 1, scaleY: 1,
|
||||||
|
|
@ -468,82 +494,159 @@ export default class Game2048 extends Phaser.Scene {
|
||||||
};
|
};
|
||||||
|
|
||||||
const afterSlides = () => {
|
const afterSlides = () => {
|
||||||
|
if (mergeSet.size === 0) {
|
||||||
this._redrawAllTiles(newState);
|
this._redrawAllTiles(newState);
|
||||||
|
spawnPhase();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (newState.mergedAt.length === 0) { spawnPhase(); return; }
|
// Build map: merge destination index → moving source containers currently at that position
|
||||||
|
const mergeSources = new Map();
|
||||||
|
for (const mv of movingEntries) {
|
||||||
|
if (!mergeSet.has(mv.toIdx)) continue;
|
||||||
|
if (!mergeSources.has(mv.toIdx)) mergeSources.set(mv.toIdx, []);
|
||||||
|
const fr = Math.floor(mv.fromIdx / 4), fc = mv.fromIdx % 4;
|
||||||
|
mergeSources.get(mv.toIdx).push({ cont: this.tileContainers[fr][fc], fromIdx: mv.fromIdx });
|
||||||
|
}
|
||||||
|
|
||||||
let mergePending = newState.mergedAt.length;
|
// Immediately update all tiles that aren't part of a merge transition
|
||||||
const afterMerges = () => { mergePending--; if (mergePending === 0) spawnPhase(); };
|
const skipIndices = new Set(newState.mergedAt);
|
||||||
|
for (const srcs of mergeSources.values()) {
|
||||||
|
for (const { fromIdx } of srcs) skipIndices.add(fromIdx);
|
||||||
|
}
|
||||||
|
this._redrawTilesExcept(newState, skipIndices);
|
||||||
|
|
||||||
for (const idx of newState.mergedAt) {
|
// Count total source containers across all merges
|
||||||
const r = Math.floor(idx / 4), c = idx % 4;
|
let totalSrcs = 0;
|
||||||
const cont = this.tileContainers[r][c];
|
for (const srcs of mergeSources.values()) totalSrcs += srcs.length;
|
||||||
const mergeVal = newState.grid[idx];
|
|
||||||
this._scorePopup(cont.x, cont.y - 40, mergeVal);
|
if (totalSrcs === 0) {
|
||||||
|
this._showMergedTiles(newState, spawnPhase);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scale down all source containers simultaneously (they're at the merge position)
|
||||||
|
let scaledDown = 0;
|
||||||
|
const onAllScaledDown = () => {
|
||||||
|
// Reset every source to its home cell (invisible) before growing merged tiles
|
||||||
|
for (const [, srcs] of mergeSources) {
|
||||||
|
for (const { cont, fromIdx } of srcs) {
|
||||||
|
const { cx, cy } = this._cellCenter(Math.floor(fromIdx / 4), fromIdx % 4);
|
||||||
|
cont.setAlpha(0).setScale(1).setPosition(cx, cy);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this._showMergedTiles(newState, spawnPhase);
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const [, srcs] of mergeSources) {
|
||||||
|
for (const { cont } of srcs) {
|
||||||
this.tweens.add({
|
this.tweens.add({
|
||||||
targets: cont, scaleX: 1.2, scaleY: 1.2,
|
targets: cont, scaleX: 0, scaleY: 0,
|
||||||
duration: MERGE_MS / 2, ease: 'Quad.easeOut',
|
duration: 120, ease: 'Quad.easeIn',
|
||||||
yoyo: true, onComplete: afterMerges,
|
onComplete: () => { scaledDown++; if (scaledDown === totalSrcs) onAllScaledDown(); },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let pending = uniqueMoves.length;
|
if (movingEntries.length === 0) { afterSlides(); return; }
|
||||||
if (pending === 0) { afterSlides(); return; }
|
|
||||||
|
|
||||||
for (const mv of uniqueMoves) {
|
// Tween actual grid containers to destination positions.
|
||||||
|
// Distance-proportional timing: 1 cell ≈ 200ms, capped at 700ms.
|
||||||
|
let pending = movingEntries.length;
|
||||||
|
for (const mv of movingEntries) {
|
||||||
const fr = Math.floor(mv.fromIdx / 4), fc = mv.fromIdx % 4;
|
const fr = Math.floor(mv.fromIdx / 4), fc = mv.fromIdx % 4;
|
||||||
const tr = Math.floor(mv.toIdx / 4), tc = mv.toIdx % 4;
|
const tr = Math.floor(mv.toIdx / 4), tc = mv.toIdx % 4;
|
||||||
const cont = this.tileContainers[fr][fc];
|
const { cx: dstX, cy: dstY } = this._cellCenter(tr, tc);
|
||||||
const { cx: tx, cy: ty } = this._cellCenter(tr, tc);
|
const cells = Math.abs(tr - fr) + Math.abs(tc - fc);
|
||||||
|
const duration = Math.min(180 + cells * 200, 700);
|
||||||
this.tweens.add({
|
this.tweens.add({
|
||||||
targets: cont, x: tx, y: ty,
|
targets: this.tileContainers[fr][fc],
|
||||||
duration: SLIDE_MS, ease: 'Quad.easeOut',
|
x: dstX, y: dstY,
|
||||||
|
duration,
|
||||||
|
ease: 'Cubic.easeInOut',
|
||||||
onComplete: () => { pending--; if (pending === 0) afterSlides(); },
|
onComplete: () => { pending--; if (pending === 0) afterSlides(); },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_redrawAllTiles(state) {
|
_showMergedTiles(newState, spawnPhase) {
|
||||||
const CS = this.cellSize - 6;
|
let mergePending = newState.mergedAt.length;
|
||||||
const baseLevel = 36;
|
const afterMerges = () => { mergePending--; if (mergePending === 0) spawnPhase(); };
|
||||||
|
|
||||||
for (let r = 0; r < 4; r++) {
|
for (const idx of newState.mergedAt) {
|
||||||
for (let c = 0; c < 4; c++) {
|
const tr = Math.floor(idx / 4), tc = idx % 4;
|
||||||
const val = state.grid[r * 4 + c];
|
const destCont = this.tileContainers[tr][tc];
|
||||||
const { cx, cy } = this._cellCenter(r, c);
|
this._paintTile(tr, tc, newState.grid[idx]);
|
||||||
const cont = this.tileContainers[r][c];
|
this._scorePopup(destCont.x, destCont.y - 40, newState.grid[idx]);
|
||||||
|
destCont.setAlpha(1).setScale(0);
|
||||||
|
this.tweens.add({
|
||||||
|
targets: destCont, scaleX: 1.2, scaleY: 1.2,
|
||||||
|
duration: 200, ease: 'Back.easeOut',
|
||||||
|
onComplete: () => {
|
||||||
|
this.tweens.add({
|
||||||
|
targets: destCont, scaleX: 1, scaleY: 1,
|
||||||
|
duration: 80, ease: 'Quad.easeOut',
|
||||||
|
onComplete: afterMerges,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_paintTile(r, c, val) {
|
||||||
|
const CS = this.cellSize - 6;
|
||||||
const gfx = this.tileGraphics[r][c];
|
const gfx = this.tileGraphics[r][c];
|
||||||
const txt = this.tileTexts[r][c];
|
const txt = this.tileTexts[r][c];
|
||||||
|
const level = getTileLevel(val);
|
||||||
|
const color = this.tilePalette[level];
|
||||||
|
gfx.clear();
|
||||||
|
gfx.fillStyle(0x000000, 0.35);
|
||||||
|
gfx.fillRoundedRect(-CS / 2 + 3, -CS / 2 + 3, CS, CS, 8);
|
||||||
|
gfx.fillStyle(color, 1);
|
||||||
|
gfx.fillRoundedRect(-CS / 2, -CS / 2, CS, CS, 8);
|
||||||
|
const sheen = this._mixInts(color, 0xffffff, 0.22);
|
||||||
|
gfx.fillStyle(sheen, 0.3);
|
||||||
|
gfx.fillRoundedRect(-CS / 2, -CS / 2, CS, Math.floor(CS * 0.45), 8);
|
||||||
|
txt.setText(String(val));
|
||||||
|
txt.setColor(this.tileTextColors[level]);
|
||||||
|
const log = Math.round(Math.log2(val));
|
||||||
|
const fsMultiplier = (FS_LEVELS[log] ?? 18) / 100;
|
||||||
|
txt.setFontSize(`${Math.floor(this.cellSize * fsMultiplier)}px`);
|
||||||
|
}
|
||||||
|
|
||||||
|
_redrawTilesExcept(state, skipSet) {
|
||||||
|
for (let r = 0; r < 4; r++) {
|
||||||
|
for (let c = 0; c < 4; c++) {
|
||||||
|
if (skipSet.has(r * 4 + c)) continue;
|
||||||
|
const val = state.grid[r * 4 + c];
|
||||||
|
const cont = this.tileContainers[r][c];
|
||||||
|
const { cx, cy } = this._cellCenter(r, c);
|
||||||
if (val === 0) {
|
if (val === 0) {
|
||||||
cont.setAlpha(0);
|
cont.setAlpha(0);
|
||||||
cont.setPosition(cx, cy).setScale(1);
|
cont.setPosition(cx, cy).setScale(1);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
cont.setPosition(cx, cy).setScale(1);
|
cont.setPosition(cx, cy).setScale(1);
|
||||||
|
this._paintTile(r, c, val);
|
||||||
|
cont.setAlpha(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const level = getTileLevel(val);
|
_redrawAllTiles(state) {
|
||||||
const color = this.tilePalette[level];
|
for (let r = 0; r < 4; r++) {
|
||||||
|
for (let c = 0; c < 4; c++) {
|
||||||
gfx.clear();
|
const val = state.grid[r * 4 + c];
|
||||||
// Shadow
|
const { cx, cy } = this._cellCenter(r, c);
|
||||||
gfx.fillStyle(0x000000, 0.35);
|
const cont = this.tileContainers[r][c];
|
||||||
gfx.fillRoundedRect(-CS / 2 + 3, -CS / 2 + 3, CS, CS, 8);
|
if (val === 0) {
|
||||||
// Main tile
|
cont.setAlpha(0);
|
||||||
gfx.fillStyle(color, 1);
|
cont.setPosition(cx, cy).setScale(1);
|
||||||
gfx.fillRoundedRect(-CS / 2, -CS / 2, CS, CS, 8);
|
continue;
|
||||||
// Top sheen
|
}
|
||||||
const sheen = this._mixInts(color, 0xffffff, 0.22);
|
cont.setPosition(cx, cy).setScale(1);
|
||||||
gfx.fillStyle(sheen, 0.3);
|
this._paintTile(r, c, val);
|
||||||
gfx.fillRoundedRect(-CS / 2, -CS / 2, CS, Math.floor(CS * 0.45), 8);
|
|
||||||
|
|
||||||
txt.setText(String(val));
|
|
||||||
txt.setColor(this.tileTextColors[level]);
|
|
||||||
const log = Math.round(Math.log2(val));
|
|
||||||
const fsMultiplier = (FS_LEVELS[log] ?? 18) / 100;
|
|
||||||
txt.setFontSize(`${Math.floor(this.cellSize * fsMultiplier)}px`);
|
|
||||||
|
|
||||||
cont.setAlpha(1);
|
cont.setAlpha(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -55,12 +55,12 @@ export function createState() {
|
||||||
export function spawnTile(state) {
|
export function spawnTile(state) {
|
||||||
const empties = [];
|
const empties = [];
|
||||||
for (let i = 0; i < 16; i++) if (state.grid[i] === 0) empties.push(i);
|
for (let i = 0; i < 16; i++) if (state.grid[i] === 0) empties.push(i);
|
||||||
if (empties.length === 0) return { ...state, spawned: null, mergedAt: [], moves: [] };
|
if (empties.length === 0) return { ...state, spawned: null };
|
||||||
const idx = empties[Math.floor(Math.random() * empties.length)];
|
const idx = empties[Math.floor(Math.random() * empties.length)];
|
||||||
const val = Math.random() < 0.9 ? 2 : 4;
|
const val = Math.random() < 0.9 ? 2 : 4;
|
||||||
const grid = new Int32Array(state.grid);
|
const grid = new Int32Array(state.grid);
|
||||||
grid[idx] = val;
|
grid[idx] = val;
|
||||||
return { ...state, grid, spawned: idx, mergedAt: [], moves: [] };
|
return { ...state, grid, spawned: idx };
|
||||||
}
|
}
|
||||||
|
|
||||||
export function hasValidMoves(state) {
|
export function hasValidMoves(state) {
|
||||||
|
|
@ -131,7 +131,7 @@ export function applyMove(state, dir) {
|
||||||
let srcConsumed = 0;
|
let srcConsumed = 0;
|
||||||
for (let di = 0; di < 4 && dstIdx < srcCols.length; di++) {
|
for (let di = 0; di < 4 && dstIdx < srcCols.length; di++) {
|
||||||
if (merged[di] === 0) continue;
|
if (merged[di] === 0) continue;
|
||||||
if (merged[di] === row[srcCols[srcConsumed]] && srcConsumed + 1 < srcCols.length && row[srcCols[srcConsumed]] === row[srcCols[srcConsumed + 1]]) {
|
if (srcConsumed + 1 < srcCols.length && row[srcCols[srcConsumed]] === row[srcCols[srcConsumed + 1]]) {
|
||||||
// two tiles merged into merged[di]
|
// two tiles merged into merged[di]
|
||||||
movesRotated.push({ fromIdx: r*4+srcCols[srcConsumed], toIdx: r*4+di, value: merged[di] });
|
movesRotated.push({ fromIdx: r*4+srcCols[srcConsumed], toIdx: r*4+di, value: merged[di] });
|
||||||
movesRotated.push({ fromIdx: r*4+srcCols[srcConsumed+1], toIdx: r*4+di, value: merged[di] });
|
movesRotated.push({ fromIdx: r*4+srcCols[srcConsumed+1], toIdx: r*4+di, value: merged[di] });
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import * as Phaser from 'phaser';
|
||||||
import { GAME_WIDTH, GAME_HEIGHT, COLORS } from '../../config.js';
|
import { GAME_WIDTH, GAME_HEIGHT, COLORS } from '../../config.js';
|
||||||
import { Button } from '../../ui/Button.js';
|
import { Button } from '../../ui/Button.js';
|
||||||
import { MusicPlayer } from '../../ui/MusicPlayer.js';
|
import { MusicPlayer } from '../../ui/MusicPlayer.js';
|
||||||
import { playSound, SFX } from '../../ui/Sounds.js';
|
import { playSound, SFX, playDotLinkSound, playScifiLaunch } from '../../ui/Sounds.js';
|
||||||
import { api } from '../../services/api.js';
|
import { api } from '../../services/api.js';
|
||||||
import {
|
import {
|
||||||
makeRng, dateSeed, localDateString, generateBoard, isSolved, manhattanAdjacent,
|
makeRng, dateSeed, localDateString, generateBoard, isSolved, manhattanAdjacent,
|
||||||
|
|
@ -770,7 +770,7 @@ export default class DotLinkGame extends Phaser.Scene {
|
||||||
const target = start === this.endA[ec] ? this.endB[ec] : this.endA[ec];
|
const target = start === this.endA[ec] ? this.endB[ec] : this.endA[ec];
|
||||||
this.draw = { color: ec, target };
|
this.draw = { color: ec, target };
|
||||||
this.drawing = true;
|
this.drawing = true;
|
||||||
playSound(this, SFX.PIECE_CLICK);
|
playDotLinkSound(this, ec, 1);
|
||||||
this.redrawPaths();
|
this.redrawPaths();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -834,7 +834,7 @@ export default class DotLinkGame extends Phaser.Scene {
|
||||||
this.drawing = false;
|
this.drawing = false;
|
||||||
this.redrawPaths();
|
this.redrawPaths();
|
||||||
this.pulseCell(next, FLOW[k % FLOW.length].n);
|
this.pulseCell(next, FLOW[k % FLOW.length].n);
|
||||||
playSound(this, SFX.CARD_PLACE);
|
playScifiLaunch(this);
|
||||||
this.checkWin();
|
this.checkWin();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -855,6 +855,7 @@ export default class DotLinkGame extends Phaser.Scene {
|
||||||
this.owner[next] = k;
|
this.owner[next] = k;
|
||||||
path.push(next);
|
path.push(next);
|
||||||
this.redrawPaths();
|
this.redrawPaths();
|
||||||
|
playDotLinkSound(this, k, path.length);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -112,6 +112,8 @@ export default class PreloadScene extends Phaser.Scene {
|
||||||
this.load.audio('sfx-scifi-riser', '/assets/fx/scifi-riser.mp3');
|
this.load.audio('sfx-scifi-riser', '/assets/fx/scifi-riser.mp3');
|
||||||
this.load.audio('sfx-scifi-reveal', '/assets/fx/scifi-reveal.mp3');
|
this.load.audio('sfx-scifi-reveal', '/assets/fx/scifi-reveal.mp3');
|
||||||
this.load.audio('sfx-scifi-woosh', '/assets/fx/scifi-woosh.mp3');
|
this.load.audio('sfx-scifi-woosh', '/assets/fx/scifi-woosh.mp3');
|
||||||
|
this.load.audio('sfx-scifi-plink', '/assets/fx/scifi-plink.mp3');
|
||||||
|
this.load.audio('sfx-scifi-plonk', '/assets/fx/scifi-plonk.mp3');
|
||||||
|
|
||||||
this.load.spritesheet('catan-special-cards', '/assets/images/catan-special-cards.png', { frameWidth: 270, frameHeight: 390 });
|
this.load.spritesheet('catan-special-cards', '/assets/images/catan-special-cards.png', { frameWidth: 270, frameHeight: 390 });
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,8 @@ export const SFX = {
|
||||||
SCIFI_RISER: 'sfx-scifi-riser',
|
SCIFI_RISER: 'sfx-scifi-riser',
|
||||||
SCIFI_REVEAL: 'sfx-scifi-reveal',
|
SCIFI_REVEAL: 'sfx-scifi-reveal',
|
||||||
SCIFI_WOOSH: 'sfx-scifi-woosh',
|
SCIFI_WOOSH: 'sfx-scifi-woosh',
|
||||||
|
SCIFI_PLINK: 'sfx-scifi-plink',
|
||||||
|
SCIFI_PLONK: 'sfx-scifi-plonk',
|
||||||
SWORD_HIT: 'sfx-sword-hit',
|
SWORD_HIT: 'sfx-sword-hit',
|
||||||
SWORD_SLICE: 'sfx-sword-slice',
|
SWORD_SLICE: 'sfx-sword-slice',
|
||||||
MONOPOLY_PURCHASE: 'sfx-monopoly-purchase',
|
MONOPOLY_PURCHASE: 'sfx-monopoly-purchase',
|
||||||
|
|
@ -93,3 +95,12 @@ export function playScifiWoosh(scene) {
|
||||||
_scifiWooshSound = scene.sound.add(SFX.SCIFI_WOOSH);
|
_scifiWooshSound = scene.sound.add(SFX.SCIFI_WOOSH);
|
||||||
_scifiWooshSound.play();
|
_scifiWooshSound.play();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Dot Link: plink/plonk with adjustable rate — even colour index → plink, odd → plonk
|
||||||
|
export function playDotLinkSound(scene, colorIdx, pathLength) {
|
||||||
|
const soundKey = colorIdx % 2 === 0 ? SFX.SCIFI_PLINK : SFX.SCIFI_PLONK;
|
||||||
|
const rate = 1 + (pathLength - 1) * 0.5;
|
||||||
|
const sound = scene.sound.add(soundKey);
|
||||||
|
sound.setRate(rate);
|
||||||
|
sound.play();
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue