Add levelling up mechanics through experience gained via killing enemies

This commit is contained in:
Peter Stockings
2026-01-04 18:36:31 +11:00
parent 42cd77998d
commit 29e46093f5
11 changed files with 373 additions and 84 deletions

View File

@@ -1,11 +1,13 @@
import Phaser from "phaser";
import { type World, type EntityId } from "../core/types";
import { type World, type EntityId, type Stats } from "../core/types";
import { GAME_CONFIG } from "../core/config/GameConfig";
export default class GameUI extends Phaser.Scene {
// HUD
private levelText!: Phaser.GameObjects.Text;
private floorText!: Phaser.GameObjects.Text;
private healthBar!: Phaser.GameObjects.Graphics;
private expBar!: Phaser.GameObjects.Graphics;
// Menu
private menuOpen = false;
@@ -31,22 +33,23 @@ export default class GameUI extends Phaser.Scene {
// 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("update-ui", (data: { world: World; playerId: EntityId; floorIndex: number }) => {
this.updateUI(data.world, data.playerId, data.floorIndex);
});
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", {
this.floorText = this.add.text(10, 10, "Floor 1", {
fontSize: "20px",
color: "#ffffff",
fontStyle: "bold"
}).setDepth(100);
this.healthBar = this.add.graphics().setDepth(100);
this.expBar = this.add.graphics().setDepth(100);
}
private createMenu() {
@@ -163,11 +166,13 @@ export default class GameUI extends Phaser.Scene {
this.deathContainer.setVisible(false);
}
showDeathScreen(data: { level: number; gold: number; stats: any }) {
showDeathScreen(data: { floor: number; gold: number; stats: Stats }) {
const lines = [
`Dungeon Level: ${data.level}`,
`Dungeon Floor: ${data.floor}`,
`Gold Collected: ${data.gold}`,
"",
`Experience gained: ${data.stats.exp}`,
`Final HP: 0 / ${data.stats.maxHp}`,
`Attack: ${data.stats.attack}`,
`Defense: ${data.stats.defense}`
@@ -211,28 +216,40 @@ export default class GameUI extends Phaser.Scene {
gameScene.events.emit("toggle-minimap");
}
private updateUI(world: World, playerId: EntityId, levelIndex: number) {
this.updateHud(world, playerId, levelIndex);
private updateUI(world: World, playerId: EntityId, floorIndex: number) {
this.updateHud(world, playerId, floorIndex);
if (this.menuOpen) {
this.updateMenuText(world, playerId, levelIndex);
this.updateMenuText(world, playerId, floorIndex);
}
}
private updateHud(world: World, playerId: EntityId, levelIndex: number) {
this.levelText.setText(`Level ${levelIndex}`);
private updateHud(world: World, playerId: EntityId, floorIndex: number) {
this.floorText.setText(`Floor ${floorIndex}`);
const p = world.actors.get(playerId);
if (!p || !p.stats) return;
const barX = 10;
const barX = 40;
const barY = 40;
const barW = 200;
const barW = 180;
const barH = 16;
this.healthBar.clear();
// Heart Icon
const iconX = 20;
const iconY = barY + barH / 2;
this.healthBar.fillStyle(0xff0000, 1);
// Draw simple heart
this.healthBar.fillCircle(iconX - 4, iconY - 2, 5);
this.healthBar.fillCircle(iconX + 4, iconY - 2, 5);
this.healthBar.fillTriangle(iconX - 9, iconY - 1, iconX + 9, iconY - 1, iconX, iconY + 9);
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);
@@ -243,21 +260,54 @@ export default class GameUI extends Phaser.Scene {
this.healthBar.lineStyle(2, 0xffffff, 1);
this.healthBar.strokeRect(barX, barY, barW, barH);
// EXP Bar
const expY = barY + barH + 6;
const expH = 10;
this.expBar.clear();
// EXP Icon (Star/Orb)
const expIconY = expY + expH / 2;
this.expBar.fillStyle(GAME_CONFIG.rendering.expOrbColor, 1);
this.expBar.fillCircle(iconX, expIconY, 6);
this.expBar.fillStyle(0xffffff, 0.5);
this.expBar.fillCircle(iconX - 2, expIconY - 2, 2);
this.expBar.fillStyle(0x444444, 1);
this.expBar.fillRect(barX, expY, barW, expH);
const exp = p.stats.exp;
const nextExp = Math.max(1, p.stats.expToNextLevel);
const expPct = Phaser.Math.Clamp(exp / nextExp, 0, 1);
const expFillW = Math.floor(barW * expPct);
this.expBar.fillStyle(GAME_CONFIG.rendering.expOrbColor, 1);
this.expBar.fillRect(barX, expY, expFillW, expH);
this.expBar.lineStyle(1, 0xffffff, 0.8);
this.expBar.strokeRect(barX, expY, barW, expH);
}
private updateMenuText(world: World, playerId: EntityId, levelIndex: number) {
private updateMenuText(world: World, playerId: EntityId, _floorIndex: number) {
const p = world.actors.get(playerId);
const stats = p?.stats;
const inv = p?.inventory;
const lines: string[] = [];
lines.push(`Level ${levelIndex}`);
lines.push(`Level ${stats?.level ?? 1}`);
lines.push("");
lines.push("Stats");
lines.push(` HP: ${stats?.hp ?? 0}/${stats?.maxHp ?? 0}`);
lines.push(` EXP: ${stats?.exp ?? 0}/${stats?.expToNextLevel ?? 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}`);