From 8fdb313cb22daff9368c45a76e32d226968bd50d Mon Sep 17 00:00:00 2001 From: Admin Date: Thu, 17 Sep 2026 22:10:38 +0000 Subject: [PATCH] Redesign responsive combat HUD --- src/game-v4.js | 185 +++++++++++++++++++++++++++++++------------------ 1 file changed, 116 insertions(+), 69 deletions(-) diff --git a/src/game-v4.js b/src/game-v4.js index 2023202..8c84dac 100644 --- a/src/game-v4.js +++ b/src/game-v4.js @@ -90,39 +90,7 @@ class GameScene extends Phaser.Scene { this.cameras.main.startFollow(this.player, true, 0.08, 0.08); this.cameras.main.setZoom(1.12); - this.title = this.add.text(18, 16, 'ARROWSTACK AIRSHIP', { - 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.createHud(); this.scale.on('resize', this.layoutHud, this); this.events.once(Phaser.Scenes.Events.SHUTDOWN, () => { @@ -132,6 +100,71 @@ class GameScene extends Phaser.Scene { 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() { if (!this.anims.exists('player-idle')) { this.anims.create({ @@ -450,43 +483,50 @@ class GameScene extends Phaser.Scene { } drawHud() { + const width = this.scale.width; + const compact = width < 700; const heading = normalizeDegrees(this.player.angle); const activePirates = this.pirates?.countActive(true) || 0; 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; - this.healthGraphics.fillStyle(0xd65757, 1); - this.healthGraphics.fillRect(w - 18 - barWidth, y, barWidth * (nearestHull / PIRATE_MAX_HULL), barHeight); + const rangeText = Number.isFinite(range) ? `${range.toFixed(0)}m` : '--'; - this.healthLabels.player.setText(`SPARROW ${this.playerHull}/${PLAYER_MAX_HULL}`); - this.healthLabels.pirate.setText(nearest ? `NEAREST PIRATE ${nearestHull}/${PIRATE_MAX_HULL}` : `WAVE ${this.wave} CLEAR`); + this.speedText.setText(`${Math.abs(this.playerSpeed).toFixed(0)} ${this.playerSpeed < 0 ? 'REV' : 'FWD'}`); + 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() { @@ -516,10 +556,17 @@ class GameScene extends Phaser.Scene { layoutHud(gameSize) { const width = gameSize.width; const height = gameSize.height; - if (this.status) this.status.setPosition(width / 2, 22); - if (this.healthLabels) { - this.healthLabels.player.setPosition(18, height - 64); - this.healthLabels.pirate.setPosition(width - 18, height - 64); + const compact = width < 700; + + if (this.headingText) this.headingText.setPosition(width / 2, 9); + 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); } } }