feat: card portrait images loaded from public/cards/
No files matched your search
|
After Width: | Height: | Size: 3.6 MiB |
|
After Width: | Height: | Size: 3.2 MiB |
|
After Width: | Height: | Size: 3.4 MiB |
|
After Width: | Height: | Size: 3.3 MiB |
|
After Width: | Height: | Size: 3.6 MiB |
|
After Width: | Height: | Size: 3.4 MiB |
|
After Width: | Height: | Size: 3.5 MiB |
|
After Width: | Height: | Size: 3.6 MiB |
|
After Width: | Height: | Size: 3.6 MiB |
|
After Width: | Height: | Size: 3.7 MiB |
|
After Width: | Height: | Size: 3.5 MiB |
|
After Width: | Height: | Size: 3.3 MiB |
@@ -31,55 +31,70 @@ export class CardObject extends Phaser.GameObjects.Container {
|
||||
}
|
||||
|
||||
_build() {
|
||||
const teamHex = this.cardData.teamColor?.hex ?? 0x888888
|
||||
const teamHex = this.cardData.teamColor?.hex ?? 0x888888
|
||||
const textureKey = `card-${this.cardData.id}`
|
||||
const hasTexture = this.scene.textures.exists(textureKey)
|
||||
|
||||
// --- Fond de carte avec couleur d'équipe ---
|
||||
// --- Ombre portée ---
|
||||
const shadow = this.scene.add.graphics()
|
||||
shadow.fillStyle(0x000000, 0.45)
|
||||
shadow.fillRoundedRect(-CARD_W / 2 + 4, -CARD_H / 2 + 4, CARD_W, CARD_H, RADIUS)
|
||||
this.add(shadow)
|
||||
|
||||
// --- Fond coloré équipe ---
|
||||
const bg = this.scene.add.graphics()
|
||||
// Ombre portée
|
||||
bg.fillStyle(0x000000, 0.4)
|
||||
bg.fillRoundedRect(4, 4, CARD_W, CARD_H, RADIUS)
|
||||
// Corps principal (couleur équipe assombrie)
|
||||
bg.fillStyle(this._darken(teamHex, 0.3))
|
||||
bg.fillRoundedRect(0, 0, CARD_W, CARD_H, RADIUS)
|
||||
// Bordure couleur équipe
|
||||
bg.lineStyle(2, teamHex, 1)
|
||||
bg.strokeRoundedRect(0, 0, CARD_W, CARD_H, RADIUS)
|
||||
// Zone portrait (rectangle foncé central)
|
||||
bg.fillStyle(0x000000, 0.35)
|
||||
bg.fillRoundedRect(8, 28, CARD_W - 16, CARD_H - 80, 6)
|
||||
bg.x = -CARD_W / 2
|
||||
bg.y = -CARD_H / 2
|
||||
bg.fillStyle(this._darken(teamHex, 0.25))
|
||||
bg.fillRoundedRect(-CARD_W / 2, -CARD_H / 2, CARD_W, CARD_H, RADIUS)
|
||||
this.add(bg)
|
||||
|
||||
// --- Nom du personnage (haut, centré) ---
|
||||
const nameText = this.scene.add.text(0, -CARD_H / 2 + 9, this.cardData.name, {
|
||||
fontSize: '13px',
|
||||
// --- Image du personnage (zone portrait) ---
|
||||
if (hasTexture) {
|
||||
const img = this.scene.add.image(0, -10, textureKey)
|
||||
// Ajuster pour remplir la zone portrait en conservant le ratio
|
||||
const portraitH = CARD_H - 50 // laisser place au nom (haut) et badges (coins)
|
||||
const portraitW = CARD_W - 4
|
||||
img.setDisplaySize(portraitW, portraitH)
|
||||
img.setAlpha(0.92)
|
||||
// Masque arrondi via crop + clip (simulé via alpha sur les bords)
|
||||
this.add(img)
|
||||
|
||||
// Overlay dégradé en bas pour lisibilité du texte
|
||||
const overlay = this.scene.add.graphics()
|
||||
overlay.fillGradientStyle(0x000000, 0x000000, 0x000000, 0x000000, 0, 0, 0.7, 0.7)
|
||||
overlay.fillRect(-CARD_W / 2, CARD_H / 2 - 55, CARD_W, 55)
|
||||
this.add(overlay)
|
||||
} else {
|
||||
// Fallback : initiale si l'image n'est pas chargée
|
||||
const initial = this.scene.add.text(0, -10, this.cardData.name[0], {
|
||||
fontSize: '60px',
|
||||
fontFamily: 'Georgia, serif',
|
||||
color: '#ffffff',
|
||||
alpha: 0.12
|
||||
}).setOrigin(0.5, 0.5)
|
||||
this.add(initial)
|
||||
}
|
||||
|
||||
// --- Bordure couleur équipe ---
|
||||
const border = this.scene.add.graphics()
|
||||
border.lineStyle(2, teamHex, 1)
|
||||
border.strokeRoundedRect(-CARD_W / 2, -CARD_H / 2, CARD_W, CARD_H, RADIUS)
|
||||
this.add(border)
|
||||
|
||||
// --- Bandeau nom (haut) ---
|
||||
const nameBg = this.scene.add.graphics()
|
||||
nameBg.fillStyle(0x000000, 0.65)
|
||||
nameBg.fillRoundedRect(-CARD_W / 2, -CARD_H / 2, CARD_W, 24, { tl: RADIUS, tr: RADIUS, bl: 0, br: 0 })
|
||||
this.add(nameBg)
|
||||
|
||||
const nameText = this.scene.add.text(0, -CARD_H / 2 + 12, this.cardData.name, {
|
||||
fontSize: '12px',
|
||||
fontFamily: 'Georgia, serif',
|
||||
color: '#ffffff',
|
||||
color: '#f5e6c8',
|
||||
stroke: '#000000',
|
||||
strokeThickness: 3,
|
||||
strokeThickness: 2,
|
||||
fontStyle: 'bold'
|
||||
}).setOrigin(0.5, 0)
|
||||
this.add(nameText)
|
||||
|
||||
// --- Initiale du personnage (zone portrait) ---
|
||||
const initial = this.scene.add.text(0, -CARD_H / 2 + 75, this.cardData.name[0], {
|
||||
fontSize: '52px',
|
||||
fontFamily: 'Georgia, serif',
|
||||
color: '#ffffff',
|
||||
alpha: 0.15
|
||||
}).setOrigin(0.5, 0.5)
|
||||
this.add(initial)
|
||||
|
||||
// --- Description courte (bas de carte) ---
|
||||
const desc = this.scene.add.text(0, CARD_H / 2 - 22, this._shortDesc(), {
|
||||
fontSize: '8px',
|
||||
fontFamily: 'Georgia, serif',
|
||||
color: '#cccccc',
|
||||
wordWrap: { width: CARD_W - 16 },
|
||||
align: 'center'
|
||||
}).setOrigin(0.5, 1)
|
||||
this.add(desc)
|
||||
this.add(nameText)
|
||||
|
||||
// --- 4 badges d'attributs dans les coins ---
|
||||
this._addCornerBadge('food', -CARD_W / 2 + 2, -CARD_H / 2 + 2, 'topLeft')
|
||||
@@ -97,36 +112,31 @@ export class CardObject extends Phaser.GameObjects.Container {
|
||||
|
||||
/**
|
||||
* Badge carré coloré dans un coin, avec la valeur de l'attribut
|
||||
* @param {'food'|'war'|'health'|'diplo'} attr
|
||||
* @param {number} bx position x relative au centre du container (coin du badge)
|
||||
* @param {number} by position y relative au centre du container (coin du badge)
|
||||
* @param {'topLeft'|'topRight'|'bottomLeft'|'bottomRight'} corner
|
||||
*/
|
||||
_addCornerBadge(attr, bx, by, corner) {
|
||||
const { color, label } = ATTR[attr]
|
||||
const value = this.cardData[attr]
|
||||
const S = CORNER
|
||||
|
||||
// Calculer l'origine du rectangle selon le coin
|
||||
let rx = bx, ry = by
|
||||
if (corner === 'topRight' || corner === 'bottomRight') rx = bx - S
|
||||
if (corner === 'bottomLeft' || corner === 'bottomRight') ry = by - S
|
||||
if (corner === 'topRight' || corner === 'bottomRight') rx = bx - S
|
||||
if (corner === 'bottomLeft' || corner === 'bottomRight') ry = by - S
|
||||
|
||||
const g = this.scene.add.graphics()
|
||||
g.fillStyle(color, 0.9)
|
||||
// Coins arrondis uniquement vers l'intérieur de la carte
|
||||
const cornerRadii = {
|
||||
topLeft: { tl: RADIUS, tr: 2, bl: 2, br: 4 },
|
||||
topRight: { tl: 2, tr: RADIUS, bl: 4, br: 2 },
|
||||
bottomLeft: { tl: 2, tr: 4, bl: RADIUS, br: 2 },
|
||||
bottomRight: { tl: 4, tr: 2, bl: 2, br: RADIUS }
|
||||
}
|
||||
|
||||
const g = this.scene.add.graphics()
|
||||
g.fillStyle(color, 0.92)
|
||||
g.fillRoundedRect(rx, ry, S, S, cornerRadii[corner])
|
||||
this.add(g)
|
||||
|
||||
// Valeur (grand, centré dans le badge)
|
||||
const cx = rx + S / 2
|
||||
const cy = ry + S / 2 - 3
|
||||
|
||||
const valText = this.scene.add.text(cx, cy, String(value), {
|
||||
fontSize: '14px',
|
||||
fontFamily: 'Georgia, serif',
|
||||
@@ -137,12 +147,11 @@ export class CardObject extends Phaser.GameObjects.Container {
|
||||
}).setOrigin(0.5, 0.5)
|
||||
this.add(valText)
|
||||
|
||||
// Label abrégé (petit, en bas du badge)
|
||||
const lblText = this.scene.add.text(cx, cy + 8, label, {
|
||||
fontSize: '6px',
|
||||
fontFamily: 'Arial, sans-serif',
|
||||
color: '#ffffff',
|
||||
alpha: 0.85
|
||||
alpha: 0.9
|
||||
}).setOrigin(0.5, 0.5)
|
||||
this.add(lblText)
|
||||
}
|
||||
@@ -190,9 +199,4 @@ export class CardObject extends Phaser.GameObjects.Container {
|
||||
const b = Math.floor(( hex & 0xff) * factor)
|
||||
return (r << 16) | (g << 8) | b
|
||||
}
|
||||
|
||||
_shortDesc() {
|
||||
const d = this.cardData.description || ''
|
||||
return d.length > 60 ? d.substring(0, 57) + '…' : d
|
||||
}
|
||||
}
|
||||
@@ -1,29 +1,45 @@
|
||||
import Phaser from 'phaser'
|
||||
|
||||
// Correspondance id personnage → fichier image
|
||||
const CARD_IMAGES = [
|
||||
{ id: 'queen', file: 'reine.png' },
|
||||
{ id: 'prince', file: 'prince.png' },
|
||||
{ id: 'butcher', file: 'boucher.png' },
|
||||
{ id: 'doctor', file: 'medecin.png' },
|
||||
{ id: 'apothecary', file: 'apoticaire.png'},
|
||||
{ id: 'jester', file: 'fou_du_roi.png'},
|
||||
{ id: 'assassin', file: 'assassin.png' },
|
||||
{ id: 'peasant', file: 'paysan.png' },
|
||||
{ id: 'executioner', file: 'bourreau.png' },
|
||||
{ id: 'knight', file: 'chevalier.png' },
|
||||
{ id: 'merchant', file: 'marchand.png' },
|
||||
{ id: 'bishop', file: 'eveque.png' }
|
||||
]
|
||||
|
||||
export class BootScene extends Phaser.Scene {
|
||||
constructor() {
|
||||
super('BootScene')
|
||||
}
|
||||
|
||||
preload() {
|
||||
// Créer des textures procédurales pour les cartes
|
||||
const graphics = this.add.graphics()
|
||||
// Charger les images des 12 personnages
|
||||
CARD_IMAGES.forEach(({ id, file }) => {
|
||||
this.load.image(`card-${id}`, `/cards/${file}`)
|
||||
})
|
||||
|
||||
// Fond de carte
|
||||
graphics.fillStyle(0x2a1500)
|
||||
graphics.fillRoundedRect(0, 0, 120, 180, 10)
|
||||
graphics.lineStyle(2, 0xd4a732)
|
||||
graphics.strokeRoundedRect(0, 0, 120, 180, 10)
|
||||
graphics.generateTexture('card-back', 120, 180)
|
||||
graphics.clear()
|
||||
// Barre de chargement simple
|
||||
const { width, height } = this.scale
|
||||
const bar = this.add.graphics()
|
||||
|
||||
// Carte face
|
||||
graphics.fillStyle(0x3a2010)
|
||||
graphics.fillRoundedRect(0, 0, 120, 180, 10)
|
||||
graphics.lineStyle(2, 0xd4a732)
|
||||
graphics.strokeRoundedRect(0, 0, 120, 180, 10)
|
||||
graphics.generateTexture('card-front', 120, 180)
|
||||
graphics.destroy()
|
||||
this.load.on('progress', (v) => {
|
||||
bar.clear()
|
||||
bar.fillStyle(0x1a0a00)
|
||||
bar.fillRect(width / 2 - 150, height / 2 - 10, 300, 20)
|
||||
bar.fillStyle(0xd4a732)
|
||||
bar.fillRect(width / 2 - 150, height / 2 - 10, 300 * v, 20)
|
||||
})
|
||||
|
||||
this.load.on('complete', () => bar.destroy())
|
||||
}
|
||||
|
||||
create() {
|
||||
|
||||