refactor(ui): unify card rendering with CardObject and remove HealthBar dependency
- Completely rewrite `CardObject` to use a structured layout: top banner (ATK/ARM), 16:9 image area, content section (name/skills), and bottom banner (HP/DLY). - Remove the `HealthBar` class dependency; HP is now displayed as text within the card's bottom banner. - Update `BattleScene` to use `CardObject` for Commander displays, ensuring visual consistency with lane cards and simplifying state updates via `.refresh()`. - Refactor the card picker UI to instantiate actual `CardObjects` instead of manual rectangles/text, adding hover scale effects for better interactivity.
This commit is contained in:
parent
e1656838fc
commit
fc20545364
|
|
@ -1,5 +1,3 @@
|
||||||
import { HealthBar } from './HealthBar.js';
|
|
||||||
|
|
||||||
const RARITY_COLORS = {
|
const RARITY_COLORS = {
|
||||||
common: 0x888888,
|
common: 0x888888,
|
||||||
rare: 0x4488ff,
|
rare: 0x4488ff,
|
||||||
|
|
@ -15,6 +13,8 @@ const FACTION_COLORS = {
|
||||||
righteous: 0xaaaa22
|
righteous: 0xaaaa22
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const BANNER_COLOR = 0x0c0c1e;
|
||||||
|
|
||||||
export class CardObject extends Phaser.GameObjects.Container {
|
export class CardObject extends Phaser.GameObjects.Container {
|
||||||
constructor(scene, x, y, cardData, options = {}) {
|
constructor(scene, x, y, cardData, options = {}) {
|
||||||
super(scene, x, y);
|
super(scene, x, y);
|
||||||
|
|
@ -27,90 +27,167 @@ export class CardObject extends Phaser.GameObjects.Container {
|
||||||
_build() {
|
_build() {
|
||||||
const w = this.options.width || 80;
|
const w = this.options.width || 80;
|
||||||
const h = this.options.height || 110;
|
const h = this.options.height || 110;
|
||||||
// Scale font sizes with card width (base size designed for w=80)
|
|
||||||
const scale = w / 80;
|
const scale = w / 80;
|
||||||
const fs = n => `${Math.round(n * scale)}px`;
|
const fs = n => `${Math.round(n * scale)}px`;
|
||||||
|
|
||||||
const rarityColor = RARITY_COLORS[this.cardData.rarity] || 0x888888;
|
const rarityColor = RARITY_COLORS[this.cardData.rarity] || 0x888888;
|
||||||
const factionColor = FACTION_COLORS[this.cardData.faction] || 0x444444;
|
const factionColor = FACTION_COLORS[this.cardData.faction] || 0x444444;
|
||||||
|
|
||||||
// Card background
|
// Section heights
|
||||||
this.bg = this.scene.add.rectangle(0, 0, w, h, factionColor, 1)
|
const bannerH = Math.round(h * 0.12); // ~22px for h=190
|
||||||
.setStrokeStyle(2, rarityColor);
|
const imageH = Math.round(w * 9 / 16); // 16:9 placeholder
|
||||||
|
|
||||||
|
// Vertical positions (relative to container centre = 0)
|
||||||
|
const topBannerCY = -h / 2 + bannerH / 2;
|
||||||
|
const imageCY = -h / 2 + bannerH + imageH / 2;
|
||||||
|
const contentTop = -h / 2 + bannerH + imageH;
|
||||||
|
const bottomBannerCY = h / 2 - bannerH / 2;
|
||||||
|
const contentH = (bottomBannerCY - bannerH / 2) - contentTop;
|
||||||
|
|
||||||
|
// ── Card outline ──────────────────────────────────────────────────────────
|
||||||
|
const outline = this.scene.add.rectangle(0, 0, w, h, 0x0a0a1a)
|
||||||
|
.setStrokeStyle(Math.max(1, Math.round(2 * scale)), rarityColor);
|
||||||
|
this.add(outline);
|
||||||
|
|
||||||
|
// ── Top banner ────────────────────────────────────────────────────────────
|
||||||
|
const topBanner = this.scene.add.rectangle(0, topBannerCY, w, bannerH, BANNER_COLOR);
|
||||||
|
this.add(topBanner);
|
||||||
|
|
||||||
|
// ATK value — left side of top banner
|
||||||
|
this.atkText = this.scene.add.text(
|
||||||
|
-w / 2 + Math.round(5 * scale), topBannerCY,
|
||||||
|
`${this.cardData.currentAttack}`,
|
||||||
|
{ fontSize: fs(9), color: '#ff8877', fontStyle: 'bold' }
|
||||||
|
).setOrigin(0, 0.5);
|
||||||
|
this.add(this.atkText);
|
||||||
|
|
||||||
|
// Small "ATK" label under the number (tiny, just for clarity)
|
||||||
|
const atkLabel = this.scene.add.text(
|
||||||
|
-w / 2 + Math.round(5 * scale), topBannerCY + Math.round(bannerH * 0.30),
|
||||||
|
'ATK',
|
||||||
|
{ fontSize: fs(5.5), color: '#886666' }
|
||||||
|
).setOrigin(0, 0.5);
|
||||||
|
this.add(atkLabel);
|
||||||
|
|
||||||
|
// ARM value — right side of top banner
|
||||||
|
this.armText = this.scene.add.text(
|
||||||
|
w / 2 - Math.round(5 * scale), topBannerCY,
|
||||||
|
`${this.cardData.currentArmor}`,
|
||||||
|
{ fontSize: fs(9), color: '#88aaff', fontStyle: 'bold' }
|
||||||
|
).setOrigin(1, 0.5);
|
||||||
|
this.add(this.armText);
|
||||||
|
|
||||||
|
// Small "ARM" label
|
||||||
|
const armLabel = this.scene.add.text(
|
||||||
|
w / 2 - Math.round(5 * scale), topBannerCY + Math.round(bannerH * 0.30),
|
||||||
|
'ARM',
|
||||||
|
{ fontSize: fs(5.5), color: '#667799' }
|
||||||
|
).setOrigin(1, 0.5);
|
||||||
|
this.add(armLabel);
|
||||||
|
|
||||||
|
// ── 16:9 image placeholder (faction colour) ───────────────────────────────
|
||||||
|
// .bg is stored so _animateAttack can tween its fillColor for the red flash
|
||||||
|
this.bg = this.scene.add.rectangle(0, imageCY, w, imageH, factionColor);
|
||||||
this.add(this.bg);
|
this.add(this.bg);
|
||||||
|
|
||||||
// Delay overlay
|
// Delay dimmer overlay (covers image when card has delay remaining)
|
||||||
if (this.cardData.currentDelay > 0) {
|
if (this.cardData.currentDelay > 0) {
|
||||||
this.delayOverlay = this.scene.add.rectangle(0, 0, w, h, 0x000000, 0.5);
|
this.delayOverlay = this.scene.add.rectangle(0, imageCY, w, imageH, 0x000000, 0.60);
|
||||||
this.add(this.delayOverlay);
|
this.add(this.delayOverlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Card name
|
// ── Content area: name + skills ───────────────────────────────────────────
|
||||||
this.nameText = this.scene.add.text(0, -h / 2 + 8 * scale, this.cardData.name, {
|
const hasSkills = this.cardData.skills && this.cardData.skills.length > 0;
|
||||||
fontSize: fs(9), color: '#ffffff', wordWrap: { width: w - 6 }, align: 'center'
|
|
||||||
}).setOrigin(0.5, 0);
|
const nameY = hasSkills
|
||||||
|
? contentTop + contentH * 0.32
|
||||||
|
: contentTop + contentH * 0.50;
|
||||||
|
|
||||||
|
this.nameText = this.scene.add.text(0, nameY, this.cardData.name, {
|
||||||
|
fontSize: fs(8), color: '#ffffff',
|
||||||
|
wordWrap: { width: w - Math.round(8 * scale) }, align: 'center'
|
||||||
|
}).setOrigin(0.5, 0.5);
|
||||||
this.add(this.nameText);
|
this.add(this.nameText);
|
||||||
|
|
||||||
// Stats block (ATK / HP / ARM / delay)
|
if (hasSkills) {
|
||||||
const statsLines = [
|
const skillStr = this.cardData.skills
|
||||||
`ATK ${this.cardData.currentAttack} HP ${this.cardData.currentHP}`,
|
.map(s => s.value != null ? `${s.name} ${s.value}` : s.name)
|
||||||
`ARM ${this.cardData.currentArmor} DLY ${this.cardData.currentDelay}`
|
.join(' · ');
|
||||||
];
|
const skillY = contentTop + contentH * 0.72;
|
||||||
this.statsText = this.scene.add.text(0, h / 2 - 32 * scale, statsLines.join('\n'), {
|
this.skillText = this.scene.add.text(0, skillY, skillStr, {
|
||||||
fontSize: fs(9), color: '#aaddff', align: 'center', lineSpacing: 2
|
fontSize: fs(7), color: '#ffcc44',
|
||||||
}).setOrigin(0.5, 1);
|
wordWrap: { width: w - Math.round(8 * scale) }, align: 'center'
|
||||||
this.add(this.statsText);
|
}).setOrigin(0.5, 0.5);
|
||||||
|
|
||||||
// Skills text
|
|
||||||
if (this.cardData.skills && this.cardData.skills.length > 0) {
|
|
||||||
const skillStr = this.cardData.skills.map(s => s.name).join(' · ');
|
|
||||||
this.skillText = this.scene.add.text(0, h / 2 - 14 * scale, skillStr, {
|
|
||||||
fontSize: fs(8), color: '#ffcc44', wordWrap: { width: w - 6 }, align: 'center'
|
|
||||||
}).setOrigin(0.5, 1);
|
|
||||||
this.add(this.skillText);
|
this.add(this.skillText);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Health bar
|
// ── Bottom banner ─────────────────────────────────────────────────────────
|
||||||
const barH = Math.max(6, Math.round(7 * scale));
|
const bottomBanner = this.scene.add.rectangle(0, bottomBannerCY, w, bannerH, BANNER_COLOR);
|
||||||
this.healthBar = new HealthBar(this.scene, -w / 2, h / 2 - barH / 2 - 1, w, barH, this.cardData.health);
|
this.add(bottomBanner);
|
||||||
this.healthBar.update(this.cardData.currentHP, this.cardData.health);
|
|
||||||
|
|
||||||
// Rarity gem
|
// HP value — left side of bottom banner
|
||||||
const gemR = Math.max(4, Math.round(4 * scale));
|
this.hpText = this.scene.add.text(
|
||||||
const gem = this.scene.add.circle(w / 2 - gemR - 2, -h / 2 + gemR + 2, gemR, rarityColor);
|
-w / 2 + Math.round(5 * scale), bottomBannerCY,
|
||||||
|
`${Math.max(0, this.cardData.currentHP)}`,
|
||||||
|
{ fontSize: fs(9), color: '#44ee88', fontStyle: 'bold' }
|
||||||
|
).setOrigin(0, 0.5);
|
||||||
|
this.add(this.hpText);
|
||||||
|
|
||||||
|
// Small "HP" label
|
||||||
|
const hpLabel = this.scene.add.text(
|
||||||
|
-w / 2 + Math.round(5 * scale), bottomBannerCY + Math.round(bannerH * 0.30),
|
||||||
|
'HP',
|
||||||
|
{ fontSize: fs(5.5), color: '#337755' }
|
||||||
|
).setOrigin(0, 0.5);
|
||||||
|
this.add(hpLabel);
|
||||||
|
|
||||||
|
// DLY value — right side of bottom banner
|
||||||
|
this.dlyText = this.scene.add.text(
|
||||||
|
w / 2 - Math.round(5 * scale), bottomBannerCY,
|
||||||
|
`${this.cardData.currentDelay}`,
|
||||||
|
{ fontSize: fs(9), color: '#aaaaaa', fontStyle: 'bold' }
|
||||||
|
).setOrigin(1, 0.5);
|
||||||
|
this.add(this.dlyText);
|
||||||
|
|
||||||
|
// Small "DLY" label
|
||||||
|
const dlyLabel = this.scene.add.text(
|
||||||
|
w / 2 - Math.round(5 * scale), bottomBannerCY + Math.round(bannerH * 0.30),
|
||||||
|
'DLY',
|
||||||
|
{ fontSize: fs(5.5), color: '#777777' }
|
||||||
|
).setOrigin(1, 0.5);
|
||||||
|
this.add(dlyLabel);
|
||||||
|
|
||||||
|
// ── Rarity gem (top-right of image area) ──────────────────────────────────
|
||||||
|
const gemR = Math.max(3, Math.round(3.5 * scale));
|
||||||
|
const gem = this.scene.add.circle(
|
||||||
|
w / 2 - gemR - Math.round(3 * scale),
|
||||||
|
-h / 2 + bannerH + gemR + Math.round(3 * scale),
|
||||||
|
gemR, rarityColor
|
||||||
|
);
|
||||||
this.add(gem);
|
this.add(gem);
|
||||||
}
|
}
|
||||||
|
|
||||||
refresh() {
|
refresh() {
|
||||||
if (this.healthBar) this.healthBar.update(this.cardData.currentHP, this.cardData.health);
|
if (this.atkText) this.atkText.setText(`${this.cardData.currentAttack}`);
|
||||||
if (this.statsText) {
|
if (this.armText) this.armText.setText(`${this.cardData.currentArmor}`);
|
||||||
const statsLines = [
|
if (this.hpText) this.hpText.setText(`${Math.max(0, this.cardData.currentHP)}`);
|
||||||
`ATK ${this.cardData.currentAttack} HP ${Math.max(0, this.cardData.currentHP)}`,
|
if (this.dlyText) this.dlyText.setText(`${this.cardData.currentDelay}`);
|
||||||
`ARM ${this.cardData.currentArmor} DLY ${this.cardData.currentDelay}`
|
|
||||||
];
|
|
||||||
this.statsText.setText(statsLines.join('\n'));
|
|
||||||
}
|
|
||||||
if (this.delayOverlay) {
|
if (this.delayOverlay) {
|
||||||
this.delayOverlay.setVisible(this.cardData.currentDelay > 0);
|
this.delayOverlay.setVisible(this.cardData.currentDelay > 0);
|
||||||
} else if (this.cardData.currentDelay > 0) {
|
|
||||||
const w = this.options.width || 80;
|
|
||||||
const h = this.options.height || 110;
|
|
||||||
this.delayOverlay = this.scene.add.rectangle(0, 0, w, h, 0x000000, 0.5);
|
|
||||||
this.add(this.delayOverlay);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
flash(color = 0xff4444) {
|
flash(color = 0xff4444) {
|
||||||
|
const factionColor = FACTION_COLORS[this.cardData.faction] || 0x444444;
|
||||||
this.scene.tweens.add({
|
this.scene.tweens.add({
|
||||||
targets: this.bg,
|
targets: this.bg,
|
||||||
fillColor: { from: color, to: FACTION_COLORS[this.cardData.faction] || 0x444444 },
|
fillColor: { from: color, to: factionColor },
|
||||||
duration: 300,
|
duration: 300,
|
||||||
ease: 'Linear'
|
ease: 'Linear'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
destroy() {
|
destroy() {
|
||||||
if (this.healthBar) this.healthBar.destroy();
|
|
||||||
super.destroy();
|
super.destroy();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import { CombatEngine } from '../combat/CombatEngine.js';
|
import { CombatEngine } from '../combat/CombatEngine.js';
|
||||||
import { CardObject } from '../objects/CardObject.js';
|
import { CardObject } from '../objects/CardObject.js';
|
||||||
import { HealthBar } from '../objects/HealthBar.js';
|
|
||||||
import { BattleField } from '../objects/BattleField.js';
|
import { BattleField } from '../objects/BattleField.js';
|
||||||
import { SaveManager } from '../managers/SaveManager.js';
|
import { SaveManager } from '../managers/SaveManager.js';
|
||||||
|
|
||||||
|
|
@ -146,55 +145,25 @@ export class BattleScene extends Phaser.Scene {
|
||||||
|
|
||||||
_buildCommanderDisplay() {
|
_buildCommanderDisplay() {
|
||||||
const state = this.engine.getState();
|
const state = this.engine.getState();
|
||||||
this.commanderObjects = new Map(); // instanceId → Container (persists across _renderState)
|
this.commanderObjects = new Map(); // instanceId → CardObject (persists across _renderState)
|
||||||
|
|
||||||
const specs = [
|
const specs = [
|
||||||
{ data: state.opponent.commander, cx: 110, cy: 240, label: 'ENEMY CMD',
|
{ data: state.opponent.commander, cx: 110, cy: 240, label: 'ENEMY CMD', labelColor: '#ff8888' },
|
||||||
bgColor: 0xaa2222, borderColor: 0xff4444, nameColor: '#ff8888', statsColor: '#ffaaaa' },
|
{ data: state.player.commander, cx: 110, cy: 490, label: 'COMMANDER', labelColor: '#ffd700' }
|
||||||
{ data: state.player.commander, cx: 110, cy: 490, label: 'COMMANDER',
|
|
||||||
bgColor: 0x2244aa, borderColor: 0xffd700, nameColor: '#ffd700', statsColor: '#aaaaff' }
|
|
||||||
];
|
];
|
||||||
|
|
||||||
for (const s of specs) {
|
for (const s of specs) {
|
||||||
const w = 160, h = 180;
|
const w = 160, h = 180;
|
||||||
const container = this.add.container(s.cx, s.cy);
|
|
||||||
|
|
||||||
// Background rectangle — stored as .bg so _animateAttack can tween fillColor
|
// Label above the card (separate text so it never scales/shakes with the card)
|
||||||
const bg = this.add.rectangle(0, 0, w, h, s.bgColor).setStrokeStyle(3, s.borderColor);
|
|
||||||
container.add(bg);
|
|
||||||
container.bg = bg;
|
|
||||||
|
|
||||||
// Label above the card (not inside container so it doesn't scale/shake with it)
|
|
||||||
this.add.text(s.cx, s.cy - h / 2 - 14, s.label,
|
this.add.text(s.cx, s.cy - h / 2 - 14, s.label,
|
||||||
{ fontSize: '11px', color: '#888888' }).setOrigin(0.5);
|
{ fontSize: '11px', color: s.labelColor }).setOrigin(0.5);
|
||||||
|
|
||||||
// Name
|
// Use CardObject for visual consistency with lane cards
|
||||||
const nameText = this.add.text(0, -h / 2 + 18, s.data.name, {
|
const cardObj = new CardObject(this, s.cx, s.cy, s.data, { width: w, height: h });
|
||||||
fontSize: '13px', color: s.nameColor, wordWrap: { width: w - 10 }, align: 'center'
|
cardObj.isCommander = true;
|
||||||
}).setOrigin(0.5);
|
|
||||||
container.add(nameText);
|
|
||||||
|
|
||||||
// Stats
|
this.commanderObjects.set(s.data.instanceId, cardObj);
|
||||||
const statsText = this.add.text(0, h / 2 - 32, `ATK:${s.data.currentAttack} ARM:${s.data.currentArmor}`, {
|
|
||||||
fontSize: '12px', color: s.statsColor
|
|
||||||
}).setOrigin(0.5);
|
|
||||||
container.add(statsText);
|
|
||||||
|
|
||||||
// HP bar (positioned relative to the scene, not the container, so we can update it easily)
|
|
||||||
const hpBar = new HealthBar(this, s.cx - w / 2, s.cy + h / 2 + 2, w, 10, s.data.health);
|
|
||||||
hpBar.update(s.data.currentHP, s.data.health);
|
|
||||||
|
|
||||||
// Attach helpers
|
|
||||||
container._hpBar = hpBar;
|
|
||||||
container._stats = statsText;
|
|
||||||
container._data = s.data;
|
|
||||||
container.isCommander = true;
|
|
||||||
container.refresh = function () {
|
|
||||||
this._hpBar.update(this._data.currentHP, this._data.health);
|
|
||||||
this._stats.setText(`ATK:${this._data.currentAttack} ARM:${this._data.currentArmor}`);
|
|
||||||
};
|
|
||||||
|
|
||||||
this.commanderObjects.set(s.data.instanceId, container);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.oDeckText = this.add.text(14, 92, '', { fontSize: '12px', color: '#aaaaaa' });
|
this.oDeckText = this.add.text(14, 92, '', { fontSize: '12px', color: '#aaaaaa' });
|
||||||
|
|
@ -490,100 +459,71 @@ export class BattleScene extends Phaser.Scene {
|
||||||
_showCardPicker(hand) {
|
_showCardPicker(hand) {
|
||||||
const { width, height } = this.scale;
|
const { width, height } = this.scale;
|
||||||
|
|
||||||
// Destroy any existing picker
|
|
||||||
this._destroyCardPicker();
|
this._destroyCardPicker();
|
||||||
|
|
||||||
this.pickerObjects = [];
|
this.pickerObjects = [];
|
||||||
|
|
||||||
// Dim overlay
|
// Dim overlay
|
||||||
const overlay = this.add.rectangle(width / 2, height / 2, width, height, 0x000000, 0.55)
|
const overlay = this.add.rectangle(width / 2, height / 2, width, height, 0x000000, 0.60)
|
||||||
.setDepth(10);
|
.setDepth(10);
|
||||||
this.pickerObjects.push(overlay);
|
this.pickerObjects.push(overlay);
|
||||||
|
|
||||||
// Panel background
|
// Panel background
|
||||||
const panelH = 280;
|
const cardW = 170, cardH = 220;
|
||||||
const panel = this.add.rectangle(width / 2, height / 2, width - 60, panelH, 0x0d1b2a, 0.97)
|
const gap = 30;
|
||||||
|
const panelPadX = 50, panelPadY = 60;
|
||||||
|
const totalW = hand.length * cardW + (hand.length - 1) * gap;
|
||||||
|
const panelW = Math.max(totalW + panelPadX * 2, 400);
|
||||||
|
const panelH = cardH + panelPadY * 2;
|
||||||
|
const panelY = height / 2;
|
||||||
|
|
||||||
|
const panel = this.add.rectangle(width / 2, panelY, panelW, panelH, 0x0d1b2a, 0.97)
|
||||||
.setStrokeStyle(2, 0x4488ff)
|
.setStrokeStyle(2, 0x4488ff)
|
||||||
.setDepth(10);
|
.setDepth(10);
|
||||||
this.pickerObjects.push(panel);
|
this.pickerObjects.push(panel);
|
||||||
|
|
||||||
// Title
|
const title = this.add.text(width / 2, panelY - panelH / 2 + 22, 'Choose a card to deploy', {
|
||||||
const title = this.add.text(width / 2, height / 2 - panelH / 2 + 22, 'Choose a card to deploy', {
|
|
||||||
fontSize: '20px', color: '#d4af37'
|
fontSize: '20px', color: '#d4af37'
|
||||||
}).setOrigin(0.5).setDepth(11);
|
}).setOrigin(0.5).setDepth(11);
|
||||||
this.pickerObjects.push(title);
|
this.pickerObjects.push(title);
|
||||||
|
|
||||||
// Card layout — up to 3 cards centred
|
|
||||||
const cardW = 170, cardH = 210;
|
|
||||||
const gap = 30;
|
|
||||||
const totalW = hand.length * cardW + (hand.length - 1) * gap;
|
|
||||||
const startX = width / 2 - totalW / 2 + cardW / 2;
|
const startX = width / 2 - totalW / 2 + cardW / 2;
|
||||||
const cardY = height / 2 + 20;
|
const cardY = panelY + 8;
|
||||||
|
|
||||||
const RARITY_COLORS = { common: 0x888888, rare: 0x4488ff, epic: 0xaa44ff, legendary: 0xffaa00 };
|
|
||||||
const FACTION_COLORS = { imperial: 0x2244aa, raider: 0xaa2222 };
|
|
||||||
|
|
||||||
hand.forEach((card, i) => {
|
hand.forEach((card, i) => {
|
||||||
const x = startX + i * (cardW + gap);
|
const x = startX + i * (cardW + gap);
|
||||||
const rarityColor = RARITY_COLORS[card.rarity] || 0x888888;
|
|
||||||
const factionColor = FACTION_COLORS[card.faction] || 0x1a3a5c;
|
|
||||||
|
|
||||||
// Card bg — interactive
|
// CardObject at depth 11
|
||||||
const cardBg = this.add.rectangle(x, cardY, cardW, cardH, factionColor)
|
const cardObj = new CardObject(this, x, cardY, card, { width: cardW, height: cardH });
|
||||||
.setStrokeStyle(3, rarityColor)
|
cardObj.setDepth(11);
|
||||||
|
this.pickerObjects.push(cardObj);
|
||||||
|
|
||||||
|
// Invisible interactive hit rect on top
|
||||||
|
const hitRect = this.add.rectangle(x, cardY, cardW, cardH, 0xffffff, 0)
|
||||||
.setInteractive({ useHandCursor: true })
|
.setInteractive({ useHandCursor: true })
|
||||||
.setDepth(11);
|
.setDepth(12);
|
||||||
|
|
||||||
cardBg.on('pointerover', () => {
|
hitRect.on('pointerover', () => {
|
||||||
cardBg.setFillStyle(Phaser.Display.Color.ValueToColor(factionColor).brighten(30).color);
|
cardObj.setScale(1.05);
|
||||||
cardBg.setStrokeStyle(4, 0xffffff);
|
cardObj.setDepth(13);
|
||||||
});
|
});
|
||||||
cardBg.on('pointerout', () => {
|
hitRect.on('pointerout', () => {
|
||||||
cardBg.setFillStyle(factionColor);
|
cardObj.setScale(1);
|
||||||
cardBg.setStrokeStyle(3, rarityColor);
|
cardObj.setDepth(11);
|
||||||
});
|
});
|
||||||
cardBg.on('pointerdown', () => {
|
hitRect.on('pointerdown', () => {
|
||||||
this._destroyCardPicker();
|
this._destroyCardPicker();
|
||||||
this._finishTurn(card);
|
this._finishTurn(card);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Card name
|
this.pickerObjects.push(hitRect);
|
||||||
const nameT = this.add.text(x, cardY - cardH / 2 + 16, card.name, {
|
|
||||||
fontSize: '13px', color: '#ffffff', wordWrap: { width: cardW - 12 }, align: 'center'
|
|
||||||
}).setOrigin(0.5, 0).setDepth(12);
|
|
||||||
|
|
||||||
// Rarity + faction
|
|
||||||
const subT = this.add.text(x, cardY - cardH / 2 + 40, `${card.rarity} · ${card.faction}`, {
|
|
||||||
fontSize: '10px', color: '#aaaaaa'
|
|
||||||
}).setOrigin(0.5).setDepth(12);
|
|
||||||
|
|
||||||
// Stats
|
|
||||||
const statsT = this.add.text(x, cardY - 20, [
|
|
||||||
`ATK ${card.currentAttack}`,
|
|
||||||
`HP ${card.currentHP}`,
|
|
||||||
`ARM ${card.currentArmor}`,
|
|
||||||
`DLY ${card.currentDelay}`
|
|
||||||
].join('\n'), {
|
|
||||||
fontSize: '13px', color: '#aaddff', lineSpacing: 4
|
|
||||||
}).setOrigin(0.5).setDepth(12);
|
|
||||||
|
|
||||||
// Skills
|
|
||||||
const skillStr = card.skills.length
|
|
||||||
? card.skills.map(s => `${s.name} ${s.value ?? ''}`).join(' | ')
|
|
||||||
: 'No skills';
|
|
||||||
const skillT = this.add.text(x, cardY + cardH / 2 - 18, skillStr, {
|
|
||||||
fontSize: '10px', color: '#ffcc44', wordWrap: { width: cardW - 10 }, align: 'center'
|
|
||||||
}).setOrigin(0.5, 1).setDepth(12);
|
|
||||||
|
|
||||||
this.pickerObjects.push(cardBg, nameT, subT, statsT, skillT);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Pass button
|
// Pass button
|
||||||
const passBtn = this.add.rectangle(width / 2, height / 2 + panelH / 2 - 22, 160, 34, 0x333333)
|
const passBtn = this.add.rectangle(width / 2, panelY + panelH / 2 - 24, 180, 34, 0x333333)
|
||||||
.setStrokeStyle(1, 0x888888)
|
.setStrokeStyle(1, 0x888888)
|
||||||
.setInteractive({ useHandCursor: true })
|
.setInteractive({ useHandCursor: true })
|
||||||
.setDepth(11);
|
.setDepth(11);
|
||||||
const passTxt = this.add.text(width / 2, height / 2 + panelH / 2 - 22, 'Pass (deploy nothing)', {
|
const passTxt = this.add.text(width / 2, panelY + panelH / 2 - 24, 'Pass (deploy nothing)', {
|
||||||
fontSize: '13px', color: '#aaaaaa'
|
fontSize: '13px', color: '#aaaaaa'
|
||||||
}).setOrigin(0.5).setDepth(12);
|
}).setOrigin(0.5).setDepth(12);
|
||||||
passBtn.on('pointerdown', () => {
|
passBtn.on('pointerdown', () => {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue