Add character overlay, where skills and passives (changing this) can be set
This commit is contained in:
184
src/ui/GameUI.ts
184
src/ui/GameUI.ts
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user