Redesign responsive combat HUD

This commit is contained in:
Admin 2026-09-17 22:10:38 +00:00
parent 98d2c799b4
commit 8fdb313cb2

View File

@ -90,39 +90,7 @@ class GameScene extends Phaser.Scene {
this.cameras.main.startFollow(this.player, true, 0.08, 0.08); this.cameras.main.startFollow(this.player, true, 0.08, 0.08);
this.cameras.main.setZoom(1.12); this.cameras.main.setZoom(1.12);
this.title = this.add.text(18, 16, 'ARROWSTACK AIRSHIP', { this.createHud();
fontFamily: 'monospace',
fontSize: '12px',
color: '#8cd3ff',
}).setScrollFactor(0).setDepth(30);
this.hud = this.add.text(18, 42, '', {
fontFamily: 'monospace',
fontSize: '14px',
color: '#e8f2f7',
backgroundColor: '#071018cc',
padding: { x: 12, y: 10 },
lineSpacing: 4,
}).setScrollFactor(0).setDepth(30);
this.status = this.add.text(this.scale.width / 2, 22, '', {
fontFamily: 'monospace',
fontSize: '18px',
color: '#ffffff',
backgroundColor: '#071018dd',
padding: { x: 16, y: 10 },
align: 'center',
}).setOrigin(0.5, 0).setScrollFactor(0).setDepth(31);
this.healthGraphics = this.add.graphics().setScrollFactor(0).setDepth(30);
this.healthLabels = {
player: this.add.text(18, this.scale.height - 64, 'SPARROW', {
fontFamily: 'monospace', fontSize: '12px', color: '#d8ecff',
}).setScrollFactor(0).setDepth(31),
pirate: this.add.text(this.scale.width - 18, this.scale.height - 64, 'PIRATES', {
fontFamily: 'monospace', fontSize: '12px', color: '#ffd0d0',
}).setOrigin(1, 0).setScrollFactor(0).setDepth(31),
};
this.scale.on('resize', this.layoutHud, this); this.scale.on('resize', this.layoutHud, this);
this.events.once(Phaser.Scenes.Events.SHUTDOWN, () => { this.events.once(Phaser.Scenes.Events.SHUTDOWN, () => {
@ -132,6 +100,71 @@ class GameScene extends Phaser.Scene {
this.spawnWave(); this.spawnWave();
} }
createHud() {
const fixedText = (x, y, text, style = {}) => this.add.text(x, y, text, {
fontFamily: 'monospace',
color: '#e8f2f7',
...style,
}).setScrollFactor(0).setDepth(31);
this.hudGraphics = this.add.graphics().setScrollFactor(0).setDepth(30);
this.brandText = fixedText(14, 10, 'ARROWSTACK // SPARROW', {
fontSize: '10px',
color: '#8cd3ff',
});
this.speedText = fixedText(14, 29, '', {
fontSize: '16px',
color: '#ffffff',
});
this.headingText = fixedText(this.scale.width / 2, 10, '', {
fontSize: '17px',
color: '#d8ecff',
align: 'center',
}).setOrigin(0.5, 0);
this.waveText = fixedText(this.scale.width / 2, 32, '', {
fontSize: '11px',
color: '#8cd3ff',
align: 'center',
}).setOrigin(0.5, 0);
this.combatText = fixedText(this.scale.width - 76, 11, '', {
fontSize: '10px',
color: '#c8d6df',
align: 'right',
lineSpacing: 2,
}).setOrigin(1, 0);
this.playerHullLabel = fixedText(14, 55, '', {
fontSize: '10px',
color: '#a7f0c1',
});
this.enemyHullLabel = fixedText(this.scale.width - 14, 55, '', {
fontSize: '10px',
color: '#ffb8b8',
align: 'right',
}).setOrigin(1, 0);
this.status = fixedText(this.scale.width / 2, 102, '', {
fontSize: '15px',
color: '#ffffff',
backgroundColor: '#071018dd',
padding: { x: 14, y: 8 },
align: 'center',
}).setOrigin(0.5, 0);
this.controlsHint = fixedText(this.scale.width / 2, this.scale.height - 22, 'ARROWS move • Q/E broadside • SPACE brake • R restart', {
fontSize: '10px',
color: '#91a8b7',
backgroundColor: '#07101899',
padding: { x: 8, y: 4 },
}).setOrigin(0.5, 1);
}
createAnimations() { createAnimations() {
if (!this.anims.exists('player-idle')) { if (!this.anims.exists('player-idle')) {
this.anims.create({ this.anims.create({
@ -450,43 +483,50 @@ class GameScene extends Phaser.Scene {
} }
drawHud() { drawHud() {
const width = this.scale.width;
const compact = width < 700;
const heading = normalizeDegrees(this.player.angle); const heading = normalizeDegrees(this.player.angle);
const activePirates = this.pirates?.countActive(true) || 0; const activePirates = this.pirates?.countActive(true) || 0;
const { pirate: nearest, range } = this.nearestPirate(); const { pirate: nearest, range } = this.nearestPirate();
const rangeText = Number.isFinite(range) ? `${range.toFixed(0).padStart(4, ' ')}m` : ' --';
this.hud.setText([
`speed ${Math.abs(this.playerSpeed).toFixed(0).padStart(3, ' ')} ${this.playerSpeed < 0 ? 'REV' : 'FWD'}`,
`heading ${heading.toFixed(0).padStart(3, ' ')}°`,
`wave ${String(this.wave).padStart(3, ' ')}`,
`pirates ${String(activePirates).padStart(3, ' ')} nearest ${rangeText}`,
`defeated ${String(this.defeated).padStart(3, ' ')}`,
'',
'drag throttle + steer',
'Q / E port / starboard',
'SPACE brake',
]);
const w = this.scale.width;
const h = this.scale.height;
const barWidth = Math.min(280, w * 0.32);
const barHeight = 16;
const y = h - 38;
this.healthGraphics.clear();
this.healthGraphics.fillStyle(0x071018, 0.85);
this.healthGraphics.fillRect(18, y, barWidth, barHeight);
this.healthGraphics.fillRect(w - 18 - barWidth, y, barWidth, barHeight);
this.healthGraphics.fillStyle(0x61c98b, 1);
this.healthGraphics.fillRect(18, y, barWidth * (this.playerHull / PLAYER_MAX_HULL), barHeight);
const nearestHull = nearest ? (nearest.getData('hull') || 0) : 0; const nearestHull = nearest ? (nearest.getData('hull') || 0) : 0;
this.healthGraphics.fillStyle(0xd65757, 1); const rangeText = Number.isFinite(range) ? `${range.toFixed(0)}m` : '--';
this.healthGraphics.fillRect(w - 18 - barWidth, y, barWidth * (nearestHull / PIRATE_MAX_HULL), barHeight);
this.healthLabels.player.setText(`SPARROW ${this.playerHull}/${PLAYER_MAX_HULL}`); this.speedText.setText(`${Math.abs(this.playerSpeed).toFixed(0)} ${this.playerSpeed < 0 ? 'REV' : 'FWD'}`);
this.healthLabels.pirate.setText(nearest ? `NEAREST PIRATE ${nearestHull}/${PIRATE_MAX_HULL}` : `WAVE ${this.wave} CLEAR`); this.headingText.setText(`${heading.toFixed(0).padStart(3, '0')}°`);
this.waveText.setText(`WAVE ${this.wave} • ${activePirates} HOSTILE${activePirates === 1 ? '' : 'S'}`);
this.combatText.setText(compact
? `K ${this.defeated}\n${rangeText}`
: `DEFEATED ${this.defeated}\nRANGE ${rangeText}`);
this.playerHullLabel.setText(`SPARROW ${this.playerHull}/${PLAYER_MAX_HULL}`);
this.enemyHullLabel.setText(nearest
? `PIRATE ${nearestHull}/${PIRATE_MAX_HULL}`
: `WAVE ${this.wave} CLEAR`);
const side = 14;
const gap = 14;
const barY = compact ? 75 : 72;
const barHeight = compact ? 8 : 10;
const barWidth = (width - side * 2 - gap) / 2;
const enemyX = side + barWidth + gap;
this.hudGraphics.clear();
this.hudGraphics.fillStyle(0x071018, 0.78);
this.hudGraphics.fillRect(0, 0, width, compact ? 96 : 98);
this.hudGraphics.lineStyle(1, 0x8cd3ff, 0.15);
this.hudGraphics.lineBetween(0, compact ? 96 : 98, width, compact ? 96 : 98);
this.hudGraphics.fillStyle(0x17232d, 0.95);
this.hudGraphics.fillRoundedRect(side, barY, barWidth, barHeight, 4);
this.hudGraphics.fillRoundedRect(enemyX, barY, barWidth, barHeight, 4);
this.hudGraphics.fillStyle(0x61c98b, 1);
this.hudGraphics.fillRoundedRect(side, barY, barWidth * (this.playerHull / PLAYER_MAX_HULL), barHeight, 4);
if (nearest) {
this.hudGraphics.fillStyle(0xd65757, 1);
this.hudGraphics.fillRoundedRect(enemyX, barY, barWidth * (nearestHull / PIRATE_MAX_HULL), barHeight, 4);
}
} }
checkGameState() { checkGameState() {
@ -516,10 +556,17 @@ class GameScene extends Phaser.Scene {
layoutHud(gameSize) { layoutHud(gameSize) {
const width = gameSize.width; const width = gameSize.width;
const height = gameSize.height; const height = gameSize.height;
if (this.status) this.status.setPosition(width / 2, 22); const compact = width < 700;
if (this.healthLabels) {
this.healthLabels.player.setPosition(18, height - 64); if (this.headingText) this.headingText.setPosition(width / 2, 9);
this.healthLabels.pirate.setPosition(width - 18, height - 64); if (this.waveText) this.waveText.setPosition(width / 2, 32);
if (this.combatText) this.combatText.setPosition(width - (compact ? 76 : 14), 11);
if (this.enemyHullLabel) this.enemyHullLabel.setPosition(width - 14, compact ? 57 : 54);
if (this.playerHullLabel) this.playerHullLabel.setPosition(14, compact ? 57 : 54);
if (this.status) this.status.setPosition(width / 2, compact ? 104 : 108);
if (this.controlsHint) {
this.controlsHint.setPosition(width / 2, height - 12);
this.controlsHint.setVisible(!compact);
} }
} }
} }