fix: clip card images via Canvas 2D in BootScene, no Phaser mask needed
This commit is contained in:
1 parent
4d09102464
commit
da06ecaf2d
2 files changed
+127
-103
No files matched your search
@@ -19,7 +19,6 @@ export class CardObject extends Phaser.GameObjects.Container {
|
|||||||
this.cardData = cardData
|
this.cardData = cardData
|
||||||
this._selected = false
|
this._selected = false
|
||||||
this._baseY = y
|
this._baseY = y
|
||||||
this._maskImg = null // référence au masque pour le détruire proprement
|
|
||||||
this._build()
|
this._build()
|
||||||
scene.add.existing(this)
|
scene.add.existing(this)
|
||||||
}
|
}
|
||||||
@@ -32,10 +31,7 @@ export class CardObject extends Phaser.GameObjects.Container {
|
|||||||
// ── 1. Ombre ─────────────────────────────────────────────────────────────
|
// ── 1. Ombre ─────────────────────────────────────────────────────────────
|
||||||
const shadow = this.scene.add.graphics()
|
const shadow = this.scene.add.graphics()
|
||||||
shadow.fillStyle(0x000000, 0.5)
|
shadow.fillStyle(0x000000, 0.5)
|
||||||
shadow.fillRoundedRect(
|
shadow.fillRoundedRect(-CARD_W / 2 + 5, -CARD_H / 2 + 5, CARD_W, CARD_H, RADIUS)
|
||||||
-CARD_W / 2 + 5, -CARD_H / 2 + 5,
|
|
||||||
CARD_W, CARD_H, RADIUS
|
|
||||||
)
|
|
||||||
this.add(shadow)
|
this.add(shadow)
|
||||||
|
|
||||||
// ── 2. Fond couleur équipe ────────────────────────────────────────────────
|
// ── 2. Fond couleur équipe ────────────────────────────────────────────────
|
||||||
@@ -44,55 +40,27 @@ export class CardObject extends Phaser.GameObjects.Container {
|
|||||||
bg.fillRoundedRect(-CARD_W / 2, -CARD_H / 2, CARD_W, CARD_H, RADIUS)
|
bg.fillRoundedRect(-CARD_W / 2, -CARD_H / 2, CARD_W, CARD_H, RADIUS)
|
||||||
this.add(bg)
|
this.add(bg)
|
||||||
|
|
||||||
// ── 3. Image portrait ─────────────────────────────────────────────────────
|
// ── 3. Image portrait (texture déjà clippée dans BootScene via canvas) ───
|
||||||
if (hasTexture) {
|
if (hasTexture) {
|
||||||
const frame = this.scene.textures.get(textureKey).getSourceImage()
|
// La texture est déjà aux bonnes dimensions et clippée en arrondi
|
||||||
const scaleX = CARD_W / frame.width
|
// → simple Image, pas de masque, suit le container nativement
|
||||||
const scaleY = CARD_H / frame.height
|
|
||||||
const scale = Math.max(scaleX, scaleY)
|
|
||||||
|
|
||||||
const img = this.scene.add.image(0, 0, textureKey)
|
const img = this.scene.add.image(0, 0, textureKey)
|
||||||
img.setScale(scale)
|
|
||||||
this.add(img)
|
this.add(img)
|
||||||
|
} else {
|
||||||
// Masque arrondi rendu dans une texture offline, puis appliqué comme BitmapMask
|
// Fallback : initiale
|
||||||
// Le BitmapMask suit le container automatiquement car on l'attache à un objet du container
|
const initial = this.scene.add.text(0, -10, this.cardData.name[0], {
|
||||||
const maskKey = `mask-card`
|
fontSize: '70px', fontFamily: 'Georgia', color: '#ffffff'
|
||||||
if (!this.scene.textures.exists(maskKey)) {
|
}).setOrigin(0.5).setAlpha(0.1)
|
||||||
const maskGfx = this.scene.make.graphics({ x: 0, y: 0, add: false })
|
this.add(initial)
|
||||||
maskGfx.fillStyle(0xffffff)
|
|
||||||
maskGfx.fillRoundedRect(0, 0, CARD_W, CARD_H, RADIUS)
|
|
||||||
maskGfx.generateTexture(maskKey, CARD_W, CARD_H)
|
|
||||||
maskGfx.destroy()
|
|
||||||
}
|
|
||||||
|
|
||||||
// L'image masque est ajoutée AU container → elle suit ses mouvements
|
|
||||||
this._maskImg = this.scene.add.image(0, 0, maskKey)
|
|
||||||
this._maskImg.setVisible(false)
|
|
||||||
this.add(this._maskImg)
|
|
||||||
|
|
||||||
img.setMask(this._maskImg.createBitmapMask())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── 4. Dégradé haut (lisibilité badges) ──────────────────────────────────
|
// ── 4. Bordure couleur équipe ─────────────────────────────────────────────
|
||||||
const gradT = this.scene.add.graphics()
|
|
||||||
gradT.fillGradientStyle(0x000000, 0x000000, 0x000000, 0x000000, 0.75, 0.75, 0, 0)
|
|
||||||
gradT.fillRect(-CARD_W / 2, -CARD_H / 2, CARD_W, BADGE + 10)
|
|
||||||
this.add(gradT)
|
|
||||||
|
|
||||||
// ── 5. Dégradé bas (lisibilité titre) ────────────────────────────────────
|
|
||||||
const gradB = this.scene.add.graphics()
|
|
||||||
gradB.fillGradientStyle(0x000000, 0x000000, 0x000000, 0x000000, 0, 0, 0.85, 0.85)
|
|
||||||
gradB.fillRect(-CARD_W / 2, CARD_H / 2 - TITLE_H - 12, CARD_W, TITLE_H + 12)
|
|
||||||
this.add(gradB)
|
|
||||||
|
|
||||||
// ── 6. Bordure couleur équipe ─────────────────────────────────────────────
|
|
||||||
const border = this.scene.add.graphics()
|
const border = this.scene.add.graphics()
|
||||||
border.lineStyle(2.5, teamHex, 1)
|
border.lineStyle(2.5, teamHex, 1)
|
||||||
border.strokeRoundedRect(-CARD_W / 2, -CARD_H / 2, CARD_W, CARD_H, RADIUS)
|
border.strokeRoundedRect(-CARD_W / 2, -CARD_H / 2, CARD_W, CARD_H, RADIUS)
|
||||||
this.add(border)
|
this.add(border)
|
||||||
|
|
||||||
// ── 7. Titre en BAS ───────────────────────────────────────────────────────
|
// ── 5. Titre en BAS ───────────────────────────────────────────────────────
|
||||||
this.add(this.scene.add.text(
|
this.add(this.scene.add.text(
|
||||||
0, CARD_H / 2 - TITLE_H / 2,
|
0, CARD_H / 2 - TITLE_H / 2,
|
||||||
this.cardData.name, {
|
this.cardData.name, {
|
||||||
@@ -105,13 +73,13 @@ export class CardObject extends Phaser.GameObjects.Container {
|
|||||||
}
|
}
|
||||||
).setOrigin(0.5, 0.5))
|
).setOrigin(0.5, 0.5))
|
||||||
|
|
||||||
// ── 8. Badges attributs dans les 4 coins ─────────────────────────────────
|
// ── 6. Badges attributs dans les 4 coins ─────────────────────────────────
|
||||||
this._addCornerBadge('food', -CARD_W / 2, -CARD_H / 2, 'topLeft')
|
this._addCornerBadge('food', -CARD_W / 2, -CARD_H / 2, 'topLeft')
|
||||||
this._addCornerBadge('war', CARD_W / 2, -CARD_H / 2, 'topRight')
|
this._addCornerBadge('war', CARD_W / 2, -CARD_H / 2, 'topRight')
|
||||||
this._addCornerBadge('health', -CARD_W / 2, CARD_H / 2, 'bottomLeft')
|
this._addCornerBadge('health', -CARD_W / 2, CARD_H / 2, 'bottomLeft')
|
||||||
this._addCornerBadge('diplo', CARD_W / 2, CARD_H / 2, 'bottomRight')
|
this._addCornerBadge('diplo', CARD_W / 2, CARD_H / 2, 'bottomRight')
|
||||||
|
|
||||||
// ── 9. Interactivité ──────────────────────────────────────────────────────
|
// ── 7. Interactivité ──────────────────────────────────────────────────────
|
||||||
this.setSize(CARD_W, CARD_H)
|
this.setSize(CARD_W, CARD_H)
|
||||||
this.setInteractive({ cursor: 'pointer' })
|
this.setInteractive({ cursor: 'pointer' })
|
||||||
this.on('pointerover', this._onHover, this)
|
this.on('pointerover', this._onHover, this)
|
||||||
@@ -122,18 +90,17 @@ export class CardObject extends Phaser.GameObjects.Container {
|
|||||||
_addCornerBadge(attr, bx, by, corner) {
|
_addCornerBadge(attr, bx, by, corner) {
|
||||||
const { color, label } = ATTR[attr]
|
const { color, label } = ATTR[attr]
|
||||||
const value = this.cardData[attr]
|
const value = this.cardData[attr]
|
||||||
const S = BADGE
|
const S = BADGE
|
||||||
|
|
||||||
let rx = bx
|
let rx = bx, ry = by
|
||||||
let ry = by
|
|
||||||
if (corner === 'topRight' || corner === 'bottomRight') rx = bx - S
|
if (corner === 'topRight' || corner === 'bottomRight') rx = bx - S
|
||||||
if (corner === 'bottomLeft' || corner === 'bottomRight') ry = by - S
|
if (corner === 'bottomLeft' || corner === 'bottomRight') ry = by - S
|
||||||
|
|
||||||
const cornerRadii = {
|
const cornerRadii = {
|
||||||
topLeft: { tl: RADIUS, tr: 2, bl: 2, br: RADIUS / 2 },
|
topLeft: { tl: RADIUS, tr: 2, bl: 2, br: RADIUS / 2 },
|
||||||
topRight: { tl: 2, tr: RADIUS, bl: RADIUS / 2, br: 2 },
|
topRight: { tl: 2, tr: RADIUS, bl: RADIUS / 2, br: 2 },
|
||||||
bottomLeft: { tl: 2, tr: RADIUS / 2, bl: RADIUS, br: 2 },
|
bottomLeft: { tl: 2, tr: RADIUS / 2, bl: RADIUS, br: 2 },
|
||||||
bottomRight: { tl: RADIUS / 2, tr: 2, bl: 2, br: RADIUS }
|
bottomRight: { tl: RADIUS / 2, tr: 2, bl: 2, br: RADIUS }
|
||||||
}
|
}
|
||||||
|
|
||||||
const g = this.scene.add.graphics()
|
const g = this.scene.add.graphics()
|
||||||
@@ -145,18 +112,12 @@ export class CardObject extends Phaser.GameObjects.Container {
|
|||||||
const cy = ry + S / 2 - 2
|
const cy = ry + S / 2 - 2
|
||||||
|
|
||||||
this.add(this.scene.add.text(cx, cy, String(value), {
|
this.add(this.scene.add.text(cx, cy, String(value), {
|
||||||
fontSize: '14px',
|
fontSize: '14px', fontFamily: 'Georgia, serif', fontStyle: 'bold',
|
||||||
fontFamily: 'Georgia, serif',
|
color: '#ffffff', stroke: '#000000', strokeThickness: 2
|
||||||
fontStyle: 'bold',
|
|
||||||
color: '#ffffff',
|
|
||||||
stroke: '#000000',
|
|
||||||
strokeThickness: 2
|
|
||||||
}).setOrigin(0.5, 0.5))
|
}).setOrigin(0.5, 0.5))
|
||||||
|
|
||||||
this.add(this.scene.add.text(cx, cy + 9, label, {
|
this.add(this.scene.add.text(cx, cy + 9, label, {
|
||||||
fontSize: '6px',
|
fontSize: '6px', fontFamily: 'Arial, sans-serif', color: '#ffffffdd'
|
||||||
fontFamily:'Arial, sans-serif',
|
|
||||||
color: '#ffffffdd'
|
|
||||||
}).setOrigin(0.5, 0.5))
|
}).setOrigin(0.5, 0.5))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -175,12 +136,8 @@ export class CardObject extends Phaser.GameObjects.Container {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_onDown() {
|
_onDown() {
|
||||||
if (this._selected) {
|
if (this._selected) { this.deselect() }
|
||||||
this.deselect()
|
else { this.select(); this.emit('select', this) }
|
||||||
} else {
|
|
||||||
this.select()
|
|
||||||
this.emit('select', this)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
select() {
|
select() {
|
||||||
@@ -195,15 +152,6 @@ export class CardObject extends Phaser.GameObjects.Container {
|
|||||||
this.scene.tweens.add({ targets: this, y: this._baseY, duration: 150, ease: 'Power2' })
|
this.scene.tweens.add({ targets: this, y: this._baseY, duration: 150, ease: 'Power2' })
|
||||||
}
|
}
|
||||||
|
|
||||||
destroy(fromScene) {
|
|
||||||
// Nettoyer le masque proprement
|
|
||||||
if (this._maskImg) {
|
|
||||||
this._maskImg.destroy()
|
|
||||||
this._maskImg = null
|
|
||||||
}
|
|
||||||
super.destroy(fromScene)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Utilitaires ───────────────────────────────────────────────────────────────
|
// ── Utilitaires ───────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
_darken(hex, factor) {
|
_darken(hex, factor) {
|
||||||
|
|||||||
@@ -1,48 +1,124 @@
|
|||||||
import Phaser from 'phaser'
|
import Phaser from 'phaser'
|
||||||
|
|
||||||
// Correspondance id personnage → fichier image
|
|
||||||
const CARD_IMAGES = [
|
const CARD_IMAGES = [
|
||||||
{ id: 'queen', file: 'reine.png' },
|
{ id: 'queen', file: 'reine.png' },
|
||||||
{ id: 'prince', file: 'prince.png' },
|
{ id: 'prince', file: 'prince.png' },
|
||||||
{ id: 'butcher', file: 'boucher.png' },
|
{ id: 'butcher', file: 'boucher.png' },
|
||||||
{ id: 'doctor', file: 'medecin.png' },
|
{ id: 'doctor', file: 'medecin.png' },
|
||||||
{ id: 'apothecary', file: 'apoticaire.png'},
|
{ id: 'apothecary', file: 'apoticaire.png' },
|
||||||
{ id: 'jester', file: 'fou_du_roi.png'},
|
{ id: 'jester', file: 'fou_du_roi.png' },
|
||||||
{ id: 'assassin', file: 'assassin.png' },
|
{ id: 'assassin', file: 'assassin.png' },
|
||||||
{ id: 'peasant', file: 'paysan.png' },
|
{ id: 'peasant', file: 'paysan.png' },
|
||||||
{ id: 'executioner', file: 'bourreau.png' },
|
{ id: 'executioner', file: 'bourreau.png' },
|
||||||
{ id: 'knight', file: 'chevalier.png' },
|
{ id: 'knight', file: 'chevalier.png' },
|
||||||
{ id: 'merchant', file: 'marchand.png' },
|
{ id: 'merchant', file: 'marchand.png' },
|
||||||
{ id: 'bishop', file: 'eveque.png' }
|
{ id: 'bishop', file: 'eveque.png' }
|
||||||
]
|
]
|
||||||
|
|
||||||
|
const CARD_W = 130
|
||||||
|
const CARD_H = 190
|
||||||
|
const RADIUS = 10
|
||||||
|
|
||||||
export class BootScene extends Phaser.Scene {
|
export class BootScene extends Phaser.Scene {
|
||||||
constructor() {
|
constructor() {
|
||||||
super('BootScene')
|
super('BootScene')
|
||||||
}
|
}
|
||||||
|
|
||||||
preload() {
|
preload() {
|
||||||
// Charger les images des 12 personnages
|
// Barre de chargement
|
||||||
CARD_IMAGES.forEach(({ id, file }) => {
|
|
||||||
this.load.image(`card-${id}`, `/cards/${file}`)
|
|
||||||
})
|
|
||||||
|
|
||||||
// Barre de chargement simple
|
|
||||||
const { width, height } = this.scale
|
const { width, height } = this.scale
|
||||||
const bar = this.add.graphics()
|
const barBg = this.add.graphics()
|
||||||
|
const barFill = this.add.graphics()
|
||||||
|
|
||||||
|
barBg.fillStyle(0x2a1500)
|
||||||
|
barBg.fillRect(width / 2 - 150, height / 2 - 10, 300, 20)
|
||||||
|
|
||||||
this.load.on('progress', (v) => {
|
this.load.on('progress', (v) => {
|
||||||
bar.clear()
|
barFill.clear()
|
||||||
bar.fillStyle(0x1a0a00)
|
barFill.fillStyle(0xd4a732)
|
||||||
bar.fillRect(width / 2 - 150, height / 2 - 10, 300, 20)
|
barFill.fillRect(width / 2 - 150, height / 2 - 10, 300 * v, 20)
|
||||||
bar.fillStyle(0xd4a732)
|
|
||||||
bar.fillRect(width / 2 - 150, height / 2 - 10, 300 * v, 20)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
this.load.on('complete', () => bar.destroy())
|
this.load.on('complete', () => {
|
||||||
|
barBg.destroy()
|
||||||
|
barFill.destroy()
|
||||||
|
})
|
||||||
|
|
||||||
|
// Charger les images brutes
|
||||||
|
CARD_IMAGES.forEach(({ id, file }) => {
|
||||||
|
this.load.image(`raw-${id}`, `/cards/${file}`)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
create() {
|
create() {
|
||||||
|
// Pour chaque personnage, générer une texture clippée via Canvas 2D
|
||||||
|
CARD_IMAGES.forEach(({ id }) => {
|
||||||
|
this._generateClippedTexture(id)
|
||||||
|
})
|
||||||
|
|
||||||
this.scene.start('GameScene')
|
this.scene.start('GameScene')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Crée une texture `card-{id}` de dimensions CARD_W × CARD_H
|
||||||
|
* avec l'image clippée en rectangle arrondi via Canvas 2D natif.
|
||||||
|
* Pas de masque Phaser, pas de RenderTexture — juste du canvas.
|
||||||
|
*/
|
||||||
|
_generateClippedTexture(id) {
|
||||||
|
const rawKey = `raw-${id}`
|
||||||
|
const outKey = `card-${id}`
|
||||||
|
|
||||||
|
if (!this.textures.exists(rawKey)) return
|
||||||
|
if (this.textures.exists(outKey)) return
|
||||||
|
|
||||||
|
const srcImage = this.textures.get(rawKey).getSourceImage()
|
||||||
|
|
||||||
|
// Créer un canvas offscreen
|
||||||
|
const canvas = document.createElement('canvas')
|
||||||
|
canvas.width = CARD_W
|
||||||
|
canvas.height = CARD_H
|
||||||
|
const ctx = canvas.getContext('2d')
|
||||||
|
|
||||||
|
// Chemin arrondi (clip)
|
||||||
|
ctx.beginPath()
|
||||||
|
ctx.moveTo(RADIUS, 0)
|
||||||
|
ctx.lineTo(CARD_W - RADIUS, 0)
|
||||||
|
ctx.quadraticCurveTo(CARD_W, 0, CARD_W, RADIUS)
|
||||||
|
ctx.lineTo(CARD_W, CARD_H - RADIUS)
|
||||||
|
ctx.quadraticCurveTo(CARD_W, CARD_H, CARD_W - RADIUS, CARD_H)
|
||||||
|
ctx.lineTo(RADIUS, CARD_H)
|
||||||
|
ctx.quadraticCurveTo(0, CARD_H, 0, CARD_H - RADIUS)
|
||||||
|
ctx.lineTo(0, RADIUS)
|
||||||
|
ctx.quadraticCurveTo(0, 0, RADIUS, 0)
|
||||||
|
ctx.closePath()
|
||||||
|
ctx.clip()
|
||||||
|
|
||||||
|
// Dessiner l'image en mode "cover" (ratio conservé, centré)
|
||||||
|
const scaleX = CARD_W / srcImage.width
|
||||||
|
const scaleY = CARD_H / srcImage.height
|
||||||
|
const scale = Math.max(scaleX, scaleY)
|
||||||
|
const dw = srcImage.width * scale
|
||||||
|
const dh = srcImage.height * scale
|
||||||
|
const ox = (CARD_W - dw) / 2
|
||||||
|
const oy = (CARD_H - dh) / 2
|
||||||
|
|
||||||
|
ctx.drawImage(srcImage, ox, oy, dw, dh)
|
||||||
|
|
||||||
|
// Dégradé haut (lisibilité badges)
|
||||||
|
const gTop = ctx.createLinearGradient(0, 0, 0, 45)
|
||||||
|
gTop.addColorStop(0, 'rgba(0,0,0,0.75)')
|
||||||
|
gTop.addColorStop(1, 'rgba(0,0,0,0)')
|
||||||
|
ctx.fillStyle = gTop
|
||||||
|
ctx.fillRect(0, 0, CARD_W, 45)
|
||||||
|
|
||||||
|
// Dégradé bas (lisibilité titre)
|
||||||
|
const gBot = ctx.createLinearGradient(0, CARD_H - 40, 0, CARD_H)
|
||||||
|
gBot.addColorStop(0, 'rgba(0,0,0,0)')
|
||||||
|
gBot.addColorStop(1, 'rgba(0,0,0,0.85)')
|
||||||
|
ctx.fillStyle = gBot
|
||||||
|
ctx.fillRect(0, CARD_H - 40, CARD_W, 40)
|
||||||
|
|
||||||
|
// Enregistrer comme texture Phaser
|
||||||
|
this.textures.addCanvas(outKey, canvas)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user