diff --git a/public/assets/ui/icons/icon_backpack.png b/public/assets/ui/icons/icon_backpack.png new file mode 100644 index 0000000..8776830 Binary files /dev/null and b/public/assets/ui/icons/icon_backpack.png differ diff --git a/public/assets/ui/icons/icon_map.png b/public/assets/ui/icons/icon_map.png new file mode 100644 index 0000000..98480ed Binary files /dev/null and b/public/assets/ui/icons/icon_map.png differ diff --git a/public/assets/ui/icons/icon_menu.png b/public/assets/ui/icons/icon_menu.png new file mode 100644 index 0000000..98480ed Binary files /dev/null and b/public/assets/ui/icons/icon_menu.png differ diff --git a/public/assets/ui/icons/icon_search.png b/public/assets/ui/icons/icon_search.png new file mode 100644 index 0000000..8b9590a Binary files /dev/null and b/public/assets/ui/icons/icon_search.png differ diff --git a/public/assets/ui/icons/icon_stats.png b/public/assets/ui/icons/icon_stats.png new file mode 100644 index 0000000..341004a Binary files /dev/null and b/public/assets/ui/icons/icon_stats.png differ diff --git a/public/assets/ui/icons/icon_wait.png b/public/assets/ui/icons/icon_wait.png new file mode 100644 index 0000000..b11f6b9 Binary files /dev/null and b/public/assets/ui/icons/icon_wait.png differ diff --git a/src/ui/GameUI.ts b/src/ui/GameUI.ts index c2a13be..13babc9 100644 --- a/src/ui/GameUI.ts +++ b/src/ui/GameUI.ts @@ -5,8 +5,8 @@ import { MenuComponent } from "./components/MenuComponent"; import { InventoryOverlay } from "./components/InventoryOverlay"; import { CharacterOverlay } from "./components/CharacterOverlay"; import { DeathOverlay } from "./components/DeathOverlay"; -import { PersistentButtonsComponent } from "./components/PersistentButtonsComponent"; import { QuickSlotComponent } from "./components/QuickSlotComponent"; +import { ActionButtonComponent } from "./components/ActionButtonComponent"; export default class GameUI extends Phaser.Scene { private hud: HudComponent; @@ -14,8 +14,8 @@ export default class GameUI extends Phaser.Scene { private inventory: InventoryOverlay; private character: CharacterOverlay; private death: DeathOverlay; - private persistentButtons: PersistentButtonsComponent; private quickSlots: QuickSlotComponent; + private actionButtons: ActionButtonComponent; constructor() { super({ key: "GameUI" }); @@ -24,8 +24,8 @@ export default class GameUI extends Phaser.Scene { this.inventory = new InventoryOverlay(this); this.character = new CharacterOverlay(this); this.death = new DeathOverlay(this); - this.persistentButtons = new PersistentButtonsComponent(this); this.quickSlots = new QuickSlotComponent(this); + this.actionButtons = new ActionButtonComponent(this); } @@ -35,8 +35,8 @@ export default class GameUI extends Phaser.Scene { this.inventory.create(); this.character.create(); this.death.create(); - this.persistentButtons.create(); this.quickSlots.create(); + this.actionButtons.create(); const gameScene = this.scene.get("GameScene"); diff --git a/src/ui/components/ActionButtonComponent.ts b/src/ui/components/ActionButtonComponent.ts new file mode 100644 index 0000000..ec77dd5 --- /dev/null +++ b/src/ui/components/ActionButtonComponent.ts @@ -0,0 +1,99 @@ +import Phaser from "phaser"; + +export class ActionButtonComponent { + private scene: Phaser.Scene; + private container!: Phaser.GameObjects.Container; + private iconsLoaded = false; + + private static readonly ACTIONS = [ + { icon: "icon_wait", label: "WAIT", event: "player-wait" }, + { icon: "icon_search", label: "SEARCH", event: "player-search" }, + { icon: "icon_map", label: "MAP", event: "toggle-minimap" }, + { icon: "icon_backpack", label: "BACKPACK", event: "toggle-inventory" }, + { icon: "icon_stats", label: "STATS", event: "toggle-character" }, + { icon: "icon_menu", label: "MENU", event: "toggle-menu" } + ]; + + private static readonly BUTTON_SIZE = 40; + private static readonly BUTTON_SPACING = 4; + private static readonly LABEL_HEIGHT = 15; + private static readonly ICON_SCALE_NORMAL = 1.5; + private static readonly ICON_SCALE_HOVER = 1.7; + private static readonly COLOR_BG = 0x2a1f3d; + private static readonly COLOR_BORDER_NORMAL = 0xD4AF37; + private static readonly COLOR_BORDER_HOVER = 0xFFD700; + + constructor(scene: Phaser.Scene) { + this.scene = scene; + } + + preload() { + const iconPath = "/assets/ui/icons/icon_"; + ["wait", "search", "menu", "backpack", "stats", "map"].forEach(name => { + this.scene.load.image(`icon_${name}`, `${iconPath}${name}.png`); + }); + this.scene.load.once("complete", () => { this.iconsLoaded = true; }); + this.scene.load.start(); + } + + create() { + if (!this.iconsLoaded) { + this.preload(); + this.scene.time.delayedCall(100, () => this.createButtons()); + } else { + this.createButtons(); + } + } + + private createButtons() { + const { width, height } = this.scene.scale; + const { ACTIONS, BUTTON_SIZE, BUTTON_SPACING, LABEL_HEIGHT } = ActionButtonComponent; + const totalWidth = (BUTTON_SIZE + BUTTON_SPACING) * ACTIONS.length - BUTTON_SPACING; + + this.container = this.scene.add.container( + width / 2 - totalWidth / 2, + height - BUTTON_SIZE - LABEL_HEIGHT - 10 + ); + this.container.setScrollFactor(0).setDepth(1500); + + ACTIONS.forEach((action, i) => { + const x = i * (BUTTON_SIZE + BUTTON_SPACING); + const graphics = this.scene.add.graphics(); + const icon = this.scene.add.sprite(BUTTON_SIZE / 2, BUTTON_SIZE / 2, action.icon).setScale(ActionButtonComponent.ICON_SCALE_NORMAL); + const label = this.scene.add.text(BUTTON_SIZE / 2, BUTTON_SIZE + 3, action.label, { + fontSize: "9px", + color: "#D4AF37", + fontStyle: "bold" + }).setOrigin(0.5, 0); + + const button = this.scene.add.container(x, 0, [graphics, icon, label]); + this.container.add(button); + + this.drawButton(graphics, false); + button.setInteractive(new Phaser.Geom.Rectangle(0, 0, BUTTON_SIZE, BUTTON_SIZE), Phaser.Geom.Rectangle.Contains); + + button.on("pointerover", () => { + this.drawButton(graphics, true); + icon.setScale(ActionButtonComponent.ICON_SCALE_HOVER); + }); + + button.on("pointerout", () => { + this.drawButton(graphics, false); + icon.setScale(ActionButtonComponent.ICON_SCALE_NORMAL); + }); + + button.on("pointerdown", () => { + this.scene.scene.get("GameScene").events.emit(action.event); + }); + }); + } + + private drawButton(graphics: Phaser.GameObjects.Graphics, hover: boolean) { + const { BUTTON_SIZE, COLOR_BG, COLOR_BORDER_NORMAL, COLOR_BORDER_HOVER } = ActionButtonComponent; + graphics.clear(); + graphics.fillStyle(COLOR_BG, 0.95); + graphics.fillRect(0, 0, BUTTON_SIZE, BUTTON_SIZE); + graphics.lineStyle(2, hover ? COLOR_BORDER_HOVER : COLOR_BORDER_NORMAL, 1); + graphics.strokeRect(0, 0, BUTTON_SIZE, BUTTON_SIZE); + } +} diff --git a/src/ui/components/PersistentButtonsComponent.ts b/src/ui/components/PersistentButtonsComponent.ts deleted file mode 100644 index 10df566..0000000 --- a/src/ui/components/PersistentButtonsComponent.ts +++ /dev/null @@ -1,95 +0,0 @@ -import Phaser from "phaser"; - -export class PersistentButtonsComponent { - private scene: Phaser.Scene; - private container!: Phaser.GameObjects.Container; - - constructor(scene: Phaser.Scene) { - this.scene = scene; - } - - create() { - const { height } = this.scene.scale; - this.container = this.scene.add.container(20, height - 20); - - this.container.setScrollFactor(0).setDepth(1500); - - const btnStyle = { - fontSize: "14px", - color: "#ffffff", - backgroundColor: "#1a1a1a", - padding: { x: 10, y: 6 }, - fontStyle: "bold" - }; - - const createBtn = (x: number, text: string, event: string) => { - const btn = this.scene.add.text(x, 0, text, btnStyle) - .setOrigin(0, 1) - .setInteractive({ useHandCursor: true }); - - btn.on("pointerover", () => btn.setBackgroundColor("#333333")); - btn.on("pointerout", () => btn.setBackgroundColor("#1a1a1a")); - btn.on("pointerdown", () => { - btn.setBackgroundColor("#444444"); - const gameScene = this.scene.scene.get("GameScene"); - gameScene.events.emit(event); - }); - btn.on("pointerup", () => btn.setBackgroundColor("#333333")); - - this.container.add(btn); - return btn; - }; - - createBtn(0, "MENU (ESC)", "toggle-menu"); - createBtn(105, "STATS (C)", "toggle-character"); - createBtn(200, "BACKPACK (I)", "toggle-inventory"); - createBtn(320, "MAP (M)", "toggle-minimap"); - - // Right-aligned buttons - const rightContainer = this.scene.add.container(this.scene.scale.width - 20, height - 20); - rightContainer.setScrollFactor(0).setDepth(1500); - - const waitBtn = this.scene.add.text(0, 0, "🕒", { - fontSize: "24px", - color: "#ffffff", - backgroundColor: "#1a1a1a", - padding: { x: 10, y: 6 }, - fontStyle: "bold" - }) - .setOrigin(1, 1) - .setInteractive({ useHandCursor: true }); - - const searchBtn = this.scene.add.text(-40, 0, "🔍", { // Offset to the left of wait button - fontSize: "24px", - color: "#ffffff", - backgroundColor: "#1a1a1a", - padding: { x: 10, y: 6 }, - fontStyle: "bold" - }) - .setOrigin(1, 1) - .setInteractive({ useHandCursor: true }); - - waitBtn.on("pointerover", () => waitBtn.setBackgroundColor("#333333")); - waitBtn.on("pointerout", () => waitBtn.setBackgroundColor("#1a1a1a")); - waitBtn.on("pointerdown", () => { - waitBtn.setBackgroundColor("#444444"); - const gameScene = this.scene.scene.get("GameScene"); - gameScene.events.emit("player-wait"); - }); - waitBtn.on("pointerup", () => waitBtn.setBackgroundColor("#333333")); - - searchBtn.on("pointerover", () => searchBtn.setBackgroundColor("#333333")); - searchBtn.on("pointerout", () => searchBtn.setBackgroundColor("#1a1a1a")); - searchBtn.on("pointerdown", () => { - searchBtn.setBackgroundColor("#444444"); - // Implementing search visual logic later, for now just log - console.log("Searching..."); - const gameScene = this.scene.scene.get("GameScene"); - gameScene.events.emit("player-search"); - }); - searchBtn.on("pointerup", () => searchBtn.setBackgroundColor("#333333")); - - rightContainer.add(waitBtn); - rightContainer.add(searchBtn); - } -} diff --git a/src/ui/components/QuickSlotComponent.ts b/src/ui/components/QuickSlotComponent.ts index b3285b7..fe8fd0e 100644 --- a/src/ui/components/QuickSlotComponent.ts +++ b/src/ui/components/QuickSlotComponent.ts @@ -17,9 +17,13 @@ export class QuickSlotComponent { const slotSize = 48; const slotSpacing = 4; const totalWidth = (slotSize + slotSpacing) * 4 - slotSpacing; + const actionButtonHeight = 40 + 10; // Button height + spacing - // Position bottom center - this.container = this.scene.add.container(width / 2 - totalWidth / 2, height - slotSize - 20); + // Position above action buttons + this.container = this.scene.add.container( + width / 2 - totalWidth / 2, + height - slotSize - actionButtonHeight - 20 + ); this.container.setScrollFactor(0).setDepth(1500); for (let i = 0; i < 4; i++) {