Add character overlay, where skills and passives (changing this) can be set

This commit is contained in:
Peter Stockings
2026-01-04 21:12:07 +11:00
parent f67f488764
commit 171abb681a
9 changed files with 321 additions and 20 deletions

View File

@@ -17,12 +17,21 @@ export default class GameUI extends Phaser.Scene {
private menuButton!: Phaser.GameObjects.Container;
private mapButton!: Phaser.GameObjects.Container;
private backpackButton!: Phaser.GameObjects.Container;
private characterButton!: Phaser.GameObjects.Container;
// Inventory/Equipment Overlay
private inventoryOpen = false;
private invContainer!: Phaser.GameObjects.Container;
private equipmentSlots: Map<string, Phaser.GameObjects.Container> = new Map();
private backpackSlots: Phaser.GameObjects.Container[] = [];
// Character Overlay
private characterOpen = false;
private charContainer!: Phaser.GameObjects.Container;
private attrText!: Phaser.GameObjects.Text;
private skillPointsText!: Phaser.GameObjects.Text;
private statPointsText!: Phaser.GameObjects.Text;
private charStatsText!: Phaser.GameObjects.Text;
// Death Screen
private deathContainer!: Phaser.GameObjects.Container;
@@ -37,6 +46,7 @@ export default class GameUI extends Phaser.Scene {
this.createHud();
this.createMenu();
this.createInventoryOverlay();
this.createCharacterOverlay();
this.createDeathScreen();
// Listen for updates from GameScene
@@ -47,9 +57,11 @@ export default class GameUI extends Phaser.Scene {
gameScene.events.on("toggle-menu", () => this.toggleMenu());
gameScene.events.on("toggle-inventory", () => this.toggleInventory());
gameScene.events.on("toggle-character", () => this.toggleCharacter());
gameScene.events.on("close-menu", () => {
this.setMenuOpen(false);
this.setInventoryOpen(false);
this.setCharacterOpen(false);
});
}
@@ -140,6 +152,20 @@ export default class GameUI extends Phaser.Scene {
bpBtnBg.setInteractive({ useHandCursor: true }).on("pointerdown", () => this.toggleInventory());
// Character Button (Right of Backpack)
const charBtnBg = this.add.rectangle(0, 0, btnW, btnH, 0x000000, 0.6).setStrokeStyle(1, 0xffffff, 0.8);
const charBtnLabel = this.add.text(0, 0, "Character", { fontSize: "14px", color: "#ffffff" }).setOrigin(0.5);
this.characterButton = this.add.container(0, 0, [charBtnBg, charBtnLabel]);
this.characterButton.setDepth(1000);
const placeCharButton = () => {
this.characterButton.setPosition(btnW / 2 + 10 + btnW + 5, cam.height - btnH / 2 - 10);
};
placeCharButton();
this.scale.on("resize", placeCharButton);
charBtnBg.setInteractive({ useHandCursor: true }).on("pointerdown", () => this.toggleCharacter());
this.setMenuOpen(false);
}
@@ -382,6 +408,139 @@ export default class GameUI extends Phaser.Scene {
gameScene.events.emit("inventory-toggled", open);
}
private toggleCharacter() {
this.setCharacterOpen(!this.characterOpen);
if (this.characterOpen) {
this.setMenuOpen(false);
this.setInventoryOpen(false);
const gameScene = this.scene.get("GameScene");
gameScene.events.emit("request-ui-update");
}
}
private setCharacterOpen(open: boolean) {
this.characterOpen = open;
this.charContainer.setVisible(open);
const gameScene = this.scene.get("GameScene");
gameScene.events.emit("character-toggled", open);
}
private createCharacterOverlay() {
const cam = this.cameras.main;
const panelW = 850;
const panelH = 550;
const bg = this.add.graphics();
bg.fillStyle(0x000000, 0.9);
bg.fillRect(-panelW / 2, -panelH / 2, panelW, panelH);
// Capture clicks
const hitArea = new Phaser.Geom.Rectangle(-panelW / 2, -panelH / 2, panelW, panelH);
this.add.zone(0, 0, panelW, panelH).setInteractive(hitArea, Phaser.Geom.Rectangle.Contains);
bg.lineStyle(3, 0x443322, 1);
bg.strokeRect(-panelW / 2, -panelH / 2, panelW, panelH);
bg.lineStyle(1, 0x887766, 0.3);
bg.strokeRect(-panelW / 2 + 5, -panelH / 2 + 5, panelW - 10, panelH - 10);
const title = this.add.text(0, -panelH / 2 + 25, "CHARACTER", {
fontSize: "28px",
color: "#d4af37",
fontStyle: "bold",
shadow: { blur: 2, color: "#000000", fill: true, offsetY: 2 }
}).setOrigin(0.5);
this.charContainer = this.add.container(0, 0, [bg, title]);
this.charContainer.setDepth(1001);
// --- Attributes Section ---
const attrX = -300;
const attrY = -145;
const treeX = 50;
const treeY = 0;
const attrTitle = this.add.text(attrX, attrY - 50, "ATTRIBUTES", { fontSize: "20px", color: "#d4af37", fontStyle: "bold" }).setOrigin(0.5);
this.charContainer.add(attrTitle);
this.attrText = this.add.text(attrX - 20, attrY + 30, "", { fontSize: "16px", color: "#ffffff", lineSpacing: 40 }).setOrigin(1, 0.5);
this.charContainer.add(this.attrText);
// Stat allocation buttons
const statsNames = ["strength", "dexterity", "intelligence"];
statsNames.forEach((name, i) => {
const btn = this.add.text(attrX + 50, attrY - 25 + i * 56, "[ + ]", { fontSize: "16px", color: "#00ff00" }).setOrigin(0, 0.5);
btn.setInteractive({ useHandCursor: true }).on("pointerdown", () => {
const gameScene = this.scene.get("GameScene");
gameScene.events.emit("allocate-stat", name);
});
this.charContainer.add(btn);
});
this.statPointsText = this.add.text(attrX, attrY + 150, "Stat Points: 0", { fontSize: "16px", color: "#d4af37" }).setOrigin(0.5);
this.charContainer.add(this.statPointsText);
this.skillPointsText = this.add.text(treeX, panelH / 2 - 40, "Skill Points: 0", { fontSize: "20px", color: "#d4af37", fontStyle: "bold" }).setOrigin(0.5);
this.charContainer.add(this.skillPointsText);
// Derived Stats
this.charStatsText = this.add.text(-attrX, 0, "", { fontSize: "14px", color: "#ffffff", lineSpacing: 10 }).setOrigin(0.5);
this.charContainer.add(this.charStatsText);
// --- Skill Tree Section ---
const treeTitle = this.add.text(treeX, -panelH / 2 + 80, "PASSIVE SKILL TREE", { fontSize: "20px", color: "#d4af37", fontStyle: "bold" }).setOrigin(0.5);
this.charContainer.add(treeTitle);
// Simple Grid for Tree
const nodes = [
{ id: "off_1", label: "Martial Arts", x: treeX - 100, y: treeY - 100, color: 0xff4444 },
{ id: "off_2", label: "Brutality", x: treeX - 100, y: treeY + 100, color: 0xcc0000 },
{ id: "def_1", label: "Thick Skin", x: treeX + 100, y: treeY - 100, color: 0x44ff44 },
{ id: "def_2", label: "Juggernaut", x: treeX + 100, y: treeY + 100, color: 0x00cc00 },
{ id: "util_1", label: "Fleetfoot", x: treeX, y: treeY - 150, color: 0x4444ff },
{ id: "util_2", label: "Cunning", x: treeX, y: treeY + 150, color: 0x0000cc },
];
// Connections
const connections = [
["off_1", "off_2"], ["def_1", "def_2"],
["util_1", "off_1"], ["util_1", "def_1"],
["util_2", "off_2"], ["util_2", "def_2"]
];
const treeLines = this.add.graphics();
treeLines.lineStyle(2, 0x333333, 1);
connections.forEach(conn => {
const n1 = nodes.find(n => n.id === conn[0])!;
const n2 = nodes.find(n => n.id === conn[1])!;
treeLines.lineBetween(n1.x, n1.y, n2.x, n2.y);
});
this.charContainer.add(treeLines);
treeLines.setDepth(-1); // Behind nodes
nodes.forEach(n => {
const circle = this.add.circle(n.x, n.y, 25, 0x1a1a1a).setStrokeStyle(2, n.color);
const label = this.add.text(n.x, n.y + 35, n.label, { fontSize: "12px", color: "#ffffff" }).setOrigin(0.5);
circle.setInteractive({ useHandCursor: true }).on("pointerdown", () => {
const gameScene = this.scene.get("GameScene");
gameScene.events.emit("allocate-passive", n.id);
});
this.charContainer.add([circle, label]);
});
const placeChar = () => {
this.charContainer.setPosition(cam.width / 2, cam.height / 2);
};
placeChar();
this.scale.on("resize", placeChar);
this.setCharacterOpen(false);
}
private updateUI(world: World, playerId: EntityId, floorIndex: number) {
this.updateHud(world, playerId, floorIndex);
if (this.menuOpen) {
@@ -390,6 +549,31 @@ export default class GameUI extends Phaser.Scene {
if (this.inventoryOpen) {
this.updateInventoryUI(world, playerId);
}
if (this.characterOpen) {
this.updateCharacterUI(world, playerId);
}
}
private updateCharacterUI(world: World, playerId: EntityId) {
const p = world.actors.get(playerId);
if (!p || !p.stats) return;
const s = p.stats;
this.attrText.setText(`STR: ${s.strength}\nDEX: ${s.dexterity}\nINT: ${s.intelligence}`);
this.statPointsText.setText(`Unspent Points: ${s.statPoints}`);
this.skillPointsText.setText(`PASSIVE SKILL POINTS: ${s.skillPoints}`);
const statsLines = [
"SECONDARY STATS",
"",
`Max HP: ${s.maxHp}`,
`Attack: ${s.attack}`,
`Defense: ${s.defense}`,
`Speed: ${p.speed}`,
"",
`Passive Nodes: ${s.passiveNodes.length > 0 ? s.passiveNodes.join(", ") : "(none)"}`
];
this.charStatsText.setText(statsLines.join("\n"));
}
private updateInventoryUI(world: World, playerId: EntityId) {