feat: add 8-bit sound effects and improve Battleship impact visuals
- Add 4 new 8-bit sound effects (select, activate, action, move) to SFX system - Wire 8-bit sounds to Risk attack, fortify, and movement animations - Add visual impact markers for hits and misses in Battleship - Fix Battleship portrait texture key reference - Improve ship drag hit area with geometric rectangle - Add phase guard and tween cancellation on ship drag start
This commit is contained in:
parent
546e1de240
commit
54a1ba2280
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -216,7 +216,7 @@ export default class BattleshipGame extends Phaser.Scene {
|
|||
// Player — bottom-right margin.
|
||||
this.plrX = RX + GRID + (GAME_WIDTH - (RX + GRID)) / 2;
|
||||
this.plrY = 740;
|
||||
createPlayerPortrait(this, this.plrX, this.plrY, r, DEPTH.ui, 'Battleship');
|
||||
createPlayerPortrait(this, this.plrX, this.plrY, r, DEPTH.ui, 'BattleshipGame');
|
||||
this.add.text(this.plrX, this.plrY - r - 12, auth.user?.username ?? 'You', {
|
||||
fontFamily: '"Julius Sans One"', fontSize: '20px', color: COLORS.textHex,
|
||||
wordWrap: { width: 240 }, align: 'center',
|
||||
|
|
@ -373,11 +373,7 @@ export default class BattleshipGame extends Phaser.Scene {
|
|||
const w = piece.horizontal ? piece.spec.len * CELL : CELL;
|
||||
const h = piece.horizontal ? CELL : piece.spec.len * CELL;
|
||||
piece.container.setSize(w, h);
|
||||
// Our hull graphics are top-left anchored (local 0,0 → w,h), but Phaser's
|
||||
// input normalizes the hit test by a Container's displayOrigin, which is
|
||||
// hardcoded to (width/2, height/2). Offset the hit area by the same amount
|
||||
// so the clickable zone lines up with the visible ship instead of sitting
|
||||
// up-and-left of it.
|
||||
piece.container.removeInteractive();
|
||||
piece.container.setInteractive(
|
||||
new Phaser.Geom.Rectangle(w / 2, h / 2, w, h), Phaser.Geom.Rectangle.Contains);
|
||||
this.input.setDraggable(piece.container);
|
||||
|
|
@ -423,6 +419,8 @@ export default class BattleshipGame extends Phaser.Scene {
|
|||
}
|
||||
|
||||
onShipDragStart(piece) {
|
||||
if (this.gs.phase !== 'placement') return;
|
||||
this.tweens.killTweensOf(piece.container);
|
||||
this.activeShip = piece;
|
||||
if (piece.placed) { piece.placed = false; this.syncFleetFromPieces(); this.updateReadyButton(); }
|
||||
piece.container.setScale(1).setDepth(DEPTH.missile);
|
||||
|
|
@ -745,10 +743,26 @@ export default class BattleshipGame extends Phaser.Scene {
|
|||
if (res.result === 'sunk') this.onShipSunk(res.ship, firedByPlayer);
|
||||
if (this.opponentPortrait) this.playOppEmotion(firedByPlayer ? 'upset' : 'happy');
|
||||
}
|
||||
this.renderMarkers();
|
||||
this.drawImpactMarker(firedByPlayer, res);
|
||||
this.refreshFleetPanels();
|
||||
}
|
||||
|
||||
drawImpactMarker(firedByPlayer, res) {
|
||||
const g = firedByPlayer ? this.enemyMarkers : this.yourMarkers;
|
||||
const ox = firedByPlayer ? EX : RX;
|
||||
const x = ox + res.c * CELL + CELL / 2;
|
||||
const y = BY + res.r * CELL + CELL / 2;
|
||||
if (res.result === 'miss') {
|
||||
g.fillStyle(0x0a1a22, 0.5); g.fillCircle(x, y, 9);
|
||||
g.fillStyle(C.miss, 0.95); g.fillCircle(x, y, 7);
|
||||
g.lineStyle(1, 0x88a6b2, 0.8); g.strokeCircle(x, y, 7);
|
||||
} else {
|
||||
g.fillStyle(C.hit, 0.25); g.fillCircle(x, y, 16);
|
||||
g.fillStyle(C.hit, 1); g.fillCircle(x, y, 10);
|
||||
g.fillStyle(C.hitGlow, 1); g.fillCircle(x, y, 5);
|
||||
}
|
||||
}
|
||||
|
||||
splash(x, y) {
|
||||
const ripple = this.add.graphics().setDepth(DEPTH.fx);
|
||||
const prox = { p: 0 };
|
||||
|
|
|
|||
|
|
@ -389,21 +389,27 @@ export default class RiskGame extends Phaser.Scene {
|
|||
|
||||
if (s.phase === 'attack') {
|
||||
if (s.owner[id] === seat && s.armies[id] >= 2 && ADJ[id].some((t) => s.owner[t] !== seat)) {
|
||||
playSound(this, SFX.EIGHTBIT_SELECT);
|
||||
this.selFrom = id; this.render(); return;
|
||||
}
|
||||
if (this.selFrom != null && canAttack(s, seat, this.selFrom, id)) { this.doHumanAttack(this.selFrom, id); return; }
|
||||
if (this.selFrom != null && canAttack(s, seat, this.selFrom, id)) {
|
||||
playSound(this, SFX.EIGHTBIT_ACTIVATE);
|
||||
this.doHumanAttack(this.selFrom, id); return;
|
||||
}
|
||||
this.selFrom = null; this.render(); return;
|
||||
}
|
||||
|
||||
if (s.phase === 'fortify') {
|
||||
if (this.selFrom == null) {
|
||||
if (s.owner[id] === seat && s.armies[id] >= 2 && connectedOwned(s, seat, id).length > 0) {
|
||||
playSound(this, SFX.EIGHTBIT_SELECT);
|
||||
this.selFrom = id; this.render();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (id === this.selFrom) { this.selFrom = null; this.render(); return; }
|
||||
if (s.owner[id] === seat && connectedOwned(s, seat, this.selFrom).includes(id)) {
|
||||
playSound(this, SFX.EIGHTBIT_ACTION);
|
||||
const from = this.selFrom, to = id;
|
||||
this.openMoveModal(1, s.armies[from] - 1, s.armies[from] - 1, async (n) => {
|
||||
this.selFrom = null;
|
||||
|
|
@ -937,8 +943,9 @@ export default class RiskGame extends Phaser.Scene {
|
|||
const progress = { t: 0 };
|
||||
this.tweens.add({
|
||||
targets: progress, t: 1, duration: 1000, ease: 'Sine.easeInOut',
|
||||
onUpdate: () => drawArc(progress.t),
|
||||
onComplete: () => { drawArc(1); resolve(); },
|
||||
onStart: () => playSound(this, SFX.BATTLESHIP_LAUNCH),
|
||||
onUpdate: () => drawArc(progress.t),
|
||||
onComplete: () => { drawArc(1); playSound(this, SFX.BATTLESHIP_HIT); resolve(); },
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -986,7 +993,8 @@ export default class RiskGame extends Phaser.Scene {
|
|||
const progress = { t: 0 };
|
||||
this.tweens.add({
|
||||
targets: progress, t: 1, duration: 1000, ease: 'Sine.easeInOut',
|
||||
onUpdate: () => drawLine(progress.t),
|
||||
onStart: () => playSound(this, SFX.EIGHTBIT_MOVE),
|
||||
onUpdate: () => drawLine(progress.t),
|
||||
onComplete: () => { drawLine(1); resolve(); },
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -115,6 +115,10 @@ export default class PreloadScene extends Phaser.Scene {
|
|||
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.audio('sfx-8bit-select', '/assets/fx/8bit-select.mp3');
|
||||
this.load.audio('sfx-8bit-activate', '/assets/fx/8bit-activate.mp3');
|
||||
this.load.audio('sfx-8bit-action', '/assets/fx/8bit-action.mp3');
|
||||
this.load.audio('sfx-8bit-move', '/assets/fx/8bit-move.mp3');
|
||||
|
||||
this.load.spritesheet('catan-special-cards', '/assets/images/catan-special-cards.png', { frameWidth: 270, frameHeight: 390 });
|
||||
|
||||
|
|
|
|||
|
|
@ -40,6 +40,10 @@ export const SFX = {
|
|||
MONOPOLY_EXPENSE: 'sfx-monopoly-expense',
|
||||
MONOPAY: 'sfx-monopoly-pay',
|
||||
MONOPOLY_PAID: 'sfx-monopoly-paid',
|
||||
EIGHTBIT_SELECT: 'sfx-8bit-select',
|
||||
EIGHTBIT_ACTIVATE: 'sfx-8bit-activate',
|
||||
EIGHTBIT_ACTION: 'sfx-8bit-action',
|
||||
EIGHTBIT_MOVE: 'sfx-8bit-move',
|
||||
};
|
||||
|
||||
export function playSound(scene, key) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue