feat(wolfenstein): add blue steel plate HUD bar with portrait and inventory

Replace the flat dark rectangle status bar with a baked riveted-metal "blue steel plate" background styled after Wolfenstein 3D's original HUD:

- Add `paintHudBar` to WolfensteinArt.js generating a 1920x140 texture with vertical steel gradient, bevel edges, recessed readout panels, divider strips with rivets, and a centered portrait socket sized around the new `HUD_PORTRAIT_SIZE` constant
- Add `paintProfile` fallback face and wire up `profile.png` (128x128, 3 health frames) in wolfenstein-artwork.json
- Rebuild `_buildHud` in WolfensteinGame.js: left panel shows episode/mission label + HEALTH value, center shows health-reactive portrait (frame swaps at 66/33 HP thresholds), right panel shows INVENTORY key icons + AMMO count with equipped weapon icon
- Derive weapon/key icon frames from `rules.items` data instead of hardcoded values so HUD stays in sync with wolfenstein-rules.json
- Add `_hudEpisodeMissionText()` helper for static per-level label text (campaign episode/mission or test level name)
- Apply subdued CRT overlay (`applyArcadeCRTOverlay`) tuned for a tactical-display feel, destroyed on teardown
- Switch HUD fonts to shared `m6x11` pixel font with white/ice-blue lettering; remove old KEY_COLORS constant and rectangular key indicators
This commit is contained in:
Brian Fertig 2026-08-22 20:45:39 -06:00
parent 0940cd7255
commit 8b213ea887
5 changed files with 294 additions and 23 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

View File

