Refactor codebase
This commit is contained in:
197
src/ui/GameUI.ts
Normal file
197
src/ui/GameUI.ts
Normal file
@@ -0,0 +1,197 @@
|
||||
import Phaser from "phaser";
|
||||
import { type World, type EntityId } from "../core/types";
|
||||
import { GAME_CONFIG } from "../core/config/GameConfig";
|
||||
|
||||
export default class GameUI extends Phaser.Scene {
|
||||
// HUD
|
||||
private levelText!: Phaser.GameObjects.Text;
|
||||
private healthBar!: Phaser.GameObjects.Graphics;
|
||||
|
||||
// Menu
|
||||
private menuOpen = false;
|
||||
private menuContainer!: Phaser.GameObjects.Container;
|
||||
private menuText!: Phaser.GameObjects.Text;
|
||||
private menuBg!: Phaser.GameObjects.Rectangle;
|
||||
private menuButton!: Phaser.GameObjects.Container;
|
||||
private mapButton!: Phaser.GameObjects.Container;
|
||||
|
||||
constructor() {
|
||||
super({ key: "GameUI" });
|
||||
}
|
||||
|
||||
create() {
|
||||
this.createHud();
|
||||
this.createMenu();
|
||||
|
||||
// Listen for updates from GameScene
|
||||
const gameScene = this.scene.get("GameScene");
|
||||
gameScene.events.on("update-ui", (data: { world: World; playerId: EntityId; levelIndex: number }) => {
|
||||
this.updateUI(data.world, data.playerId, data.levelIndex);
|
||||
});
|
||||
|
||||
gameScene.events.on("toggle-menu", () => this.toggleMenu());
|
||||
gameScene.events.on("close-menu", () => this.setMenuOpen(false));
|
||||
}
|
||||
|
||||
private createHud() {
|
||||
this.levelText = this.add.text(10, 10, "Level 1", {
|
||||
fontSize: "20px",
|
||||
color: "#ffffff",
|
||||
fontStyle: "bold"
|
||||
}).setDepth(100);
|
||||
|
||||
this.healthBar = this.add.graphics().setDepth(100);
|
||||
}
|
||||
|
||||
private createMenu() {
|
||||
const cam = this.cameras.main;
|
||||
|
||||
const btnW = 90;
|
||||
const btnH = 28;
|
||||
|
||||
// Menu Button
|
||||
const btnBg = this.add.rectangle(0, 0, btnW, btnH, 0x000000, 0.6).setStrokeStyle(1, 0xffffff, 0.8);
|
||||
const btnLabel = this.add.text(0, 0, "Menu", { fontSize: "14px", color: "#ffffff" }).setOrigin(0.5);
|
||||
|
||||
this.menuButton = this.add.container(0, 0, [btnBg, btnLabel]);
|
||||
this.menuButton.setDepth(1000);
|
||||
|
||||
const placeButton = () => {
|
||||
this.menuButton.setPosition(cam.width - btnW / 2 - 10, btnH / 2 + 10);
|
||||
};
|
||||
placeButton();
|
||||
this.scale.on("resize", placeButton);
|
||||
|
||||
btnBg.setInteractive({ useHandCursor: true }).on("pointerdown", () => this.toggleMenu());
|
||||
|
||||
// Map Button (left of Menu button)
|
||||
const mapBtnBg = this.add.rectangle(0, 0, btnW, btnH, 0x000000, 0.6).setStrokeStyle(1, 0xffffff, 0.8);
|
||||
const mapBtnLabel = this.add.text(0, 0, "Map", { fontSize: "14px", color: "#ffffff" }).setOrigin(0.5);
|
||||
|
||||
this.mapButton = this.add.container(0, 0, [mapBtnBg, mapBtnLabel]);
|
||||
this.mapButton.setDepth(1000);
|
||||
|
||||
const placeMapButton = () => {
|
||||
this.mapButton.setPosition(cam.width - btnW / 2 - 10 - btnW - 5, btnH / 2 + 10);
|
||||
};
|
||||
placeMapButton();
|
||||
this.scale.on("resize", placeMapButton);
|
||||
|
||||
mapBtnBg.setInteractive({ useHandCursor: true }).on("pointerdown", () => this.toggleMap());
|
||||
|
||||
// Panel (center)
|
||||
const panelW = GAME_CONFIG.ui.menuPanelWidth;
|
||||
const panelH = GAME_CONFIG.ui.menuPanelHeight;
|
||||
|
||||
this.menuBg = this.add
|
||||
.rectangle(0, 0, panelW, panelH, 0x000000, 0.8)
|
||||
.setStrokeStyle(1, 0xffffff, 0.9)
|
||||
.setInteractive(); // capture clicks
|
||||
|
||||
this.menuText = this.add
|
||||
.text(-panelW / 2 + 14, -panelH / 2 + 12, "", {
|
||||
fontSize: "14px",
|
||||
color: "#ffffff",
|
||||
wordWrap: { width: panelW - 28 }
|
||||
})
|
||||
.setOrigin(0, 0);
|
||||
|
||||
this.menuContainer = this.add.container(0, 0, [this.menuBg, this.menuText]);
|
||||
this.menuContainer.setDepth(1001);
|
||||
|
||||
const placePanel = () => {
|
||||
this.menuContainer.setPosition(cam.width / 2, cam.height / 2);
|
||||
};
|
||||
placePanel();
|
||||
this.scale.on("resize", placePanel);
|
||||
|
||||
this.setMenuOpen(false);
|
||||
}
|
||||
|
||||
private toggleMenu() {
|
||||
this.setMenuOpen(!this.menuOpen);
|
||||
// Request UI update when menu is opened to populate the text
|
||||
if (this.menuOpen) {
|
||||
const gameScene = this.scene.get("GameScene");
|
||||
gameScene.events.emit("request-ui-update");
|
||||
}
|
||||
}
|
||||
|
||||
private setMenuOpen(open: boolean) {
|
||||
this.menuOpen = open;
|
||||
this.menuContainer.setVisible(open);
|
||||
|
||||
// Notify GameScene back?
|
||||
const gameScene = this.scene.get("GameScene");
|
||||
gameScene.events.emit("menu-toggled", open);
|
||||
}
|
||||
|
||||
private toggleMap() {
|
||||
// Close menu and toggle minimap
|
||||
this.setMenuOpen(false);
|
||||
const gameScene = this.scene.get("GameScene");
|
||||
gameScene.events.emit("toggle-minimap");
|
||||
}
|
||||
|
||||
private updateUI(world: World, playerId: EntityId, levelIndex: number) {
|
||||
this.updateHud(world, playerId, levelIndex);
|
||||
if (this.menuOpen) {
|
||||
this.updateMenuText(world, playerId, levelIndex);
|
||||
}
|
||||
}
|
||||
|
||||
private updateHud(world: World, playerId: EntityId, levelIndex: number) {
|
||||
this.levelText.setText(`Level ${levelIndex}`);
|
||||
|
||||
const p = world.actors.get(playerId);
|
||||
if (!p || !p.stats) return;
|
||||
|
||||
const barX = 10;
|
||||
const barY = 40;
|
||||
const barW = 200;
|
||||
const barH = 16;
|
||||
|
||||
this.healthBar.clear();
|
||||
this.healthBar.fillStyle(0x444444, 1);
|
||||
this.healthBar.fillRect(barX, barY, barW, barH);
|
||||
|
||||
const hp = Math.max(0, p.stats.hp);
|
||||
const maxHp = Math.max(1, p.stats.maxHp);
|
||||
const pct = Phaser.Math.Clamp(hp / maxHp, 0, 1);
|
||||
const fillW = Math.floor(barW * pct);
|
||||
|
||||
this.healthBar.fillStyle(0xff0000, 1);
|
||||
this.healthBar.fillRect(barX, barY, fillW, barH);
|
||||
|
||||
this.healthBar.lineStyle(2, 0xffffff, 1);
|
||||
this.healthBar.strokeRect(barX, barY, barW, barH);
|
||||
}
|
||||
|
||||
private updateMenuText(world: World, playerId: EntityId, levelIndex: number) {
|
||||
const p = world.actors.get(playerId);
|
||||
const stats = p?.stats;
|
||||
const inv = p?.inventory;
|
||||
|
||||
const lines: string[] = [];
|
||||
lines.push(`Level ${levelIndex}`);
|
||||
lines.push("");
|
||||
lines.push("Stats");
|
||||
lines.push(` HP: ${stats?.hp ?? 0}/${stats?.maxHp ?? 0}`);
|
||||
lines.push(` Attack: ${stats?.attack ?? 0}`);
|
||||
lines.push(` Defense: ${stats?.defense ?? 0}`);
|
||||
lines.push(` Speed: ${p?.speed ?? 0}`);
|
||||
lines.push("");
|
||||
lines.push("Inventory");
|
||||
lines.push(` Gold: ${inv?.gold ?? 0}`);
|
||||
lines.push(` Items: ${(inv?.items?.length ?? 0) === 0 ? "(none)" : ""}`);
|
||||
|
||||
if (inv?.items?.length) {
|
||||
for (const it of inv.items) lines.push(` - ${it}`);
|
||||
}
|
||||
|
||||
lines.push("");
|
||||
lines.push("Hotkeys: I to toggle, Esc to close");
|
||||
|
||||
this.menuText.setText(lines.join("\n"));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user