fix: card image cover ratio, title at bottom, badges above image
This commit is contained in:
1 parent
60b545c42d
commit
6e384d5985
1 file changed
+80
-76
@@ -1,6 +1,6 @@
|
||||
import Phaser from 'phaser'
|
||||
|
||||
// Couleurs des 4 attributs (identiques au serveur)
|
||||
// Couleurs des 4 attributs
|
||||
const ATTR = {
|
||||
food: { color: 0xf0c030, label: 'NOU' },
|
||||
war: { color: 0xdd2222, label: 'GUE' },
|
||||
@@ -8,101 +8,108 @@ const ATTR = {
|
||||
diplo: { color: 0x3399ee, label: 'DIP' }
|
||||
}
|
||||
|
||||
const CARD_W = 130
|
||||
const CARD_H = 190
|
||||
const CORNER = 28 // taille du badge de coin
|
||||
const RADIUS = 10
|
||||
const CARD_W = 130
|
||||
const CARD_H = 190
|
||||
const RADIUS = 10
|
||||
const BADGE = 30 // taille du badge de coin
|
||||
const TITLE_H = 26 // hauteur du bandeau titre en bas
|
||||
|
||||
export class CardObject extends Phaser.GameObjects.Container {
|
||||
/**
|
||||
* @param {Phaser.Scene} scene
|
||||
* @param {number} x
|
||||
* @param {number} y
|
||||
* @param {object} cardData - { id, name, food, war, health, diplo, teamColor: { hex } }
|
||||
*/
|
||||
constructor(scene, x, y, cardData) {
|
||||
super(scene, x, y)
|
||||
this.cardData = cardData
|
||||
this._selected = false
|
||||
this._baseY = y
|
||||
|
||||
this._build()
|
||||
scene.add.existing(this)
|
||||
}
|
||||
|
||||
_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)
|
||||
|
||||
// --- Ombre portée ---
|
||||
// ── 1. Ombre ─────────────────────────────────────────────────────────────
|
||||
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)
|
||||
shadow.fillStyle(0x000000, 0.5)
|
||||
shadow.fillRoundedRect(-CARD_W / 2 + 5, -CARD_H / 2 + 5, CARD_W, CARD_H, RADIUS)
|
||||
this.add(shadow)
|
||||
|
||||
// --- Fond coloré équipe ---
|
||||
// ── 2. Fond couleur équipe ────────────────────────────────────────────────
|
||||
const bg = this.scene.add.graphics()
|
||||
bg.fillStyle(this._darken(teamHex, 0.25))
|
||||
bg.fillStyle(this._darken(teamHex, 0.2))
|
||||
bg.fillRoundedRect(-CARD_W / 2, -CARD_H / 2, CARD_W, CARD_H, RADIUS)
|
||||
this.add(bg)
|
||||
|
||||
// --- Image du personnage (zone portrait) ---
|
||||
// ── 3. Image portrait (ratio conservé, recadré au centre) ─────────────────
|
||||
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)
|
||||
const img = this.scene.add.image(0, 0, textureKey)
|
||||
|
||||
// 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)
|
||||
// Calculer le scale pour couvrir toute la carte sans déformer (cover)
|
||||
const scaleX = CARD_W / img.width
|
||||
const scaleY = CARD_H / img.height
|
||||
const scale = Math.max(scaleX, scaleY)
|
||||
img.setScale(scale)
|
||||
|
||||
// Décaler légèrement vers le haut pour centrer le visage (haut du portrait)
|
||||
img.setY(-CARD_H * 0.05)
|
||||
img.setAlpha(0.9)
|
||||
|
||||
// Masque rectangulaire pour clipper l'image dans les bords de la carte
|
||||
const mask = this.scene.add.graphics()
|
||||
mask.fillStyle(0xffffff)
|
||||
mask.fillRoundedRect(
|
||||
this.x - CARD_W / 2,
|
||||
this.y - CARD_H / 2,
|
||||
CARD_W, CARD_H, RADIUS
|
||||
)
|
||||
img.setMask(mask.createGeometryMask())
|
||||
|
||||
this.add(img)
|
||||
} 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)
|
||||
// Fallback : initiale
|
||||
const initial = this.scene.add.text(0, 0, this.cardData.name[0], {
|
||||
fontSize: '70px', fontFamily: 'Georgia', color: '#ffffff', alpha: 0.1
|
||||
}).setOrigin(0.5)
|
||||
this.add(initial)
|
||||
}
|
||||
|
||||
// --- Bordure couleur équipe ---
|
||||
// ── 4. Overlay dégradé bas (pour lisibilité du titre) ────────────────────
|
||||
const gradBot = this.scene.add.graphics()
|
||||
gradBot.fillGradientStyle(0x000000, 0x000000, 0x000000, 0x000000, 0, 0, 0.85, 0.85)
|
||||
gradBot.fillRect(-CARD_W / 2, CARD_H / 2 - TITLE_H - 10, CARD_W, TITLE_H + 10)
|
||||
this.add(gradBot)
|
||||
|
||||
// ── 5. Overlay dégradé haut (pour lisibilité des badges) ─────────────────
|
||||
const gradTop = this.scene.add.graphics()
|
||||
gradTop.fillGradientStyle(0x000000, 0x000000, 0x000000, 0x000000, 0.7, 0.7, 0, 0)
|
||||
gradTop.fillRect(-CARD_W / 2, -CARD_H / 2, CARD_W, BADGE + 8)
|
||||
this.add(gradTop)
|
||||
|
||||
// ── 6. Bordure couleur équipe ─────────────────────────────────────────────
|
||||
const border = this.scene.add.graphics()
|
||||
border.lineStyle(2, teamHex, 1)
|
||||
border.lineStyle(2.5, 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',
|
||||
// ── 7. Titre en BAS ───────────────────────────────────────────────────────
|
||||
const nameText = this.scene.add.text(0, CARD_H / 2 - TITLE_H / 2, this.cardData.name, {
|
||||
fontSize: '13px',
|
||||
fontFamily: 'Georgia, serif',
|
||||
fontStyle: 'bold',
|
||||
color: '#f5e6c8',
|
||||
stroke: '#000000',
|
||||
strokeThickness: 2,
|
||||
fontStyle: 'bold'
|
||||
strokeThickness: 3
|
||||
}).setOrigin(0.5, 0.5)
|
||||
this.add(nameText)
|
||||
|
||||
// --- 4 badges d'attributs dans les coins ---
|
||||
this._addCornerBadge('food', -CARD_W / 2 + 2, -CARD_H / 2 + 2, 'topLeft')
|
||||
this._addCornerBadge('war', CARD_W / 2 - 2, -CARD_H / 2 + 2, 'topRight')
|
||||
this._addCornerBadge('health', -CARD_W / 2 + 2, CARD_H / 2 - 2, 'bottomLeft')
|
||||
this._addCornerBadge('diplo', CARD_W / 2 - 2, CARD_H / 2 - 2, 'bottomRight')
|
||||
// ── 8. Badges attributs dans les 4 coins (AU-DESSUS de l'image) ───────────
|
||||
this._addCornerBadge('food', -CARD_W / 2, -CARD_H / 2, 'topLeft')
|
||||
this._addCornerBadge('war', CARD_W / 2, -CARD_H / 2, 'topRight')
|
||||
this._addCornerBadge('health', -CARD_W / 2, CARD_H / 2, 'bottomLeft')
|
||||
this._addCornerBadge('diplo', CARD_W / 2, CARD_H / 2, 'bottomRight')
|
||||
|
||||
// --- Interactivité ---
|
||||
// ── 9. Interactivité ──────────────────────────────────────────────────────
|
||||
this.setSize(CARD_W, CARD_H)
|
||||
this.setInteractive({ cursor: 'pointer' })
|
||||
this.on('pointerover', this._onHover, this)
|
||||
@@ -110,53 +117,50 @@ export class CardObject extends Phaser.GameObjects.Container {
|
||||
this.on('pointerdown', this._onDown, this)
|
||||
}
|
||||
|
||||
/**
|
||||
* Badge carré coloré dans un coin, avec la valeur de l'attribut
|
||||
*/
|
||||
_addCornerBadge(attr, bx, by, corner) {
|
||||
const { color, label } = ATTR[attr]
|
||||
const value = this.cardData[attr]
|
||||
const S = CORNER
|
||||
const S = BADGE
|
||||
|
||||
// Position du carré selon le coin
|
||||
let rx = bx, ry = by
|
||||
if (corner === 'topRight' || corner === 'bottomRight') rx = bx - S
|
||||
if (corner === 'bottomLeft' || corner === 'bottomRight') ry = by - S
|
||||
|
||||
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 }
|
||||
topLeft: { tl: RADIUS, tr: 2, bl: 2, br: RADIUS / 2 },
|
||||
topRight: { tl: 2, tr: RADIUS, bl: RADIUS / 2, br: 2 },
|
||||
bottomLeft: { tl: 2, tr: RADIUS / 2, bl: RADIUS, br: 2 },
|
||||
bottomRight: { tl: RADIUS / 2, tr: 2, bl: 2, br: RADIUS }
|
||||
}
|
||||
|
||||
const g = this.scene.add.graphics()
|
||||
g.fillStyle(color, 0.92)
|
||||
g.fillStyle(color, 0.95)
|
||||
g.fillRoundedRect(rx, ry, S, S, cornerRadii[corner])
|
||||
this.add(g)
|
||||
|
||||
const cx = rx + S / 2
|
||||
const cy = ry + S / 2 - 3
|
||||
|
||||
const valText = this.scene.add.text(cx, cy, String(value), {
|
||||
// Valeur
|
||||
this.add(this.scene.add.text(cx, cy, String(value), {
|
||||
fontSize: '14px',
|
||||
fontFamily: 'Georgia, serif',
|
||||
fontStyle: 'bold',
|
||||
color: '#ffffff',
|
||||
stroke: '#000000',
|
||||
strokeThickness: 2
|
||||
}).setOrigin(0.5, 0.5)
|
||||
this.add(valText)
|
||||
}).setOrigin(0.5, 0.5))
|
||||
|
||||
const lblText = this.scene.add.text(cx, cy + 8, label, {
|
||||
// Label
|
||||
this.add(this.scene.add.text(cx, cy + 9, label, {
|
||||
fontSize: '6px',
|
||||
fontFamily: 'Arial, sans-serif',
|
||||
color: '#ffffff',
|
||||
alpha: 0.9
|
||||
}).setOrigin(0.5, 0.5)
|
||||
this.add(lblText)
|
||||
color: '#ffffffdd'
|
||||
}).setOrigin(0.5, 0.5))
|
||||
}
|
||||
|
||||
// --- Interactions ---
|
||||
// ── Interactions ─────────────────────────────────────────────────────────────
|
||||
|
||||
_onHover() {
|
||||
if (this._selected) return
|
||||
@@ -191,7 +195,7 @@ export class CardObject extends Phaser.GameObjects.Container {
|
||||
this.scene.tweens.add({ targets: this, y: this._baseY, duration: 150, ease: 'Power2' })
|
||||
}
|
||||
|
||||
// --- Utilitaires ---
|
||||
// ── Utilitaires ───────────────────────────────────────────────────────────────
|
||||
|
||||
_darken(hex, factor) {
|
||||
const r = Math.floor(((hex >> 16) & 0xff) * factor)
|
||||
|
||||
Reference in new issue
Block a user