@ -6,7 +6,8 @@
"doors": { "key": "wolfenstein-doors", "path": "assets/images/wolfenstein/doors.png", "frameWidth": 64, "frameHeight": 64 },
"wallArt": { "key": "wolfenstein-wall-art", "path": "assets/images/wolfenstein/wall-art.png", "frameWidth": 64, "frameHeight": 64 },
"objects": { "key": "wolfenstein-objects", "path": "assets/images/wolfenstein/objects.png", "frameWidth": 64, "frameHeight": 64 },
"pickups": { "key": "wolfenstein-pickups", "path": "assets/images/wolfenstein/pickups.png", "frameWidth": 64, "frameHeight": 64 }
"pickups": { "key": "wolfenstein-pickups", "path": "assets/images/wolfenstein/pickups.png", "frameWidth": 64, "frameHeight": 64 },
"profile": { "key": "wolfenstein-profile", "path": "assets/images/wolfenstein/profile.png", "frameWidth": 128, "frameHeight": 128 }
},
"artwork": [
{ "key": "wolfenstein-bullet", "path": null },

View File

@ -21,6 +21,24 @@ export const WALL_COLORS = {
10: 0xe8ddb8, // cream tile
};
// Displayed size (px) of the live health-portrait Image in the HUD bar —
// exported so WolfensteinGame's setDisplaySize can never drift out of sync
// with the recessed "socket" baked into paintHudBar's wolf-hud-bar texture
// below, which is sized around this same constant.
export const HUD_PORTRAIT_SIZE = 118;
// "Blue steel plate" HUD bar palette (see paintHudBar) — kept local to this
// file rather than added to src/config.js's shared warm-gold COLORS, since
// it's a deliberately distinct look scoped to this one bar, the same "own
// literal, not worth the coupling" tradeoff KEY_COLORS already makes in
// WolfensteinGame.js/WolfensteinEditor.js.
const STEEL_LIGHT = 0x5d7fa3; // top of the plate's vertical gradient (overhead sheen)
const STEEL_DARK = 0x1c2c3d; // bottom of the plate's vertical gradient
const STEEL_MID = 0x3d5875; // divider-strip / bezel-ring body fill
const STEEL_HILITE = 0x9fc2e0; // crisp top bevel edge, rivet highlight flecks, ring outlines
const STEEL_SHADOW = 0x0b141d; // crisp bottom bevel edge, recess floors, rivet shade
const RIVET_BODY = 0x24384a;
export function ensureSprites(scene) {
if (scene.textures.exists('wolf-guard')) return;
paintGuard(scene);
@ -35,6 +53,8 @@ export function ensureSprites(scene) {
paintWeaponGatling(scene);
paintWeaponPlasmarifle(scene);
paintWeaponFists(scene);
paintHudBar(scene);
paintProfile(scene);
}
function paintGuard(scene) {
@ -213,3 +233,118 @@ function paintObject(scene) {
g.generateTexture('wolf-object', S, S);
g.destroy();
}
/** A dark base + body-colored disc + a small offset highlight fleck — reused at every rivet position across the bar (divider strips, portrait bezel corners, bookend corners). */
function drawRivet(g, x, y, r = 4) {
g.fillStyle(STEEL_SHADOW, 0.9);
g.fillCircle(x, y, r);
g.fillStyle(RIVET_BODY, 1);
g.fillCircle(x, y, r * 0.75);
g.fillStyle(STEEL_HILITE, 0.8);
g.fillCircle(x - r * 0.25, y - r * 0.25, r * 0.25);
}
/**
* The bottom HUD bar's background final art in its own right (like
* paintSparkle/paintBullet), not a placeholder awaiting a real PNG. A
* "blue steel plate" styled after the original Wolfenstein 3D's status
* bar: a vertical steel gradient base, crisp top-highlight/bottom-shadow
* bevel edges (raised-plate look), two riveted divider strips splitting it
* into left-info / center-portrait / right-info sub-panels, a recessed
* "readout" backing behind each side's text rows, and a recessed bezel
* "socket" at center sized around HUD_PORTRAIT_SIZE for the live health
* portrait (WolfensteinGame._buildHud) to sit in. All measurements here are
* local canvas coordinates (0,0 top-left of this 1920x140 bake) the
* caller places the resulting image at its natural size centered on the
* bar's actual screen position, so local (x,y) maps 1:1 to screen
* (x, y + 940).
*/
function paintHudBar(scene) {
const W = 1920, H = 140;
const g = scene.make.graphics({ x: 0, y: 0, add: false });
// Base plate: light at top (overhead sheen) to dark at bottom.
g.fillGradientStyle(STEEL_LIGHT, STEEL_LIGHT, STEEL_DARK, STEEL_DARK, 1);
g.fillRect(0, 0, W, H);
// Two faint diagonal specular sheens across the upper half — a cheap
// brushed-metal touch, not meant to read as a distinct shape on its own.
g.fillStyle(0xffffff, 0.05);
g.fillTriangle(0, 0, W * 0.4, 0, W * 0.15, H * 0.5);
g.fillTriangle(W * 0.55, 0, W, 0, W * 0.85, H * 0.4);
// Crisp top-highlight / bottom-shadow bevel edges.
g.fillStyle(STEEL_HILITE, 0.9);
g.fillRect(0, 0, W, 3);
g.fillStyle(STEEL_SHADOW, 0.9);
g.fillRect(0, H - 3, W, 3);
// Recessed "readout" backing behind each side's text rows — separates the
// text zones from the raised bevel/rivet zones, reads as a sunken
// instrument panel rather than text just floating on the bare plate.
const backingY = 10, backingH = H - 20;
g.fillStyle(STEEL_SHADOW, 0.35);
g.fillRoundedRect(56, backingY, 846 - 56, backingH, 8);
g.fillRoundedRect(1074, backingY, 1864 - 1074, backingH, 8);
g.lineStyle(1, STEEL_HILITE, 0.25);
g.strokeRoundedRect(56, backingY, 846 - 56, backingH, 8);
g.strokeRoundedRect(1074, backingY, 1864 - 1074, backingH, 8);
// Divider strips separating left / portrait / right sub-panels, each a
// raised bar with its own light/dark side bevels and two rivets.
for (const dx of [856, 1044]) {
g.fillStyle(STEEL_MID, 1);
g.fillRect(dx, 0, 20, H);
g.fillStyle(STEEL_HILITE, 0.7);
g.fillRect(dx, 0, 2, H);
g.fillStyle(STEEL_SHADOW, 0.7);
g.fillRect(dx + 18, 0, 2, H);
drawRivet(g, dx + 10, 26);
drawRivet(g, dx + 10, H - 26);
}
// Portrait bezel: a raised outer ring (4 corner rivets) around a
// recessed inner socket exactly HUD_PORTRAIT_SIZE square, centered.
const bw = 136, bh = 136, bx = W / 2 - bw / 2, by = 2;
g.fillStyle(STEEL_MID, 1);
g.fillRoundedRect(bx, by, bw, bh, 12);
g.lineStyle(2, STEEL_HILITE, 0.8);
g.strokeRoundedRect(bx, by, bw, bh, 12);
const margin = (bw - HUD_PORTRAIT_SIZE) / 2;
const sx = bx + margin, sy = by + margin;
g.fillStyle(STEEL_SHADOW, 0.85);
g.fillRoundedRect(sx, sy, HUD_PORTRAIT_SIZE, HUD_PORTRAIT_SIZE, 6);
g.lineStyle(2, STEEL_SHADOW, 1);
g.strokeRoundedRect(sx, sy, HUD_PORTRAIT_SIZE, HUD_PORTRAIT_SIZE, 6);
drawRivet(g, bx + 8, by + 12);
drawRivet(g, bx + bw - 8, by + 12);
drawRivet(g, bx + 8, by + bh - 12);
drawRivet(g, bx + bw - 8, by + bh - 12);
// Bookend rivets near the far left/right edges, slightly larger — gives
// the whole plate a structural, riveted-steel-panel finish.
drawRivet(g, 34, 22, 5); drawRivet(g, 34, H - 22, 5);
drawRivet(g, W - 34, 22, 5); drawRivet(g, W - 34, H - 22, 5);
g.generateTexture('wolf-hud-bar', W, H);
g.destroy();
}
// Single-frame fallback face if wolfenstein-profile isn't loaded — same
// "one generic placeholder regardless of variant" idiom as paintPickup/
// paintObject, since a static fallback has no health value to react to
// anyway. WolfensteinGame._updateHud skips the per-health frame swap
// entirely when this is what's showing (see this._profileHasFrames).
function paintProfile(scene) {
const S = 128;
const g = scene.make.graphics({ x: 0, y: 0, add: false });
g.fillStyle(0x1a2230, 1);
g.fillRect(0, 0, S, S);
g.fillStyle(0x5a4632, 1); // hair
g.fillRoundedRect(S * 0.2, S * 0.12, S * 0.6, S * 0.24, 8);
g.fillStyle(0xd8b48a, 1); // skin tone, matches paintGuard's face color
g.fillCircle(S * 0.5, S * 0.56, S * 0.3);
g.generateTexture('wolf-profile', S, S);
g.destroy();
}

View File

@ -15,14 +15,23 @@ import * as Logic from './WolfensteinLogic.js';
import WolfensteinView, { VIEW_H } from './WolfensteinView.js';
import * as Screens from './WolfensteinScreens.js';
import { makeCamera } from './WolfensteinRaycaster.js';
import { ensureSprites, HUD_PORTRAIT_SIZE } from './WolfensteinArt.js';
import { applyArcadeCRTOverlay } from '../../ui/ArcadeCRTOverlay.js';
// Same blue/red/yellow palette as WolfensteinView's DOOR_COLOR_HEX and
// WolfensteinEditor's LOCK_COLORS — kept as its own literal here rather than
// imported, same "not worth the coupling for a 3-entry table" tradeoff those
// two make with each other.
const KEY_COLORS = { blue: 0x2a5adf, red: 0xd82a2a, yellow: 0xe0b820 };
const capitalize = (s) => s.charAt(0).toUpperCase() + s.slice(1);
// The bottom HUD bar + its immediate gameplay-time neighbors (door/lock
// hints, toast) use the repo's shared retro-arcade pixel font, matching the
// "blue steel plate" bar's own military-tactical-display look — same
// fallback-chain convention every other m6x11-using game here uses (see
// e.g. TotalAnnihilation's TAHud.js `FONT` export). Menu/editor screens are
// untouched and keep "Julius Sans One".
const FONT = 'm6x11, "Julius Sans One"';
// White/ice-blue lettering for the new bar specifically — deliberately NOT
// the shared warm-cream COLORS.textHex, per the "white lettering" ask.
const HUD_TEXT_VALUE = '#ffffff';
const HUD_TEXT_LABEL = '#c9def0';
const SAVE_KEY = 'wolfenstein-save';
const SAVE_SLOT_COUNT = 4;
const saveSlotKey = (i) => `wolfenstein-save-slot-${i}`;
@ -55,6 +64,19 @@ export default class WolfensteinGame extends Phaser.Scene {
this._bindPointerLock();
// Whole-scene retro-arcade dressing (scanlines + curved-CRT vignette),
// matching sibling "Arcade, Console & PC" games — tuned toward a
// subdued tactical-display feel (shallower curve/vignette than the
// module's own defaults, steel-blue tint) rather than a bouncy arcade
// cabinet look, to fit a serious FPS. Destroyed in _teardown().
this.crt = applyArcadeCRTOverlay(this, {
scanlineTint: 0x8fc7ff,
accentTint: 0x5d7fa3,
curveAmount: 0.22,
curveVignetteStrength: 0.3,
curveAberration: 0.0025,
});
this.hud = this._buildHud();
this._setHudVisible(false);
@ -170,6 +192,7 @@ export default class WolfensteinGame extends Phaser.Scene {
const model = Logic.buildLevelModel(levelJson);
this.state = Logic.createState(model, this.rules, meta.carry ?? null);
this.meta = meta;
this.hud.episodeMission.setText(this._hudEpisodeMissionText());
this.view?.destroy();
this.view = new WolfensteinView(this, this.rules);
this.swapScreen(null);
@ -189,6 +212,7 @@ export default class WolfensteinGame extends Phaser.Scene {
mode: 'campaign', campaignId: restored.levelMeta.campaignId, missionIndex: restored.levelMeta.missionIndex,
carry: this._extractCarry(restored.player),
};
this.hud.episodeMission.setText(this._hudEpisodeMissionText());
this.view?.destroy();
this.view = new WolfensteinView(this, this.rules);
this.swapScreen(null);
@ -350,26 +374,117 @@ export default class WolfensteinGame extends Phaser.Scene {
// ------------------------------------------------------------- HUD
/**
* Static per-level text for the left panel's label row computed once
* (see _beginLevel/_resumeState), not every frame, since neither the
* episode/mission numbering nor a Test Play level's name changes
* mid-level. Episode number comes from the campaign's own POSITION in
* this.campaigns.campaigns (index+1) rather than parsing meta.campaignId
* (e.g. "episode1") more robust if campaign ids are ever renamed, and
* the same array showCampaignList/_onMissionWon already use.
*/
_hudEpisodeMissionText() {
if (this.meta?.mode === 'campaign') {
const idx = this.campaigns.campaigns.findIndex((c) => c.id === this.meta.campaignId);
return `EPISODE ${idx >= 0 ? idx + 1 : '?'} · MISSION ${this.meta.missionIndex + 1}`;
}
// Test Play (and any future non-campaign mode) has no campaign/mission numbering.
return this.state?.levelMeta?.name ? this.state.levelMeta.name.toUpperCase() : 'TEST LEVEL';
}
/**
* "Blue steel plate" status bar, styled after the original Wolfenstein
* 3D's a baked riveted-metal background (WolfensteinArt.paintHudBar)
* with a health-reactive portrait centered, episode/mission + health on
* the left, and ammo + the equipped weapon's icon + held-key inventory
* on the right. The left/right text below sits over paintHudBar's own
* baked recessed "readout" panels at local-x 56-846 / 1074-1864 which
* map 1:1 to these same screen-x values, since the bar image is centered
* at its natural 1920px width so the label row (y=972) and value row
* (y=1042) positions below must stay in sync with that texture if either
* one changes.
*/
_buildHud() {
const y = GAME_HEIGHT - 70;
// Needed here — not just inside WolfensteinView's own constructor,
// which doesn't run until a level actually begins — because this HUD
// is built once in create(), before any level/view exists, and now
// needs wolf-hud-bar/wolf-profile already baked. Idempotent (guarded
// by an existence check), so the later call from WolfensteinView is a
// cheap no-op.
ensureSprites(this);
const y = GAME_HEIGHT - 70; // bar vertical center, screen y=1010
const labelY = y - 38; // ~972 — episode/mission, INVENTORY: + key icons
const valueY = y + 32; // ~1042 — health, ammo + weapon icon
const objs = {};
objs.bar = this.add.rectangle(GAME_WIDTH / 2, GAME_HEIGHT - 70, GAME_WIDTH, 140, 0x0a0806, 0.92).setDepth(20);
objs.health = this.add.text(80, y, '', { fontFamily: '"Julius Sans One"', fontSize: '34px', color: COLORS.textHex }).setOrigin(0, 0.5).setDepth(21);
objs.weapon = this.add.text(GAME_WIDTH / 2, y, '', { fontFamily: '"Julius Sans One"', fontSize: '34px', color: COLORS.textHex }).setOrigin(0.5).setDepth(21);
objs.ammo = this.add.text(GAME_WIDTH - 80, y, '', { fontFamily: '"Julius Sans One"', fontSize: '34px', color: COLORS.textHex }).setOrigin(1, 0.5).setDepth(21);
objs.toast = this.add.text(GAME_WIDTH / 2, GAME_HEIGHT - 170, '', { fontFamily: '"Julius Sans One"', fontSize: '26px', color: COLORS.goldHex }).setOrigin(0.5).setDepth(21).setAlpha(0);
objs.bar = this.add.image(GAME_WIDTH / 2, y, 'wolf-hud-bar').setDepth(20);
// Left panel: episode/mission (see _hudEpisodeMissionText) above health.
objs.episodeMission = this.add.text(80, labelY, '', {
fontFamily: FONT, fontSize: '22px', color: HUD_TEXT_LABEL, letterSpacing: 2,
}).setOrigin(0, 0.5).setDepth(21);
objs.health = this.add.text(80, valueY, '', {
fontFamily: FONT, fontSize: '36px', fontStyle: 'bold', color: HUD_TEXT_VALUE,
}).setOrigin(0, 0.5).setDepth(21);
// Center: health-reactive portrait, sitting in wolf-hud-bar's own baked
// recessed socket (sized around HUD_PORTRAIT_SIZE). _profileHasFrames
// gates the per-health frame swap in _updateHud — the single-frame
// fallback texture has nothing to swap to.
this._profileHasFrames = this.textures.exists('wolfenstein-profile');
objs.portrait = this.add
.image(GAME_WIDTH / 2, y, this._profileHasFrames ? 'wolfenstein-profile' : 'wolf-profile')
.setDisplaySize(HUD_PORTRAIT_SIZE, HUD_PORTRAIT_SIZE).setDepth(21);
// Right panel: INVENTORY: + up to 3 held-key icons above ammo + the
// equipped weapon's icon. Both icon rows reuse wolfenstein-pickups —
// already loaded for world pickups, same art the player already
// recognizes, no separate HUD-icon asset needed. Frame lookups are
// built from rules data (not re-hardcoded numbers) so the HUD can never
// disagree with data/wolfenstein-rules.json's own frame assignments —
// plain instance fields, NOT inside `objs`/`this.hud`, since
// _setHudVisible blindly calls .setVisible() on every this.hud value.
this._pickupsTextureOk = this.textures.exists('wolfenstein-pickups');
this._weaponIconFrame = Object.fromEntries(
this.rules.items.filter((i) => i.kind === 'weapon').map((i) => [i.grantsWeapon, i.frame]),
);
this._keyIconFrame = Object.fromEntries(
this.rules.items.filter((i) => i.kind === 'key').map((i) => [i.color, i.frame]),
);
objs.inventoryLabel = this.add.text(1725, labelY, 'INVENTORY:', {
fontFamily: FONT, fontSize: '22px', color: HUD_TEXT_LABEL, letterSpacing: 2,
}).setOrigin(1, 0.5).setDepth(21);
// Anchored near the right edge (not adjacent to the label's own
// right-aligned width) so the label can never grow into these — same
// "each independently anchored, never collide" idea the label/value
// rows on both sides already lean on.
const keyX = { blue: 1751, red: 1785, yellow: 1819 };
for (const color of ['blue', 'red', 'yellow']) {
objs[`key_${color}`] = this.add
.image(keyX[color], labelY, 'wolfenstein-pickups', this._keyIconFrame[color])
.setDisplaySize(30, 30).setDepth(21).setVisible(false);
}
// Icon sits to the RIGHT of the ammo text's right-edge anchor, so a
// longer ammo count (up to "AMMO 200") can only grow further left,
// never toward — let alone under — the icon. Starts hidden (like the
// key icons above) rather than showing frame 0's pistol icon for one
// tick regardless of what's actually equipped — _updateHud's first
// pass sets both the correct frame and visibility together before this
// is ever actually seen.
objs.weaponIcon = this.add.image(1815, valueY, 'wolfenstein-pickups').setDisplaySize(48, 48).setDepth(21).setVisible(false);
objs.ammo = this.add.text(1790, valueY, '', {
fontFamily: FONT, fontSize: '36px', fontStyle: 'bold', color: HUD_TEXT_VALUE,
}).setOrigin(1, 0.5).setDepth(21);
objs.toast = this.add.text(GAME_WIDTH / 2, GAME_HEIGHT - 170, '', { fontFamily: FONT, fontSize: '26px', color: COLORS.goldHex }).setOrigin(0.5).setDepth(21).setAlpha(0);
objs.crosshair = this.add.text(GAME_WIDTH / 2, VIEW_H / 2, '+', { fontSize: '38px', color: COLORS.textHex }).setOrigin(0.5).setDepth(15);
objs.lockHint = this.add.text(GAME_WIDTH / 2, 60, 'Click to aim', { fontFamily: '"Julius Sans One"', fontSize: '22px', color: COLORS.mutedHex }).setOrigin(0.5).setDepth(21);
objs.lockHint = this.add.text(GAME_WIDTH / 2, 60, 'Click to aim', { fontFamily: FONT, fontSize: '22px', color: COLORS.mutedHex }).setOrigin(0.5).setDepth(21);
// Doors no longer open automatically on approach (see openNearestDoor) —
// without this, there's no way to discover that Space/E is the interact key.
objs.doorHint = this.add.text(GAME_WIDTH / 2, VIEW_H / 2 + 46, '[SPACE/E] Open', { fontFamily: '"Julius Sans One"', fontSize: '22px', color: COLORS.goldHex }).setOrigin(0.5).setDepth(21);
// A small keyring above the health readout — one square per color,
// hidden until that key is actually picked up this level (see
// _updateHud). Flat individually-keyed objects, not a Container/group,
// because _setHudVisible below just iterates Object.values(this.hud).
objs.key_blue = this.add.rectangle(80, y - 42, 22, 22, KEY_COLORS.blue).setStrokeStyle(2, 0x000000).setDepth(21).setVisible(false);
objs.key_red = this.add.rectangle(112, y - 42, 22, 22, KEY_COLORS.red).setStrokeStyle(2, 0x000000).setDepth(21).setVisible(false);
objs.key_yellow = this.add.rectangle(144, y - 42, 22, 22, KEY_COLORS.yellow).setStrokeStyle(2, 0x000000).setDepth(21).setVisible(false);
objs.doorHint = this.add.text(GAME_WIDTH / 2, VIEW_H / 2 + 46, '[SPACE/E] Open', { fontFamily: FONT, fontSize: '22px', color: COLORS.goldHex }).setOrigin(0.5).setDepth(21);
return objs;
}
@ -385,15 +500,34 @@ export default class WolfensteinGame extends Phaser.Scene {
this.hud.key_blue.setVisible(false);
this.hud.key_red.setVisible(false);
this.hud.key_yellow.setVisible(false);
// Same reasoning — would otherwise flash frame 0's pistol icon for a
// tick regardless of what's actually equipped (e.g. a fresh
// fists-only campaign start).
this.hud.weaponIcon.setVisible(false);
}
}
_updateHud() {
const p = this.state.player;
this.hud.health.setText(`HP ${Math.ceil(p.health)}`);
this.hud.weapon.setText(p.weapon.toUpperCase());
this.hud.health.setText(`HEALTH ${Math.ceil(p.health)}`);
// >=66 calm, >=33 bloodied, else badly wounded — matches profile.png's
// 3 frames. Skipped when only the single-frame fallback is loaded (see
// _buildHud's _profileHasFrames), which has nothing to swap to.
if (this._profileHasFrames) {
this.hud.portrait.setFrame(p.health >= 66 ? 0 : p.health >= 33 ? 1 : 2);
}
const w = this.rules.weaponById[p.weapon];
this.hud.ammo.setText(w.kind === 'projectile' ? `AMMO ${p.ammo[w.ammoType] ?? 0}` : '');
// Fists has no world-pickup icon (it was never something you find lying
// around) — _weaponIconFrame simply has no entry for it, so this hides
// the icon right alongside the ammo text blanking above, no separate
// fists special-case needed.
const iconFrame = this._weaponIconFrame[p.weapon];
const showWeaponIcon = this._pickupsTextureOk && iconFrame != null;
this.hud.weaponIcon.setVisible(showWeaponIcon);
if (showWeaponIcon) this.hud.weaponIcon.setFrame(iconFrame);
this.hud.lockHint.setVisible(!this._locked);
this.hud.key_blue.setVisible(p.keys.includes('blue'));
this.hud.key_red.setVisible(p.keys.includes('red'));
@ -489,5 +623,6 @@ export default class WolfensteinGame extends Phaser.Scene {
document.removeEventListener('mousemove', this._onMouseMove);
this._exitLock();
this.view?.destroy();
this.crt?.destroy();
}
}