refactor(ui): upgrade to 1920x1080 resolution with responsive scaling
- Update `game.js` configuration to 1920x1080 with `Phaser.Scale.FIT` for responsive window resizing. - Scale all UI elements across scenes (MainMenu, Battle, DeckBuilder, Collection, Store, Fusion) to match the new resolution: - Increase font sizes by ~35-50% for better readability. - Enlarge buttons and interactive areas to accommodate the larger viewport. - Expand card display dimensions in battle and deck management screens. - Adjust layout coordinates in `BattleScene` to center the 4-lane arena properly on the wider screen. - Update `BootScene` loading text to center dynamically based on scale dimensions. - Add project documentation (`CLAUDE.md`) outlining architecture, scene flow, and data models for future contributors.
This commit is contained in:
parent
d865634a2f
commit
73302be3cc
|
|
@ -1,7 +1,8 @@
|
|||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(node:*)"
|
||||
"Bash(node:*)",
|
||||
"Bash(wc -l /home/brianfertig/git/tyrants-edge/src/**/*.js)"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,73 @@
|
|||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## Running the Game
|
||||
|
||||
```bash
|
||||
./start_web.sh # Starts Python HTTP server on port 8000
|
||||
```
|
||||
|
||||
Open `http://localhost:8000` in a browser. No build step — all source is raw ES modules. Changes take effect on page refresh.
|
||||
|
||||
There are no tests or linting configured.
|
||||
|
||||
## Architecture
|
||||
|
||||
**Tyrants Edge** is a browser-based deck-building card game built with Phaser 3 (v3.60.0, loaded via CDN). Vanilla JavaScript only — no bundler, no npm.
|
||||
|
||||
### Scene Flow
|
||||
|
||||
`BootScene` → `MainMenuScene` → any of:
|
||||
- `BattleScene` — 1v1 card battles vs AI
|
||||
- `DeckBuilderScene` — customize decks (1 commander + 3–10 assault cards)
|
||||
- `CampaignScene` → launches `BattleScene` with mission config
|
||||
- `CollectionScene`, `StoreScene`, `FusionScene`
|
||||
|
||||
`BootScene` loads all JSON data (`data/*.json`) and card art images, then instantiates global managers stored in Phaser's game registry.
|
||||
|
||||
### Global Managers (accessed via `this.registry.get(...)`)
|
||||
|
||||
| Key | Class | Purpose |
|
||||
|-----|-------|---------|
|
||||
| `cardManager` | `CardManager` | Card definitions and instance creation |
|
||||
| `packManager` | `PackManager` | Booster pack opening logic |
|
||||
| `save` | `SaveManager` | localStorage persistence (versioned) |
|
||||
| `missions` | raw JSON | Campaign mission data |
|
||||
|
||||
### Combat System (`src/combat/`)
|
||||
|
||||
- **`CombatEngine.js`** — Core battle loop. Turn phases: Draw → Deploy → Activation → Rupture → Jam → Commander Skills → Card Attacks → Cleanup. Uses event queue for animation sequencing.
|
||||
- **`SkillProcessor.js`** — Resolves card skill effects (strike, swipe, pierce, rupture, berserk, siege, rally, heal, protect, jam, enfeeble, weaken, etc.) with triggers: `on_turn_start`, `on_attack`, `on_kill`.
|
||||
- **`CombatAI.js`** — AI opponent decision-making.
|
||||
|
||||
Combat uses `RNG.js` (seeded random) for deterministic, replayable battles.
|
||||
|
||||
### Card Rendering
|
||||
|
||||
`CardObject.js` handles all visual card rendering (stats, art, skills). `BattleField.js` manages the 4-lane battle arena layout.
|
||||
|
||||
### Data Model
|
||||
|
||||
All game content is JSON in `data/`:
|
||||
- **`cards.json`** — Card stats, faction, rarity, skills
|
||||
- **`packs.json`** — Booster pack definitions
|
||||
- **`missions.json`** — Campaign missions with enemy decks and rewards
|
||||
- **`skills.json`** — Skill reference (skills are also inline in cards)
|
||||
|
||||
Card factions: `imperial`, `raider`, `bloodthirsty`, `xeno`, `righteous`
|
||||
Card types: `commander`, `assault`
|
||||
Rarities: `common`, `rare`, `epic`, `legendary`
|
||||
|
||||
### Save Format (localStorage)
|
||||
|
||||
```json
|
||||
{
|
||||
"version": 1,
|
||||
"gold": 400,
|
||||
"collection": { "cardId": count },
|
||||
"decks": [],
|
||||
"campaignProgress": { "completedMissions": [], "currentChapter": 1 },
|
||||
"starterDeckReceived": false
|
||||
}
|
||||
```
|
||||
|
|
@ -9,10 +9,14 @@ import { FusionScene } from './scenes/FusionScene.js';
|
|||
|
||||
const config = {
|
||||
type: Phaser.AUTO,
|
||||
width: 1280,
|
||||
height: 720,
|
||||
width: 1920,
|
||||
height: 1080,
|
||||
backgroundColor: '#1a1a2e',
|
||||
parent: document.body,
|
||||
scale: {
|
||||
mode: Phaser.Scale.FIT,
|
||||
autoCenter: Phaser.Scale.CENTER_BOTH
|
||||
},
|
||||
scene: [
|
||||
BootScene,
|
||||
MainMenuScene,
|
||||
|
|
|
|||
|
|
@ -18,10 +18,9 @@ export class BattleScene extends Phaser.Scene {
|
|||
const save = this.registry.get('save');
|
||||
|
||||
// Layout constants
|
||||
// Lanes: 4 lanes centred at x = 295, 505, 715, 925 (spacing 210)
|
||||
// Commander column: x = 110
|
||||
// Right UI panel: x = 1090–1280
|
||||
// Opponent row: y = 240 Player row: y = 490 Midline: y = 362
|
||||
// Lanes: 4 lanes centred at x = 430, 740, 1050, 1360 (spacing 310)
|
||||
// Commander column: x = 150
|
||||
// Opponent row: y = 305 Player row: y = 715 Midline: y = 510
|
||||
|
||||
// Particle dot texture (white circle, 8×8) used for attack effects
|
||||
if (!this.textures.exists('particle_dot')) {
|
||||
|
|
@ -36,25 +35,25 @@ export class BattleScene extends Phaser.Scene {
|
|||
this.add.rectangle(width / 2, height / 2, width, height, 0x1a1a2e);
|
||||
|
||||
// Midfield divider
|
||||
this.add.rectangle(width / 2, 362, width, 2, 0x334466);
|
||||
this.add.rectangle(width / 2, 510, width, 2, 0x334466);
|
||||
|
||||
// Vertical lane separators (5 lines bounding 4 lanes)
|
||||
for (let i = 0; i < 5; i++) {
|
||||
const x = 190 + i * 210;
|
||||
this.add.rectangle(x, 362, 1, 600, 0x222244);
|
||||
const x = 280 + i * 310;
|
||||
this.add.rectangle(x, 510, 1, 780, 0x222244);
|
||||
}
|
||||
|
||||
// Commander column right edge
|
||||
this.add.rectangle(190, height / 2, 1, height, 0x222244);
|
||||
this.add.rectangle(280, height / 2, 1, height, 0x222244);
|
||||
|
||||
// Side labels
|
||||
this.add.text(14, 72, 'OPPONENT', { fontSize: '13px', color: '#ff8888' });
|
||||
this.add.text(14, 368, 'PLAYER', { fontSize: '13px', color: '#88aaff' });
|
||||
this.add.text(14, 95, 'OPPONENT', { fontSize: '17px', color: '#ff8888' });
|
||||
this.add.text(14, 520, 'PLAYER', { fontSize: '17px', color: '#88aaff' });
|
||||
|
||||
this.battlefield = new BattleField(this, {
|
||||
playerY: 490, opponentY: 240,
|
||||
commanderPlayerX: 110, commanderOpponentX: 110,
|
||||
laneStartX: 295, laneSpacing: 210
|
||||
playerY: 715, opponentY: 305,
|
||||
commanderPlayerX: 150, commanderOpponentX: 150,
|
||||
laneStartX: 430, laneSpacing: 310
|
||||
});
|
||||
|
||||
// Setup combat
|
||||
|
|
@ -93,46 +92,46 @@ export class BattleScene extends Phaser.Scene {
|
|||
this._buildCommanderDisplay();
|
||||
|
||||
// ── Top bar ──────────────────────────────────────────────────────────────
|
||||
this.turnText = this.add.text(width / 2, 18, 'Turn 0', {
|
||||
this.turnText = this.add.text(960, 30, 'Turn 0', {
|
||||
fontSize: '20px', color: '#ffffff'
|
||||
}).setOrigin(0.5);
|
||||
|
||||
const battleLabel = this.missionData ? this.missionData.name : 'Skirmish Battle';
|
||||
this.add.text(width / 2, 44, battleLabel, {
|
||||
this.add.text(960, 58, battleLabel, {
|
||||
fontSize: '14px', color: '#888888'
|
||||
}).setOrigin(0.5);
|
||||
|
||||
// ── Bottom bar ────────────────────────────────────────────────────────────
|
||||
this.statusText = this.add.text(width / 2, 706, 'Press SPACE or click Next Turn to start', {
|
||||
fontSize: '13px', color: '#aaaaaa'
|
||||
// ── Combat log — bottom strip ────────────────────────────────────────────
|
||||
this.statusText = this.add.text(width / 2, 1058, 'Press SPACE or click Next Turn to start', {
|
||||
fontSize: '17px', color: '#aaaaaa'
|
||||
}).setOrigin(0.5);
|
||||
|
||||
this.logLines = [];
|
||||
for (let i = 0; i < 5; i++) {
|
||||
this.logLines.push(this.add.text(14, 632 + i * 14, '', {
|
||||
fontSize: '11px', color: '#777777'
|
||||
for (let i = 0; i < 6; i++) {
|
||||
this.logLines.push(this.add.text(14, 915 + i * 20, '', {
|
||||
fontSize: '15px', color: '#777777'
|
||||
}));
|
||||
}
|
||||
|
||||
// ── Right panel buttons ───────────────────────────────────────────────────
|
||||
// ── Top bar buttons ─────────────────────────────────────────────────────
|
||||
this.autoPlay = false;
|
||||
this.autoTimer = null;
|
||||
this.waitingForPick = false;
|
||||
this.isAnimating = false;
|
||||
|
||||
const autoBtn = this.add.rectangle(1185, 80, 170, 38, 0x224422)
|
||||
const autoBtn = this.add.rectangle(1680, 35, 180, 44, 0x224422)
|
||||
.setInteractive({ useHandCursor: true })
|
||||
.setStrokeStyle(1, 0x44aa44);
|
||||
this.autoBtnText = this.add.text(1185, 80, 'Auto: OFF', {
|
||||
fontSize: '15px', color: '#ffffff'
|
||||
this.autoBtnText = this.add.text(1680, 35, 'Auto: OFF', {
|
||||
fontSize: '18px', color: '#ffffff'
|
||||
}).setOrigin(0.5);
|
||||
autoBtn.on('pointerdown', () => this._toggleAuto());
|
||||
|
||||
const nextBtn = this.add.rectangle(1185, 130, 170, 38, 0x1a3a5c)
|
||||
const nextBtn = this.add.rectangle(1840, 35, 180, 44, 0x1a3a5c)
|
||||
.setInteractive({ useHandCursor: true })
|
||||
.setStrokeStyle(1, 0x4488ff);
|
||||
this.add.text(1185, 130, 'Next Turn', {
|
||||
fontSize: '15px', color: '#ffffff'
|
||||
this.add.text(1840, 35, 'Next Turn', {
|
||||
fontSize: '18px', color: '#ffffff'
|
||||
}).setOrigin(0.5);
|
||||
nextBtn.on('pointerdown', () => this._beginTurn());
|
||||
|
||||
|
|
@ -148,16 +147,16 @@ export class BattleScene extends Phaser.Scene {
|
|||
this.commanderObjects = new Map(); // instanceId → CardObject (persists across _renderState)
|
||||
|
||||
const specs = [
|
||||
{ data: state.opponent.commander, cx: 110, cy: 240, label: 'ENEMY CMD', labelColor: '#ff8888' },
|
||||
{ data: state.player.commander, cx: 110, cy: 490, label: 'COMMANDER', labelColor: '#ffd700' }
|
||||
{ data: state.opponent.commander, cx: 150, cy: 305, label: 'ENEMY CMD', labelColor: '#ff8888' },
|
||||
{ data: state.player.commander, cx: 150, cy: 715, label: 'COMMANDER', labelColor: '#ffd700' }
|
||||
];
|
||||
|
||||
for (const s of specs) {
|
||||
const w = 160, h = 180;
|
||||
const w = 240, h = 336;
|
||||
|
||||
// Label above the card (separate text so it never scales/shakes with the card)
|
||||
this.add.text(s.cx, s.cy - h / 2 - 14, s.label,
|
||||
{ fontSize: '11px', color: s.labelColor }).setOrigin(0.5);
|
||||
{ fontSize: '15px', color: s.labelColor }).setOrigin(0.5);
|
||||
|
||||
// Use CardObject for visual consistency with lane cards
|
||||
const cardObj = new CardObject(this, s.cx, s.cy, s.data, { width: w, height: h });
|
||||
|
|
@ -166,8 +165,8 @@ export class BattleScene extends Phaser.Scene {
|
|||
this.commanderObjects.set(s.data.instanceId, cardObj);
|
||||
}
|
||||
|
||||
this.oDeckText = this.add.text(14, 92, '', { fontSize: '12px', color: '#aaaaaa' });
|
||||
this.pDeckText = this.add.text(14, 385, '', { fontSize: '12px', color: '#aaaaaa' });
|
||||
this.oDeckText = this.add.text(14, 115, '', { fontSize: '15px', color: '#aaaaaa' });
|
||||
this.pDeckText = this.add.text(14, 540, '', { fontSize: '15px', color: '#aaaaaa' });
|
||||
}
|
||||
|
||||
_beginTurn() {
|
||||
|
|
@ -381,7 +380,7 @@ export class BattleScene extends Phaser.Scene {
|
|||
|
||||
// ── Attacker: particle burst from card edges ──────────────────────────────
|
||||
if (attackerObj) {
|
||||
const cardW = 170, cardH = 190;
|
||||
const cardW = 260, cardH = 364;
|
||||
const emitter = this.add.particles(attackerObj.x, attackerObj.y, 'particle_dot', {
|
||||
speed: { min: 90, max: 300 },
|
||||
scale: { start: 1.8, end: 0 },
|
||||
|
|
@ -419,7 +418,7 @@ export class BattleScene extends Phaser.Scene {
|
|||
const origX = defenderObj.x;
|
||||
this.tweens.add({
|
||||
targets: defenderObj,
|
||||
x: { from: origX - 18, to: origX + 18 },
|
||||
x: { from: origX - 26, to: origX + 26 },
|
||||
duration: 60,
|
||||
yoyo: true,
|
||||
repeat: 6,
|
||||
|
|
@ -429,17 +428,17 @@ export class BattleScene extends Phaser.Scene {
|
|||
}
|
||||
|
||||
// Floating damage number
|
||||
const dmgAnchorX = defenderObj?.x ?? (attackerObj?.x ?? 640);
|
||||
const dmgAnchorY = defenderObj?.y ?? (attackerObj?.y ?? 360);
|
||||
const dmgAnchorX = defenderObj?.x ?? (attackerObj?.x ?? 960);
|
||||
const dmgAnchorY = defenderObj?.y ?? (attackerObj?.y ?? 510);
|
||||
const dmgText = this.add.text(
|
||||
dmgAnchorX, dmgAnchorY - 70,
|
||||
dmgAnchorX, dmgAnchorY - 100,
|
||||
`-${event.damage}`,
|
||||
{ fontSize: '46px', color: '#ff3333', stroke: '#000000', strokeThickness: 5 }
|
||||
{ fontSize: '56px', color: '#ff3333', stroke: '#000000', strokeThickness: 5 }
|
||||
).setOrigin(0.5).setDepth(30);
|
||||
|
||||
this.tweens.add({
|
||||
targets: dmgText,
|
||||
y: dmgAnchorY - 170,
|
||||
y: dmgAnchorY - 250,
|
||||
alpha: 0,
|
||||
duration: 1300,
|
||||
ease: 'Power2',
|
||||
|
|
@ -462,21 +461,21 @@ export class BattleScene extends Phaser.Scene {
|
|||
this.pickerObjects.push(overlay);
|
||||
|
||||
// Panel background
|
||||
const cardW = 170, cardH = 220;
|
||||
const gap = 30;
|
||||
const panelPadX = 50, panelPadY = 60;
|
||||
const cardW = 260, cardH = 364;
|
||||
const gap = 40;
|
||||
const panelPadX = 60, panelPadY = 70;
|
||||
const totalW = hand.length * cardW + (hand.length - 1) * gap;
|
||||
const panelW = Math.max(totalW + panelPadX * 2, 400);
|
||||
const panelW = Math.max(totalW + panelPadX * 2, 500);
|
||||
const panelH = cardH + panelPadY * 2;
|
||||
const panelY = height / 2;
|
||||
const panelY = 540;
|
||||
|
||||
const panel = this.add.rectangle(width / 2, panelY, panelW, panelH, 0x0d1b2a, 0.97)
|
||||
.setStrokeStyle(2, 0x4488ff)
|
||||
.setDepth(10);
|
||||
this.pickerObjects.push(panel);
|
||||
|
||||
const title = this.add.text(width / 2, panelY - panelH / 2 + 22, 'Choose a card to deploy', {
|
||||
fontSize: '20px', color: '#d4af37'
|
||||
const title = this.add.text(width / 2, panelY - panelH / 2 + 26, 'Choose a card to deploy', {
|
||||
fontSize: '22px', color: '#d4af37'
|
||||
}).setOrigin(0.5).setDepth(11);
|
||||
this.pickerObjects.push(title);
|
||||
|
||||
|
|
@ -513,12 +512,12 @@ export class BattleScene extends Phaser.Scene {
|
|||
});
|
||||
|
||||
// Pass button
|
||||
const passBtn = this.add.rectangle(width / 2, panelY + panelH / 2 - 24, 180, 34, 0x333333)
|
||||
const passBtn = this.add.rectangle(width / 2, panelY + panelH / 2 - 28, 220, 44, 0x333333)
|
||||
.setStrokeStyle(1, 0x888888)
|
||||
.setInteractive({ useHandCursor: true })
|
||||
.setDepth(11);
|
||||
const passTxt = this.add.text(width / 2, panelY + panelH / 2 - 24, 'Pass (deploy nothing)', {
|
||||
fontSize: '13px', color: '#aaaaaa'
|
||||
const passTxt = this.add.text(width / 2, panelY + panelH / 2 - 28, 'Pass (deploy nothing)', {
|
||||
fontSize: '17px', color: '#aaaaaa'
|
||||
}).setOrigin(0.5).setDepth(12);
|
||||
passBtn.on('pointerdown', () => {
|
||||
this._destroyCardPicker();
|
||||
|
|
@ -542,19 +541,19 @@ export class BattleScene extends Phaser.Scene {
|
|||
|
||||
state.player.lanes.forEach((card, i) => {
|
||||
const pos = this.battlefield.getPlayerLanePos(i);
|
||||
const co = new CardObject(this, pos.x, pos.y, card, { width: 170, height: 190 });
|
||||
const co = new CardObject(this, pos.x, pos.y, card, { width: 260, height: 364 });
|
||||
this.cardObjects.set(card.instanceId, co);
|
||||
});
|
||||
|
||||
state.opponent.lanes.forEach((card, i) => {
|
||||
const pos = this.battlefield.getOpponentLanePos(i);
|
||||
const co = new CardObject(this, pos.x, pos.y, card, { width: 170, height: 190 });
|
||||
const co = new CardObject(this, pos.x, pos.y, card, { width: 260, height: 364 });
|
||||
this.cardObjects.set(card.instanceId, co);
|
||||
});
|
||||
}
|
||||
|
||||
_updateLog() {
|
||||
const recent = this.engine.log.slice(-5);
|
||||
const recent = this.engine.log.slice(-6);
|
||||
this.logLines.forEach((line, i) => {
|
||||
line.setText(recent[i] || '');
|
||||
});
|
||||
|
|
@ -591,13 +590,13 @@ export class BattleScene extends Phaser.Scene {
|
|||
// Overlay
|
||||
this.add.rectangle(width / 2, height / 2, width, height, won ? 0x003300 : 0x330000, 0.75);
|
||||
|
||||
this.add.text(width / 2, height / 2 - 100, won ? 'VICTORY!' : 'DEFEAT', {
|
||||
fontSize: '56px', color: won ? '#44ff44' : '#ff4444',
|
||||
this.add.text(width / 2, height / 2 - 120, won ? 'VICTORY!' : 'DEFEAT', {
|
||||
fontSize: '72px', color: won ? '#44ff44' : '#ff4444',
|
||||
stroke: '#000000', strokeThickness: 6
|
||||
}).setOrigin(0.5);
|
||||
|
||||
this.add.text(width / 2, height / 2 - 40, `Battle lasted ${this.engine.turn} turns`, {
|
||||
fontSize: '18px', color: '#aaaaaa'
|
||||
fontSize: '24px', color: '#aaaaaa'
|
||||
}).setOrigin(0.5);
|
||||
|
||||
if (won && this.missionData) {
|
||||
|
|
@ -609,8 +608,8 @@ export class BattleScene extends Phaser.Scene {
|
|||
}
|
||||
this.registry.set('save', save);
|
||||
|
||||
this.add.text(width / 2, height / 2 + 10, `+${rewards.gold} Gold`, {
|
||||
fontSize: '28px', color: '#ffd700'
|
||||
this.add.text(width / 2, height / 2 + 20, `+${rewards.gold} Gold`, {
|
||||
fontSize: '36px', color: '#ffd700'
|
||||
}).setOrigin(0.5);
|
||||
|
||||
if (rewards.cards && rewards.cards.length > 0) {
|
||||
|
|
@ -619,30 +618,30 @@ export class BattleScene extends Phaser.Scene {
|
|||
const c = cardManager.getCard(id);
|
||||
return c ? c.name : id;
|
||||
}).join(', ');
|
||||
this.add.text(width / 2, height / 2 + 50, `New card(s): ${cardNames}`, {
|
||||
fontSize: '16px', color: '#aaffaa'
|
||||
this.add.text(width / 2, height / 2 + 70, `New card(s): ${cardNames}`, {
|
||||
fontSize: '22px', color: '#aaffaa'
|
||||
}).setOrigin(0.5);
|
||||
}
|
||||
} else if (!won) {
|
||||
this.add.text(width / 2, height / 2 + 10, 'Better luck next time!', {
|
||||
fontSize: '20px', color: '#aaaaaa'
|
||||
this.add.text(width / 2, height / 2 + 20, 'Better luck next time!', {
|
||||
fontSize: '24px', color: '#aaaaaa'
|
||||
}).setOrigin(0.5);
|
||||
}
|
||||
|
||||
const backBtn = this.add.rectangle(width / 2, height / 2 + 130, 200, 50, 0x1a3a5c)
|
||||
const backBtn = this.add.rectangle(width / 2, height / 2 + 160, 260, 60, 0x1a3a5c)
|
||||
.setInteractive({ useHandCursor: true })
|
||||
.setStrokeStyle(2, 0x4488ff);
|
||||
this.add.text(width / 2, height / 2 + 130, 'Continue', {
|
||||
fontSize: '22px', color: '#ffffff'
|
||||
this.add.text(width / 2, height / 2 + 160, 'Continue', {
|
||||
fontSize: '28px', color: '#ffffff'
|
||||
}).setOrigin(0.5);
|
||||
backBtn.on('pointerdown', () => this.scene.start(this.missionData ? 'CampaignScene' : 'MainMenuScene'));
|
||||
}
|
||||
|
||||
_makeBackButton() {
|
||||
const bg = this.add.rectangle(1185, 185, 170, 38, 0x333333)
|
||||
const bg = this.add.rectangle(80, 35, 180, 44, 0x333333)
|
||||
.setInteractive({ useHandCursor: true })
|
||||
.setStrokeStyle(1, 0x888888);
|
||||
this.add.text(1185, 185, '← Back', { fontSize: '15px', color: '#ffffff' }).setOrigin(0.5);
|
||||
this.add.text(80, 35, '← Back', { fontSize: '18px', color: '#ffffff' }).setOrigin(0.5);
|
||||
bg.on('pointerdown', () => {
|
||||
if (this.autoTimer) this.autoTimer.remove();
|
||||
this.scene.start('MainMenuScene');
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ export class BootScene extends Phaser.Scene {
|
|||
});
|
||||
|
||||
// Loading text
|
||||
const loadText = this.add.text(640, 360, 'Loading...', {
|
||||
const loadText = this.add.text(this.scale.width / 2, this.scale.height / 2, 'Loading...', {
|
||||
fontSize: '28px', color: '#ffffff'
|
||||
}).setOrigin(0.5);
|
||||
|
||||
|
|
|
|||
|
|
@ -7,8 +7,8 @@ export class CampaignScene extends Phaser.Scene {
|
|||
const { width, height } = this.scale;
|
||||
|
||||
this.add.rectangle(width / 2, height / 2, width, height, 0x0d1b2a);
|
||||
this.add.text(width / 2, 40, 'Campaign — Chapter 1', {
|
||||
fontSize: '32px', color: '#d4af37'
|
||||
this.add.text(width / 2, 45, 'Campaign — Chapter 1', {
|
||||
fontSize: '42px', color: '#d4af37'
|
||||
}).setOrigin(0.5);
|
||||
|
||||
const completed = save.campaignProgress.completedMissions;
|
||||
|
|
@ -16,13 +16,13 @@ export class CampaignScene extends Phaser.Scene {
|
|||
missions.forEach((mission, i) => {
|
||||
const isUnlocked = !mission.unlockCondition || completed.includes(mission.unlockCondition);
|
||||
const isDone = completed.includes(mission.id);
|
||||
const x = 200 + (i % 4) * 250;
|
||||
const y = 300 + Math.floor(i / 4) * 200;
|
||||
const x = 300 + (i % 4) * 375;
|
||||
const y = 380 + Math.floor(i / 4) * 260;
|
||||
|
||||
const color = isDone ? 0x228822 : isUnlocked ? 0x1a3a5c : 0x333333;
|
||||
const borderColor = isDone ? 0x44ff44 : isUnlocked ? 0x4488ff : 0x666666;
|
||||
|
||||
const node = this.add.rectangle(x, y, 200, 140, color)
|
||||
const node = this.add.rectangle(x, y, 300, 220, color)
|
||||
.setStrokeStyle(2, borderColor);
|
||||
|
||||
if (isUnlocked) {
|
||||
|
|
@ -32,34 +32,34 @@ export class CampaignScene extends Phaser.Scene {
|
|||
node.on('pointerdown', () => this._startMission(mission));
|
||||
}
|
||||
|
||||
this.add.text(x, y - 45, mission.name, {
|
||||
fontSize: '14px', color: isUnlocked ? '#ffffff' : '#666666',
|
||||
wordWrap: { width: 180 }, align: 'center'
|
||||
this.add.text(x, y - 70, mission.name, {
|
||||
fontSize: '20px', color: isUnlocked ? '#ffffff' : '#666666',
|
||||
wordWrap: { width: 270 }, align: 'center'
|
||||
}).setOrigin(0.5);
|
||||
|
||||
this.add.text(x, y - 22, `Chapter ${mission.chapter}`, {
|
||||
fontSize: '12px', color: '#888888'
|
||||
this.add.text(x, y - 35, `Chapter ${mission.chapter}`, {
|
||||
fontSize: '16px', color: '#888888'
|
||||
}).setOrigin(0.5);
|
||||
|
||||
this.add.text(x, y, isDone ? 'Complete' : isUnlocked ? 'Click to battle' : 'Locked', {
|
||||
fontSize: '12px', color: isDone ? '#44ff44' : isUnlocked ? '#aaffaa' : '#666666'
|
||||
fontSize: '16px', color: isDone ? '#44ff44' : isUnlocked ? '#aaffaa' : '#666666'
|
||||
}).setOrigin(0.5);
|
||||
|
||||
// Rewards
|
||||
this.add.text(x, y + 22, `+${mission.rewards.gold} gold`, {
|
||||
fontSize: '11px', color: '#ffd700'
|
||||
this.add.text(x, y + 30, `+${mission.rewards.gold} gold`, {
|
||||
fontSize: '14px', color: '#ffd700'
|
||||
}).setOrigin(0.5);
|
||||
|
||||
if (mission.rewards.cards && mission.rewards.cards.length > 0) {
|
||||
this.add.text(x, y + 40, `Card reward available`, {
|
||||
fontSize: '10px', color: '#aaffaa'
|
||||
this.add.text(x, y + 55, `Card reward available`, {
|
||||
fontSize: '14px', color: '#aaffaa'
|
||||
}).setOrigin(0.5);
|
||||
}
|
||||
});
|
||||
|
||||
// Mission lore panel
|
||||
this.loreText = this.add.text(width / 2, height - 60, 'Hover over a mission to learn more.', {
|
||||
fontSize: '13px', color: '#888888', align: 'center', wordWrap: { width: width - 100 }
|
||||
this.loreText = this.add.text(width / 2, height - 70, 'Hover over a mission to learn more.', {
|
||||
fontSize: '18px', color: '#888888', align: 'center', wordWrap: { width: width - 100 }
|
||||
}).setOrigin(0.5);
|
||||
|
||||
// Back button
|
||||
|
|
@ -69,8 +69,8 @@ export class CampaignScene extends Phaser.Scene {
|
|||
_startMission(mission) {
|
||||
const save = this.registry.get('save');
|
||||
if (!save.decks || save.decks.length === 0) {
|
||||
this.add.text(640, 660, 'No deck! Create a deck in Deck Builder first.', {
|
||||
fontSize: '16px', color: '#ff4444'
|
||||
this.add.text(960, 940, 'No deck! Create a deck in Deck Builder first.', {
|
||||
fontSize: '22px', color: '#ff4444'
|
||||
}).setOrigin(0.5);
|
||||
return;
|
||||
}
|
||||
|
|
@ -78,10 +78,10 @@ export class CampaignScene extends Phaser.Scene {
|
|||
}
|
||||
|
||||
_makeBackButton() {
|
||||
const bg = this.add.rectangle(80, 40, 120, 40, 0x333333)
|
||||
const bg = this.add.rectangle(80, 45, 160, 50, 0x333333)
|
||||
.setInteractive({ useHandCursor: true })
|
||||
.setStrokeStyle(1, 0x888888);
|
||||
this.add.text(80, 40, 'Back', { fontSize: '16px', color: '#ffffff' }).setOrigin(0.5);
|
||||
this.add.text(80, 45, 'Back', { fontSize: '18px', color: '#ffffff' }).setOrigin(0.5);
|
||||
bg.on('pointerdown', () => this.scene.start('MainMenuScene'));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,15 +7,15 @@ export class CollectionScene extends Phaser.Scene {
|
|||
const { width, height } = this.scale;
|
||||
|
||||
this.add.rectangle(width / 2, height / 2, width, height, 0x0d1b2a);
|
||||
this.add.text(width / 2, 35, 'Card Collection', {
|
||||
fontSize: '28px', color: '#d4af37'
|
||||
this.add.text(width / 2, 40, 'Card Collection', {
|
||||
fontSize: '38px', color: '#d4af37'
|
||||
}).setOrigin(0.5);
|
||||
|
||||
// Stats
|
||||
const totalCards = cardManager.getAllCards().length;
|
||||
const ownedCards = Object.keys(save.collection).length;
|
||||
this.add.text(width - 20, 20, `Owned: ${ownedCards}/${totalCards}`, {
|
||||
fontSize: '14px', color: '#aaaaaa'
|
||||
fontSize: '20px', color: '#aaaaaa'
|
||||
}).setOrigin(1, 0);
|
||||
|
||||
this.filter = { faction: 'all', rarity: 'all' };
|
||||
|
|
@ -29,26 +29,26 @@ export class CollectionScene extends Phaser.Scene {
|
|||
const factions = ['all', 'imperial', 'raider'];
|
||||
const rarities = ['all', 'common', 'rare', 'epic', 'legendary'];
|
||||
|
||||
this.add.text(20, 68, 'Faction:', { fontSize: '12px', color: '#888888' });
|
||||
let x = 100;
|
||||
this.add.text(20, 90, 'Faction:', { fontSize: '16px', color: '#888888' });
|
||||
let x = 130;
|
||||
factions.forEach(f => {
|
||||
const btn = this.add.rectangle(x + 40, 75, 80, 26, 0x1a3a5c)
|
||||
const btn = this.add.rectangle(x + 55, 97, 110, 34, 0x1a3a5c)
|
||||
.setInteractive({ useHandCursor: true })
|
||||
.setStrokeStyle(1, 0x4488ff);
|
||||
this.add.text(x + 40, 75, f, { fontSize: '11px', color: '#ffffff' }).setOrigin(0.5);
|
||||
this.add.text(x + 55, 97, f, { fontSize: '15px', color: '#ffffff' }).setOrigin(0.5);
|
||||
btn.on('pointerdown', () => { this.filter.faction = f; this._renderCards(); });
|
||||
x += 90;
|
||||
x += 120;
|
||||
});
|
||||
|
||||
this.add.text(20, 98, 'Rarity:', { fontSize: '12px', color: '#888888' });
|
||||
x = 100;
|
||||
this.add.text(20, 128, 'Rarity:', { fontSize: '16px', color: '#888888' });
|
||||
x = 130;
|
||||
rarities.forEach(r => {
|
||||
const btn = this.add.rectangle(x + 40, 105, 80, 26, 0x1a3a5c)
|
||||
const btn = this.add.rectangle(x + 55, 135, 110, 34, 0x1a3a5c)
|
||||
.setInteractive({ useHandCursor: true })
|
||||
.setStrokeStyle(1, 0x4488ff);
|
||||
this.add.text(x + 40, 105, r, { fontSize: '11px', color: '#ffffff' }).setOrigin(0.5);
|
||||
this.add.text(x + 55, 135, r, { fontSize: '15px', color: '#ffffff' }).setOrigin(0.5);
|
||||
btn.on('pointerdown', () => { this.filter.rarity = r; this._renderCards(); });
|
||||
x += 90;
|
||||
x += 120;
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -64,19 +64,19 @@ export class CollectionScene extends Phaser.Scene {
|
|||
.filter(c => this.filter.rarity === 'all' || c.rarity === this.filter.rarity);
|
||||
|
||||
const cols = 8;
|
||||
const cardW = 130;
|
||||
const cardH = 90;
|
||||
const startX = 75;
|
||||
const startY = 148;
|
||||
const padX = 10;
|
||||
const padY = 10;
|
||||
const cardW = 195;
|
||||
const cardH = 135;
|
||||
const startX = 110;
|
||||
const startY = 190;
|
||||
const padX = 15;
|
||||
const padY = 15;
|
||||
|
||||
allCards.forEach((card, i) => {
|
||||
const col = i % cols;
|
||||
const row = Math.floor(i / cols);
|
||||
const x = startX + col * (cardW + padX);
|
||||
const y = startY + row * (cardH + padY);
|
||||
if (y > 670) return; // clip overflow
|
||||
if (y > 970) return; // clip overflow
|
||||
|
||||
const owned = save.collection[card.id] || 0;
|
||||
const rarityColors = { common: 0x888888, rare: 0x4488ff, epic: 0xaa44ff, legendary: 0xffaa00 };
|
||||
|
|
@ -87,53 +87,53 @@ export class CollectionScene extends Phaser.Scene {
|
|||
.setStrokeStyle(2, owned > 0 ? (rarityColors[card.rarity] || 0x888888) : 0x333333);
|
||||
this.cardGroup.push(bg);
|
||||
|
||||
const nameT = this.add.text(x, y - 30, card.name, {
|
||||
fontSize: '9px', color: owned > 0 ? '#ffffff' : '#555555',
|
||||
wordWrap: { width: cardW - 4 }, align: 'center'
|
||||
const nameT = this.add.text(x, y - 45, card.name, {
|
||||
fontSize: '14px', color: owned > 0 ? '#ffffff' : '#555555',
|
||||
wordWrap: { width: cardW - 6 }, align: 'center'
|
||||
}).setOrigin(0.5);
|
||||
this.cardGroup.push(nameT);
|
||||
|
||||
const typeT = this.add.text(x, y - 18, `[${card.type}]`, {
|
||||
fontSize: '8px', color: owned > 0 ? '#aaaaaa' : '#444444'
|
||||
const typeT = this.add.text(x, y - 28, `[${card.type}]`, {
|
||||
fontSize: '12px', color: owned > 0 ? '#aaaaaa' : '#444444'
|
||||
}).setOrigin(0.5);
|
||||
this.cardGroup.push(typeT);
|
||||
|
||||
const statsT = this.add.text(x, y - 4, `ATK:${card.attack} HP:${card.health}${card.armor > 0 ? ' ARM:' + card.armor : ''}`, {
|
||||
fontSize: '9px', color: owned > 0 ? '#aaaaff' : '#444455'
|
||||
const statsT = this.add.text(x, y - 8, `ATK:${card.attack} HP:${card.health}${card.armor > 0 ? ' ARM:' + card.armor : ''}`, {
|
||||
fontSize: '14px', color: owned > 0 ? '#aaaaff' : '#444455'
|
||||
}).setOrigin(0.5);
|
||||
this.cardGroup.push(statsT);
|
||||
|
||||
const skillsT = this.add.text(x, y + 10, card.skills.map(s => s.name).join(', '), {
|
||||
fontSize: '8px', color: owned > 0 ? '#ffcc44' : '#443300',
|
||||
wordWrap: { width: cardW - 4 }, align: 'center'
|
||||
const skillsT = this.add.text(x, y + 12, card.skills.map(s => s.name).join(', '), {
|
||||
fontSize: '12px', color: owned > 0 ? '#ffcc44' : '#443300',
|
||||
wordWrap: { width: cardW - 6 }, align: 'center'
|
||||
}).setOrigin(0.5);
|
||||
this.cardGroup.push(skillsT);
|
||||
|
||||
const ownedT = this.add.text(x, y + 28, `x${owned}`, {
|
||||
fontSize: '11px', color: owned > 0 ? '#44ff44' : '#333333'
|
||||
const ownedT = this.add.text(x, y + 38, `x${owned}`, {
|
||||
fontSize: '16px', color: owned > 0 ? '#44ff44' : '#333333'
|
||||
}).setOrigin(0.5);
|
||||
this.cardGroup.push(ownedT);
|
||||
|
||||
const rarityT = this.add.text(x + cardW / 2 - 4, y - cardH / 2 + 6, card.rarity[0].toUpperCase(), {
|
||||
fontSize: '9px',
|
||||
fontSize: '13px',
|
||||
color: '#' + (rarityColors[card.rarity] || 0x888888).toString(16).padStart(6, '0')
|
||||
}).setOrigin(1, 0);
|
||||
this.cardGroup.push(rarityT);
|
||||
});
|
||||
|
||||
if (allCards.length === 0) {
|
||||
const t = this.add.text(640, 400, 'No cards match the current filter.', {
|
||||
fontSize: '16px', color: '#666666'
|
||||
const t = this.add.text(960, 540, 'No cards match the current filter.', {
|
||||
fontSize: '22px', color: '#666666'
|
||||
}).setOrigin(0.5);
|
||||
this.cardGroup.push(t);
|
||||
}
|
||||
}
|
||||
|
||||
_makeBackButton() {
|
||||
const bg = this.add.rectangle(80, 700, 120, 35, 0x333333)
|
||||
const bg = this.add.rectangle(80, 1040, 160, 45, 0x333333)
|
||||
.setInteractive({ useHandCursor: true })
|
||||
.setStrokeStyle(1, 0x888888);
|
||||
this.add.text(80, 700, 'Back', { fontSize: '14px', color: '#ffffff' }).setOrigin(0.5);
|
||||
this.add.text(80, 1040, 'Back', { fontSize: '18px', color: '#ffffff' }).setOrigin(0.5);
|
||||
bg.on('pointerdown', () => this.scene.start('MainMenuScene'));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ export class DeckBuilderScene extends Phaser.Scene {
|
|||
create() {
|
||||
const { width, height } = this.scale;
|
||||
this.add.rectangle(width / 2, height / 2, width, height, 0x0d1b2a);
|
||||
this.add.text(width / 2, 35, 'Deck Builder', { fontSize: '28px', color: '#d4af37' }).setOrigin(0.5);
|
||||
this.add.text(width / 2, 40, 'Deck Builder', { fontSize: '38px', color: '#d4af37' }).setOrigin(0.5);
|
||||
|
||||
const save = this.registry.get('save');
|
||||
|
||||
|
|
@ -30,25 +30,25 @@ export class DeckBuilderScene extends Phaser.Scene {
|
|||
if (this.deckPanelGroup) this.deckPanelGroup.forEach(o => o.destroy());
|
||||
this.deckPanelGroup = [];
|
||||
|
||||
const x0 = 870;
|
||||
const x0 = 1300;
|
||||
const cardManager = this.registry.get('cardManager');
|
||||
|
||||
const title = this.add.text(x0 + 175, 70, 'Current Deck', { fontSize: '18px', color: '#d4af37' }).setOrigin(0.5);
|
||||
const title = this.add.text(x0 + 250, 90, 'Current Deck', { fontSize: '24px', color: '#d4af37' }).setOrigin(0.5);
|
||||
this.deckPanelGroup.push(title);
|
||||
|
||||
// Commander slot
|
||||
const cmdCard = this.workingDeck.commander ? cardManager.getCard(this.workingDeck.commander) : null;
|
||||
const cmdBg = this.add.rectangle(x0 + 175, 110, 330, 36, cmdCard ? 0x1a3a5c : 0x2a1a1c).setStrokeStyle(2, 0xffd700);
|
||||
const cmdText = this.add.text(x0 + 175, 110, cmdCard ? `Commander: ${cmdCard.name}` : 'No Commander Selected', {
|
||||
fontSize: '13px', color: cmdCard ? '#ffd700' : '#888888'
|
||||
const cmdBg = this.add.rectangle(x0 + 250, 140, 500, 50, cmdCard ? 0x1a3a5c : 0x2a1a1c).setStrokeStyle(2, 0xffd700);
|
||||
const cmdText = this.add.text(x0 + 250, 140, cmdCard ? `Commander: ${cmdCard.name}` : 'No Commander Selected', {
|
||||
fontSize: '18px', color: cmdCard ? '#ffd700' : '#888888'
|
||||
}).setOrigin(0.5);
|
||||
this.deckPanelGroup.push(cmdBg, cmdText);
|
||||
|
||||
// Remove commander button
|
||||
if (cmdCard) {
|
||||
const rmCmdBtn = this.add.rectangle(x0 + 325, 110, 28, 26, 0x662222)
|
||||
const rmCmdBtn = this.add.rectangle(x0 + 480, 140, 38, 32, 0x662222)
|
||||
.setInteractive({ useHandCursor: true }).setStrokeStyle(1, 0xff4444);
|
||||
const rmCmdTxt = this.add.text(x0 + 325, 110, 'X', { fontSize: '13px', color: '#ff4444' }).setOrigin(0.5);
|
||||
const rmCmdTxt = this.add.text(x0 + 480, 140, 'X', { fontSize: '16px', color: '#ff4444' }).setOrigin(0.5);
|
||||
rmCmdBtn.on('pointerdown', () => {
|
||||
this.workingDeck.commander = null;
|
||||
this._renderDeckPanel();
|
||||
|
|
@ -60,50 +60,50 @@ export class DeckBuilderScene extends Phaser.Scene {
|
|||
const counts = {};
|
||||
this.workingDeck.cards.forEach(id => { counts[id] = (counts[id] || 0) + 1; });
|
||||
|
||||
let y = 148;
|
||||
let y = 190;
|
||||
Object.entries(counts).forEach(([cardId, count]) => {
|
||||
const card = cardManager.getCard(cardId);
|
||||
if (!card) return;
|
||||
|
||||
const rarityColors = { common: 0x888888, rare: 0x4488ff, epic: 0xaa44ff, legendary: 0xffaa00 };
|
||||
const bg = this.add.rectangle(x0 + 175, y, 330, 28, 0x1a3a5c).setStrokeStyle(1, rarityColors[card.rarity] || 0x336699);
|
||||
const txt = this.add.text(x0 + 20, y, `${card.name} x${count}`, { fontSize: '12px', color: '#ffffff' });
|
||||
const statsT = this.add.text(x0 + 240, y, `ATK:${card.attack}`, { fontSize: '10px', color: '#aaaaff' }).setOrigin(0.5);
|
||||
const bg = this.add.rectangle(x0 + 250, y, 500, 40, 0x1a3a5c).setStrokeStyle(1, rarityColors[card.rarity] || 0x336699);
|
||||
const txt = this.add.text(x0 + 20, y, `${card.name} x${count}`, { fontSize: '16px', color: '#ffffff' });
|
||||
const statsT = this.add.text(x0 + 360, y, `ATK:${card.attack}`, { fontSize: '14px', color: '#aaaaff' }).setOrigin(0.5);
|
||||
|
||||
// Remove one button
|
||||
const rmBtn = this.add.rectangle(x0 + 325, y, 28, 22, 0x662222)
|
||||
const rmBtn = this.add.rectangle(x0 + 480, y, 38, 32, 0x662222)
|
||||
.setInteractive({ useHandCursor: true }).setStrokeStyle(1, 0xff4444);
|
||||
const rmTxt = this.add.text(x0 + 325, y, '-', { fontSize: '16px', color: '#ff4444' }).setOrigin(0.5);
|
||||
const rmTxt = this.add.text(x0 + 480, y, '-', { fontSize: '20px', color: '#ff4444' }).setOrigin(0.5);
|
||||
rmBtn.on('pointerdown', () => {
|
||||
const idx = this.workingDeck.cards.lastIndexOf(cardId);
|
||||
if (idx !== -1) this.workingDeck.cards.splice(idx, 1);
|
||||
this._renderDeckPanel();
|
||||
});
|
||||
this.deckPanelGroup.push(bg, txt, statsT, rmBtn, rmTxt);
|
||||
y += 30;
|
||||
y += 44;
|
||||
});
|
||||
|
||||
// Filler slots
|
||||
const totalSlots = 10;
|
||||
const filledSlots = this.workingDeck.cards.length;
|
||||
for (let s = filledSlots; s < Math.min(totalSlots, filledSlots + 2); s++) {
|
||||
const slotBg = this.add.rectangle(x0 + 175, y, 330, 28, 0x111122).setStrokeStyle(1, 0x333355);
|
||||
const slotTxt = this.add.text(x0 + 175, y, '— empty slot —', { fontSize: '11px', color: '#333355' }).setOrigin(0.5);
|
||||
const slotBg = this.add.rectangle(x0 + 250, y, 500, 40, 0x111122).setStrokeStyle(1, 0x333355);
|
||||
const slotTxt = this.add.text(x0 + 250, y, '— empty slot —', { fontSize: '14px', color: '#333355' }).setOrigin(0.5);
|
||||
this.deckPanelGroup.push(slotBg, slotTxt);
|
||||
y += 30;
|
||||
y += 44;
|
||||
}
|
||||
|
||||
// Total count
|
||||
const countColor = this.workingDeck.cards.length > 10 ? '#ff4444' : this.workingDeck.cards.length >= 3 ? '#aaaaff' : '#888888';
|
||||
const total = this.add.text(x0 + 175, y + 14, `Cards: ${this.workingDeck.cards.length} / 10`, {
|
||||
fontSize: '14px', color: countColor
|
||||
const total = this.add.text(x0 + 250, y + 18, `Cards: ${this.workingDeck.cards.length} / 10`, {
|
||||
fontSize: '18px', color: countColor
|
||||
}).setOrigin(0.5);
|
||||
this.deckPanelGroup.push(total);
|
||||
|
||||
// Validation indicator
|
||||
const validation = DeckManager.validate(this.workingDeck, this.registry.get('cardManager'));
|
||||
const validText = this.add.text(x0 + 175, y + 38, validation.valid ? 'Valid Deck' : validation.errors[0], {
|
||||
fontSize: '12px', color: validation.valid ? '#44ff44' : '#ff6644'
|
||||
const validText = this.add.text(x0 + 250, y + 46, validation.valid ? 'Valid Deck' : validation.errors[0], {
|
||||
fontSize: '16px', color: validation.valid ? '#44ff44' : '#ff6644'
|
||||
}).setOrigin(0.5);
|
||||
this.deckPanelGroup.push(validText);
|
||||
}
|
||||
|
|
@ -116,24 +116,24 @@ export class DeckBuilderScene extends Phaser.Scene {
|
|||
const cardManager = this.registry.get('cardManager');
|
||||
|
||||
// Header
|
||||
const hdr = this.add.text(420, 72, 'Your Cards — click to add to deck', { fontSize: '13px', color: '#888888' }).setOrigin(0.5);
|
||||
const hdr = this.add.text(500, 100, 'Your Cards — click to add to deck', { fontSize: '18px', color: '#888888' }).setOrigin(0.5);
|
||||
this.cardListGroup.push(hdr);
|
||||
|
||||
const ownedIds = Object.keys(save.collection).filter(id => save.collection[id] > 0);
|
||||
const cols = 6;
|
||||
const cardW = 130;
|
||||
const cardH = 90;
|
||||
const startX = 80;
|
||||
const startY = 145;
|
||||
const cardW = 195;
|
||||
const cardH = 135;
|
||||
const startX = 120;
|
||||
const startY = 180;
|
||||
|
||||
ownedIds.forEach((cardId, i) => {
|
||||
const card = cardManager.getCard(cardId);
|
||||
if (!card) return;
|
||||
const col = i % cols;
|
||||
const row = Math.floor(i / cols);
|
||||
const x = startX + col * (cardW + 8);
|
||||
const y = startY + row * (cardH + 8);
|
||||
if (y > 635) return; // clip
|
||||
const x = startX + col * (cardW + 12);
|
||||
const y = startY + row * (cardH + 12);
|
||||
if (y > 920) return; // clip
|
||||
|
||||
const rarityColors = { common: 0x888888, rare: 0x4488ff, epic: 0xaa44ff, legendary: 0xffaa00 };
|
||||
const factionColors = { imperial: 0x2244aa, raider: 0xaa2222 };
|
||||
|
|
@ -167,39 +167,39 @@ export class DeckBuilderScene extends Phaser.Scene {
|
|||
});
|
||||
}
|
||||
|
||||
const nameT = this.add.text(x, y - 30, card.name, {
|
||||
fontSize: '9px', color: canAdd ? '#ffffff' : '#555555',
|
||||
wordWrap: { width: cardW - 6 }, align: 'center'
|
||||
const nameT = this.add.text(x, y - 45, card.name, {
|
||||
fontSize: '14px', color: canAdd ? '#ffffff' : '#555555',
|
||||
wordWrap: { width: cardW - 8 }, align: 'center'
|
||||
}).setOrigin(0.5);
|
||||
|
||||
const typeLabel = isCommander ? '[CMD]' : `[${card.rarity}]`;
|
||||
const typeT = this.add.text(x, y - 18, typeLabel, {
|
||||
fontSize: '8px', color: canAdd ? '#aaaaaa' : '#444444'
|
||||
const typeT = this.add.text(x, y - 28, typeLabel, {
|
||||
fontSize: '12px', color: canAdd ? '#aaaaaa' : '#444444'
|
||||
}).setOrigin(0.5);
|
||||
|
||||
const statsT = this.add.text(x, y - 4, `ATK:${card.attack} HP:${card.health} ARM:${card.armor}`, {
|
||||
fontSize: '9px', color: canAdd ? '#aaaaff' : '#444455'
|
||||
const statsT = this.add.text(x, y - 8, `ATK:${card.attack} HP:${card.health} ARM:${card.armor}`, {
|
||||
fontSize: '14px', color: canAdd ? '#aaaaff' : '#444455'
|
||||
}).setOrigin(0.5);
|
||||
|
||||
const inDeckCount = isCommander
|
||||
? (this.workingDeck.commander === cardId ? 1 : 0)
|
||||
: this.workingDeck.cards.filter(id => id === cardId).length;
|
||||
|
||||
const countT = this.add.text(x, y + 10, `Owned:${save.collection[cardId]} In deck:${inDeckCount}`, {
|
||||
fontSize: '8px', color: canAdd ? '#44ff44' : '#444444'
|
||||
const countT = this.add.text(x, y + 12, `Owned:${save.collection[cardId]} In deck:${inDeckCount}`, {
|
||||
fontSize: '12px', color: canAdd ? '#44ff44' : '#444444'
|
||||
}).setOrigin(0.5);
|
||||
|
||||
const skillT = this.add.text(x, y + 24, card.skills.map(s => s.name).join(', '), {
|
||||
fontSize: '8px', color: canAdd ? '#ffcc44' : '#443300',
|
||||
wordWrap: { width: cardW - 6 }, align: 'center'
|
||||
const skillT = this.add.text(x, y + 34, card.skills.map(s => s.name).join(', '), {
|
||||
fontSize: '12px', color: canAdd ? '#ffcc44' : '#443300',
|
||||
wordWrap: { width: cardW - 8 }, align: 'center'
|
||||
}).setOrigin(0.5);
|
||||
|
||||
this.cardListGroup.push(bg, nameT, typeT, statsT, countT, skillT);
|
||||
});
|
||||
|
||||
if (ownedIds.length === 0) {
|
||||
const t = this.add.text(420, 300, 'No cards in collection.\nVisit the Store to buy packs!', {
|
||||
fontSize: '16px', color: '#666666', align: 'center'
|
||||
const t = this.add.text(500, 450, 'No cards in collection.\nVisit the Store to buy packs!', {
|
||||
fontSize: '22px', color: '#666666', align: 'center'
|
||||
}).setOrigin(0.5);
|
||||
this.cardListGroup.push(t);
|
||||
}
|
||||
|
|
@ -207,10 +207,10 @@ export class DeckBuilderScene extends Phaser.Scene {
|
|||
|
||||
_makeButtons() {
|
||||
// Save deck button
|
||||
const saveBtn = this.add.rectangle(1050, 640, 150, 40, 0x224422)
|
||||
const saveBtn = this.add.rectangle(1550, 930, 200, 50, 0x224422)
|
||||
.setInteractive({ useHandCursor: true })
|
||||
.setStrokeStyle(2, 0x44aa44);
|
||||
this.add.text(1050, 640, 'Save Deck', { fontSize: '16px', color: '#ffffff' }).setOrigin(0.5);
|
||||
this.add.text(1550, 930, 'Save Deck', { fontSize: '20px', color: '#ffffff' }).setOrigin(0.5);
|
||||
saveBtn.on('pointerdown', () => {
|
||||
const save = this.registry.get('save');
|
||||
const validation = DeckManager.validate(this.workingDeck, this.registry.get('cardManager'));
|
||||
|
|
@ -224,10 +224,10 @@ export class DeckBuilderScene extends Phaser.Scene {
|
|||
});
|
||||
|
||||
// Clear deck button
|
||||
const clearBtn = this.add.rectangle(1050, 690, 150, 35, 0x442222)
|
||||
const clearBtn = this.add.rectangle(1550, 990, 200, 45, 0x442222)
|
||||
.setInteractive({ useHandCursor: true })
|
||||
.setStrokeStyle(2, 0xaa4444);
|
||||
this.add.text(1050, 690, 'Clear Deck', { fontSize: '14px', color: '#ffffff' }).setOrigin(0.5);
|
||||
this.add.text(1550, 990, 'Clear Deck', { fontSize: '18px', color: '#ffffff' }).setOrigin(0.5);
|
||||
clearBtn.on('pointerdown', () => {
|
||||
this.workingDeck.commander = null;
|
||||
this.workingDeck.cards = [];
|
||||
|
|
@ -238,15 +238,15 @@ export class DeckBuilderScene extends Phaser.Scene {
|
|||
|
||||
_showMsg(msg, color) {
|
||||
const { width } = this.scale;
|
||||
const t = this.add.text(width / 2, 580, msg, { fontSize: '14px', color }).setOrigin(0.5);
|
||||
const t = this.add.text(width / 2, 860, msg, { fontSize: '18px', color }).setOrigin(0.5);
|
||||
this.time.delayedCall(2000, () => t.destroy());
|
||||
}
|
||||
|
||||
_makeBackButton() {
|
||||
const bg = this.add.rectangle(80, 700, 120, 35, 0x333333)
|
||||
const bg = this.add.rectangle(80, 1040, 160, 45, 0x333333)
|
||||
.setInteractive({ useHandCursor: true })
|
||||
.setStrokeStyle(1, 0x888888);
|
||||
this.add.text(80, 700, 'Back', { fontSize: '14px', color: '#ffffff' }).setOrigin(0.5);
|
||||
this.add.text(80, 1040, 'Back', { fontSize: '18px', color: '#ffffff' }).setOrigin(0.5);
|
||||
bg.on('pointerdown', () => this.scene.start('MainMenuScene'));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,21 +12,21 @@ export class FusionScene extends Phaser.Scene {
|
|||
create() {
|
||||
const { width, height } = this.scale;
|
||||
this.add.rectangle(width / 2, height / 2, width, height, 0x0d1b2a);
|
||||
this.add.text(width / 2, 35, 'Fusion Lab', { fontSize: '28px', color: '#d4af37' }).setOrigin(0.5);
|
||||
this.add.text(width / 2, 68, 'Combine 3 copies of a card to upgrade it to the next rarity tier', {
|
||||
fontSize: '13px', color: '#888888'
|
||||
this.add.text(width / 2, 40, 'Fusion Lab', { fontSize: '38px', color: '#d4af37' }).setOrigin(0.5);
|
||||
this.add.text(width / 2, 80, 'Combine 3 copies of a card to upgrade it to the next rarity tier', {
|
||||
fontSize: '18px', color: '#888888'
|
||||
}).setOrigin(0.5);
|
||||
|
||||
// Recipe reference
|
||||
const recipeX = width - 200;
|
||||
this.add.text(recipeX, 120, 'Fusion Recipes', { fontSize: '14px', color: '#d4af37' }).setOrigin(0.5);
|
||||
const recipeX = width - 300;
|
||||
this.add.text(recipeX, 155, 'Fusion Recipes', { fontSize: '20px', color: '#d4af37' }).setOrigin(0.5);
|
||||
const recipeLines = [
|
||||
'3x Common -> 1x Rare',
|
||||
'3x Rare -> 1x Epic',
|
||||
'3x Epic -> 1x Legendary'
|
||||
];
|
||||
recipeLines.forEach((line, i) => {
|
||||
this.add.text(recipeX, 145 + i * 20, line, { fontSize: '12px', color: '#aaaaaa' }).setOrigin(0.5);
|
||||
this.add.text(recipeX, 195 + i * 28, line, { fontSize: '16px', color: '#aaaaaa' }).setOrigin(0.5);
|
||||
});
|
||||
|
||||
this.listGroup = [];
|
||||
|
|
@ -49,13 +49,13 @@ export class FusionScene extends Phaser.Scene {
|
|||
});
|
||||
|
||||
if (fusibleCards.length === 0) {
|
||||
const t = this.add.text(width / 2, 360, 'No cards available for fusion.\nCollect 3 or more copies of the same card to fuse them.', {
|
||||
fontSize: '16px', color: '#888888', align: 'center'
|
||||
const t = this.add.text(width / 2, 480, 'No cards available for fusion.\nCollect 3 or more copies of the same card to fuse them.', {
|
||||
fontSize: '22px', color: '#888888', align: 'center'
|
||||
}).setOrigin(0.5);
|
||||
this.listGroup.push(t);
|
||||
|
||||
// Show progress toward next fusion
|
||||
this.add.text(width / 2, 460, 'Your collection progress:', { fontSize: '14px', color: '#666666' }).setOrigin(0.5);
|
||||
this.add.text(width / 2, 610, 'Your collection progress:', { fontSize: '18px', color: '#666666' }).setOrigin(0.5);
|
||||
const nearFusion = Object.entries(save.collection)
|
||||
.filter(([id, count]) => {
|
||||
const card = cardManager.getCard(id);
|
||||
|
|
@ -68,9 +68,9 @@ export class FusionScene extends Phaser.Scene {
|
|||
const card = cardManager.getCard(id);
|
||||
if (!card) return;
|
||||
const recipe = FUSION_RECIPES[card.rarity];
|
||||
const t2 = this.add.text(width / 2, 490 + i * 24,
|
||||
const t2 = this.add.text(width / 2, 650 + i * 30,
|
||||
`${card.name} (${card.rarity}): ${count}/${recipe.count}`,
|
||||
{ fontSize: '13px', color: '#555555' }
|
||||
{ fontSize: '18px', color: '#555555' }
|
||||
).setOrigin(0.5);
|
||||
this.listGroup.push(t2);
|
||||
});
|
||||
|
|
@ -81,37 +81,37 @@ export class FusionScene extends Phaser.Scene {
|
|||
const card = cardManager.getCard(cardId);
|
||||
const recipe = FUSION_RECIPES[card.rarity];
|
||||
const cols = 4;
|
||||
const x = 180 + (i % cols) * 260;
|
||||
const y = 210 + Math.floor(i / cols) * 200;
|
||||
const x = 270 + (i % cols) * 380;
|
||||
const y = 280 + Math.floor(i / cols) * 265;
|
||||
|
||||
if (y > 640) return;
|
||||
if (y > 940) return;
|
||||
|
||||
const rarityColors = { common: 0x888888, rare: 0x4488ff, epic: 0xaa44ff, legendary: 0xffaa00 };
|
||||
const factionColors = { imperial: 0x2244aa, raider: 0xaa2222 };
|
||||
|
||||
const bg = this.add.rectangle(x, y, 230, 150, factionColors[card.faction] || 0x1a3a5c)
|
||||
const bg = this.add.rectangle(x, y, 340, 230, factionColors[card.faction] || 0x1a3a5c)
|
||||
.setStrokeStyle(2, rarityColors[card.rarity] || 0x888888);
|
||||
|
||||
const nameT = this.add.text(x, y - 55, card.name, {
|
||||
fontSize: '14px', color: '#ffffff', wordWrap: { width: 220 }, align: 'center'
|
||||
const nameT = this.add.text(x, y - 85, card.name, {
|
||||
fontSize: '20px', color: '#ffffff', wordWrap: { width: 320 }, align: 'center'
|
||||
}).setOrigin(0.5);
|
||||
const rarityT = this.add.text(x, y - 36, card.rarity.toUpperCase(), {
|
||||
fontSize: '11px', color: '#' + (rarityColors[card.rarity] || 0x888888).toString(16).padStart(6, '0')
|
||||
const rarityT = this.add.text(x, y - 56, card.rarity.toUpperCase(), {
|
||||
fontSize: '16px', color: '#' + (rarityColors[card.rarity] || 0x888888).toString(16).padStart(6, '0')
|
||||
}).setOrigin(0.5);
|
||||
const statsT = this.add.text(x, y - 20, `ATK:${card.attack} HP:${card.health} ARM:${card.armor}`, {
|
||||
fontSize: '11px', color: '#aaaaff'
|
||||
const statsT = this.add.text(x, y - 32, `ATK:${card.attack} HP:${card.health} ARM:${card.armor}`, {
|
||||
fontSize: '16px', color: '#aaaaff'
|
||||
}).setOrigin(0.5);
|
||||
const countT = this.add.text(x, y - 4, `You have: ${count} (need ${recipe.count})`, {
|
||||
fontSize: '11px', color: '#44ff44'
|
||||
const countT = this.add.text(x, y - 8, `You have: ${count} (need ${recipe.count})`, {
|
||||
fontSize: '16px', color: '#44ff44'
|
||||
}).setOrigin(0.5);
|
||||
const resultT = this.add.text(x, y + 14, `Result: ${recipe.result_rarity.toUpperCase()}`, {
|
||||
fontSize: '13px', color: '#ffd700'
|
||||
const resultT = this.add.text(x, y + 18, `Result: ${recipe.result_rarity.toUpperCase()}`, {
|
||||
fontSize: '18px', color: '#ffd700'
|
||||
}).setOrigin(0.5);
|
||||
|
||||
const fuseBtn = this.add.rectangle(x, y + 48, 160, 36, 0x553300)
|
||||
const fuseBtn = this.add.rectangle(x, y + 68, 220, 48, 0x553300)
|
||||
.setInteractive({ useHandCursor: true })
|
||||
.setStrokeStyle(2, 0xffaa00);
|
||||
const fuseTxt = this.add.text(x, y + 48, 'FUSE (3x)', { fontSize: '14px', color: '#ffd700' }).setOrigin(0.5);
|
||||
const fuseTxt = this.add.text(x, y + 68, 'FUSE (3x)', { fontSize: '18px', color: '#ffd700' }).setOrigin(0.5);
|
||||
|
||||
fuseBtn.on('pointerover', () => fuseBtn.setFillStyle(0x775500));
|
||||
fuseBtn.on('pointerout', () => fuseBtn.setFillStyle(0x553300));
|
||||
|
|
@ -161,7 +161,7 @@ export class FusionScene extends Phaser.Scene {
|
|||
_showMsg(msg, color) {
|
||||
const { width } = this.scale;
|
||||
if (this._msgText) this._msgText.destroy();
|
||||
this._msgText = this.add.text(width / 2, 660, msg, { fontSize: '18px', color }).setOrigin(0.5);
|
||||
this._msgText = this.add.text(width / 2, 960, msg, { fontSize: '24px', color }).setOrigin(0.5);
|
||||
this.time.delayedCall(2500, () => {
|
||||
if (this._msgText) this._msgText.destroy();
|
||||
this._msgText = null;
|
||||
|
|
@ -169,10 +169,10 @@ export class FusionScene extends Phaser.Scene {
|
|||
}
|
||||
|
||||
_makeBackButton() {
|
||||
const bg = this.add.rectangle(80, 700, 120, 35, 0x333333)
|
||||
const bg = this.add.rectangle(80, 1040, 160, 45, 0x333333)
|
||||
.setInteractive({ useHandCursor: true })
|
||||
.setStrokeStyle(1, 0x888888);
|
||||
this.add.text(80, 700, 'Back', { fontSize: '14px', color: '#ffffff' }).setOrigin(0.5);
|
||||
this.add.text(80, 1040, 'Back', { fontSize: '18px', color: '#ffffff' }).setOrigin(0.5);
|
||||
bg.on('pointerdown', () => this.scene.start('MainMenuScene'));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,25 +12,25 @@ export class MainMenuScene extends Phaser.Scene {
|
|||
this.add.rectangle(width / 2, height / 2, width, 2, 0x1a3a5c).setAlpha(0.5);
|
||||
|
||||
// Title
|
||||
this.add.text(width / 2, 100, "TYRANT'S EDGE", {
|
||||
fontSize: '48px',
|
||||
this.add.text(width / 2, 120, "TYRANT'S EDGE", {
|
||||
fontSize: '72px',
|
||||
color: '#d4af37',
|
||||
stroke: '#000000',
|
||||
strokeThickness: 4
|
||||
strokeThickness: 6
|
||||
}).setOrigin(0.5);
|
||||
|
||||
this.add.text(width / 2, 155, 'A Collectible Card Game', {
|
||||
fontSize: '18px', color: '#888888'
|
||||
this.add.text(width / 2, 190, 'A Collectible Card Game', {
|
||||
fontSize: '24px', color: '#888888'
|
||||
}).setOrigin(0.5);
|
||||
|
||||
// Gold display
|
||||
this.goldText = this.add.text(width - 20, 20, `Gold: ${save.gold}`, {
|
||||
fontSize: '18px', color: '#ffd700'
|
||||
this.goldText = this.add.text(width - 30, 30, `Gold: ${save.gold}`, {
|
||||
fontSize: '24px', color: '#ffd700'
|
||||
}).setOrigin(1, 0);
|
||||
|
||||
// Version
|
||||
this.add.text(20, height - 20, 'v1.0', {
|
||||
fontSize: '12px', color: '#444444'
|
||||
fontSize: '14px', color: '#444444'
|
||||
}).setOrigin(0, 1);
|
||||
|
||||
const buttons = [
|
||||
|
|
@ -42,20 +42,23 @@ export class MainMenuScene extends Phaser.Scene {
|
|||
{ label: 'Fusion Lab', scene: 'FusionScene' }
|
||||
];
|
||||
|
||||
const startY = 310;
|
||||
const spacing = 90;
|
||||
|
||||
buttons.forEach((btn, i) => {
|
||||
this._makeButton(width / 2, 250 + i * 70, btn.label, () => {
|
||||
this._makeButton(width / 2, startY + i * spacing, btn.label, () => {
|
||||
this.scene.start(btn.scene, btn.data || {});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
_makeButton(x, y, label, callback) {
|
||||
const bg = this.add.rectangle(x, y, 280, 50, 0x1a3a5c)
|
||||
const bg = this.add.rectangle(x, y, 400, 65, 0x1a3a5c)
|
||||
.setInteractive({ useHandCursor: true })
|
||||
.setStrokeStyle(2, 0x4488ff);
|
||||
|
||||
const text = this.add.text(x, y, label, {
|
||||
fontSize: '20px', color: '#ffffff'
|
||||
fontSize: '28px', color: '#ffffff'
|
||||
}).setOrigin(0.5);
|
||||
|
||||
bg.on('pointerover', () => bg.setFillStyle(0x2a5a8c));
|
||||
|
|
|
|||
|
|
@ -9,19 +9,19 @@ export class StoreScene extends Phaser.Scene {
|
|||
const packManager = this.registry.get('packManager');
|
||||
|
||||
this.add.rectangle(width / 2, height / 2, width, height, 0x0d1b2a);
|
||||
this.add.text(width / 2, 40, 'Card Store', { fontSize: '32px', color: '#d4af37' }).setOrigin(0.5);
|
||||
this.add.text(width / 2, 75, 'Buy packs to expand your collection', {
|
||||
fontSize: '14px', color: '#888888'
|
||||
this.add.text(width / 2, 45, 'Card Store', { fontSize: '42px', color: '#d4af37' }).setOrigin(0.5);
|
||||
this.add.text(width / 2, 90, 'Buy packs to expand your collection', {
|
||||
fontSize: '20px', color: '#888888'
|
||||
}).setOrigin(0.5);
|
||||
|
||||
this.goldText = this.add.text(width - 20, 20, `Gold: ${save.gold}`, {
|
||||
fontSize: '18px', color: '#ffd700'
|
||||
fontSize: '24px', color: '#ffd700'
|
||||
}).setOrigin(1, 0);
|
||||
|
||||
const packs = packManager.getAllPacks();
|
||||
packs.forEach((pack, i) => {
|
||||
const x = width / 2 + (i - packs.length / 2 + 0.5) * 300;
|
||||
this._renderPack(pack, x, 280);
|
||||
const x = width / 2 + (i - packs.length / 2 + 0.5) * 440;
|
||||
this._renderPack(pack, x, 350);
|
||||
});
|
||||
|
||||
this.revealArea = [];
|
||||
|
|
@ -30,29 +30,29 @@ export class StoreScene extends Phaser.Scene {
|
|||
|
||||
_renderPack(pack, x, y) {
|
||||
// Pack visual
|
||||
const packBg = this.add.rectangle(x, y, 260, 220, 0x1a3a5c)
|
||||
const packBg = this.add.rectangle(x, y, 380, 340, 0x1a3a5c)
|
||||
.setStrokeStyle(3, 0xffd700);
|
||||
|
||||
// Pack art (procedural)
|
||||
this.add.rectangle(x, y - 40, 120, 100, 0x0d2240).setStrokeStyle(1, 0x4488ff);
|
||||
this.add.text(x, y - 40, 'PACK', { fontSize: '28px', color: '#4488ff' }).setOrigin(0.5);
|
||||
this.add.rectangle(x, y - 60, 180, 150, 0x0d2240).setStrokeStyle(1, 0x4488ff);
|
||||
this.add.text(x, y - 60, 'PACK', { fontSize: '38px', color: '#4488ff' }).setOrigin(0.5);
|
||||
|
||||
this.add.text(x, y + 30, pack.name, { fontSize: '18px', color: '#ffffff' }).setOrigin(0.5);
|
||||
this.add.text(x, y + 52, `${pack.cardCount} cards per pack`, { fontSize: '13px', color: '#aaaaaa' }).setOrigin(0.5);
|
||||
this.add.text(x, y + 70, `Cost: ${pack.cost} Gold`, { fontSize: '15px', color: '#ffd700' }).setOrigin(0.5);
|
||||
this.add.text(x, y + 45, pack.name, { fontSize: '24px', color: '#ffffff' }).setOrigin(0.5);
|
||||
this.add.text(x, y + 75, `${pack.cardCount} cards per pack`, { fontSize: '18px', color: '#aaaaaa' }).setOrigin(0.5);
|
||||
this.add.text(x, y + 100, `Cost: ${pack.cost} Gold`, { fontSize: '20px', color: '#ffd700' }).setOrigin(0.5);
|
||||
|
||||
// Drop rates display
|
||||
const rateLines = Object.entries(pack.dropRates).map(([r, v]) =>
|
||||
`${r.charAt(0).toUpperCase() + r.slice(1)}: ${Math.floor(v * 100)}%`
|
||||
).join(' ');
|
||||
this.add.text(x, y + 90, rateLines, {
|
||||
fontSize: '10px', color: '#888888', wordWrap: { width: 240 }, align: 'center'
|
||||
this.add.text(x, y + 128, rateLines, {
|
||||
fontSize: '14px', color: '#888888', wordWrap: { width: 360 }, align: 'center'
|
||||
}).setOrigin(0.5);
|
||||
|
||||
const buyBtn = this.add.rectangle(x, y + 120, 180, 45, 0x224422)
|
||||
const buyBtn = this.add.rectangle(x, y + 170, 240, 55, 0x224422)
|
||||
.setInteractive({ useHandCursor: true })
|
||||
.setStrokeStyle(2, 0x44aa44);
|
||||
const buyTxt = this.add.text(x, y + 120, 'Buy Pack', { fontSize: '18px', color: '#ffffff' }).setOrigin(0.5);
|
||||
const buyTxt = this.add.text(x, y + 170, 'Buy Pack', { fontSize: '22px', color: '#ffffff' }).setOrigin(0.5);
|
||||
|
||||
buyBtn.on('pointerover', () => buyBtn.setFillStyle(0x336633));
|
||||
buyBtn.on('pointerout', () => buyBtn.setFillStyle(0x224422));
|
||||
|
|
@ -83,40 +83,40 @@ export class StoreScene extends Phaser.Scene {
|
|||
this.revealArea = [];
|
||||
|
||||
// Show revealed cards with animation
|
||||
const totalW = cards.length * 200 + (cards.length - 1) * 20;
|
||||
const startX = width / 2 - totalW / 2 + 90;
|
||||
const totalW = cards.length * 300 + (cards.length - 1) * 20;
|
||||
const startX = width / 2 - totalW / 2 + 140;
|
||||
const rarityColors = { common: 0x888888, rare: 0x4488ff, epic: 0xaa44ff, legendary: 0xffaa00 };
|
||||
const factionColors = { imperial: 0x2244aa, raider: 0xaa2222 };
|
||||
|
||||
// Label
|
||||
const label = this.add.text(width / 2, 510, 'You received:', {
|
||||
fontSize: '16px', color: '#ffffff'
|
||||
const label = this.add.text(width / 2, 700, 'You received:', {
|
||||
fontSize: '22px', color: '#ffffff'
|
||||
}).setOrigin(0.5).setAlpha(0);
|
||||
this.revealArea.push(label);
|
||||
this.tweens.add({ targets: label, alpha: 1, duration: 300 });
|
||||
|
||||
cards.forEach((card, i) => {
|
||||
const x = startX + i * 220;
|
||||
const y = 610;
|
||||
const x = startX + i * 320;
|
||||
const y = 860;
|
||||
|
||||
const bg = this.add.rectangle(x, y, 190, 140, factionColors[card.faction] || 0x1a3a5c)
|
||||
const bg = this.add.rectangle(x, y, 280, 210, factionColors[card.faction] || 0x1a3a5c)
|
||||
.setStrokeStyle(3, rarityColors[card.rarity] || 0x888888)
|
||||
.setAlpha(0);
|
||||
const nameT = this.add.text(x, y - 45, card.name, {
|
||||
fontSize: '14px', color: '#ffffff', wordWrap: { width: 180 }, align: 'center'
|
||||
const nameT = this.add.text(x, y - 70, card.name, {
|
||||
fontSize: '20px', color: '#ffffff', wordWrap: { width: 260 }, align: 'center'
|
||||
}).setOrigin(0.5).setAlpha(0);
|
||||
const rarityT = this.add.text(x, y - 18, card.rarity.toUpperCase(), {
|
||||
fontSize: '12px',
|
||||
const rarityT = this.add.text(x, y - 30, card.rarity.toUpperCase(), {
|
||||
fontSize: '16px',
|
||||
color: '#' + (rarityColors[card.rarity] || 0x888888).toString(16).padStart(6, '0')
|
||||
}).setOrigin(0.5).setAlpha(0);
|
||||
const statsT = this.add.text(x, y + 4, `ATK:${card.attack} HP:${card.health} ARM:${card.armor}`, {
|
||||
fontSize: '12px', color: '#aaaaff'
|
||||
fontSize: '16px', color: '#aaaaff'
|
||||
}).setOrigin(0.5).setAlpha(0);
|
||||
const skillT = this.add.text(x, y + 24, card.skills.map(s => s.name).join(', '), {
|
||||
fontSize: '10px', color: '#ffcc44'
|
||||
const skillT = this.add.text(x, y + 34, card.skills.map(s => s.name).join(', '), {
|
||||
fontSize: '14px', color: '#ffcc44'
|
||||
}).setOrigin(0.5).setAlpha(0);
|
||||
const newT = this.add.text(x, y + 45, (save.collection[card.id] || 0) === 1 ? 'NEW!' : `x${save.collection[card.id]}`, {
|
||||
fontSize: '12px', color: (save.collection[card.id] || 0) === 1 ? '#44ff44' : '#aaaaaa'
|
||||
const newT = this.add.text(x, y + 65, (save.collection[card.id] || 0) === 1 ? 'NEW!' : `x${save.collection[card.id]}`, {
|
||||
fontSize: '16px', color: (save.collection[card.id] || 0) === 1 ? '#44ff44' : '#aaaaaa'
|
||||
}).setOrigin(0.5).setAlpha(0);
|
||||
|
||||
this.revealArea.push(bg, nameT, rarityT, statsT, skillT, newT);
|
||||
|
|
@ -131,15 +131,15 @@ export class StoreScene extends Phaser.Scene {
|
|||
|
||||
_showMsg(msg, color) {
|
||||
const { width } = this.scale;
|
||||
const t = this.add.text(width / 2, 470, msg, { fontSize: '18px', color }).setOrigin(0.5);
|
||||
const t = this.add.text(width / 2, 660, msg, { fontSize: '24px', color }).setOrigin(0.5);
|
||||
this.time.delayedCall(2000, () => t.destroy());
|
||||
}
|
||||
|
||||
_makeBackButton() {
|
||||
const bg = this.add.rectangle(80, 700, 120, 35, 0x333333)
|
||||
const bg = this.add.rectangle(80, 1040, 160, 45, 0x333333)
|
||||
.setInteractive({ useHandCursor: true })
|
||||
.setStrokeStyle(1, 0x888888);
|
||||
this.add.text(80, 700, 'Back', { fontSize: '14px', color: '#ffffff' }).setOrigin(0.5);
|
||||
this.add.text(80, 1040, 'Back', { fontSize: '18px', color: '#ffffff' }).setOrigin(0.5);
|
||||
bg.on('pointerdown', () => this.scene.start('MainMenuScene'));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue