import Phaser from 'phaser' import { CARD_W, CARD_H, RADIUS } from '../objects/cardLayout' import { ITEM_CARD_W, ITEM_IMAGE_H, ITEM_RADIUS } from '../objects/itemLayout' import { ITEMS } from '../items' import { BUILDINGS } from '../buildings' import { EVENTS } from '../events' 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() { // Barre de chargement const { width, height } = this.scale 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) => { barFill.clear() barFill.fillStyle(0xd4a732) barFill.fillRect(width / 2 - 150, height / 2 - 10, 300 * v, 20) }) this.load.on('complete', () => { barBg.destroy() barFill.destroy() }) // Charger les images brutes des personnages CARD_IMAGES.forEach(({ id, file }) => { this.load.image(`raw-${id}`, `/cards/${file}`) }) // Charger les images brutes des objets ITEMS.forEach(({ id, image }) => { this.load.image(`raw-item-${id}`, `/items/${image}`) }) // Charger les images brutes des bâtiments BUILDINGS.forEach(({ id, image }) => { this.load.image(`raw-building-${id}`, `/buildings/${image}`) }) // Charger les images brutes des cartes Événement EVENTS.forEach(({ id, image }) => { this.load.image(`raw-event-${id}`, `/events/${image}`) }) } create() { // Pour chaque personnage, générer une texture clippée via Canvas 2D CARD_IMAGES.forEach(({ id }) => { this._generateClippedTexture(`raw-${id}`, `card-${id}`) }) // Pour chaque objet, générer une texture portrait (zone haute uniquement, // le bas de la carte objet est réservé au texte d'effet côté ItemObject) ITEMS.forEach(({ id }) => { this._generatePortraitTexture(`raw-item-${id}`, `item-${id}`) }) // Pour chaque bâtiment, même traitement (même layout de carte que les objets) BUILDINGS.forEach(({ id }) => { this._generatePortraitTexture(`raw-building-${id}`, `building-${id}`) }) // Pour chaque carte Événement, texture plein cadre (mêmes proportions // que les cartes personnage) — la mécanique diffère mais le rendu visuel // se veut cohérent avec le style épuré de CardObject. EVENTS.forEach(({ id }) => { this._generateClippedTexture(`raw-event-${id}`, `event-${id}`) }) this.scene.start('GameScene') } /** * Crée une texture `outKey` 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). Réutilisée pour les personnages ET les cartes * Événement, qui partagent le même format "portrait plein cadre". */ _generateClippedTexture(rawKey, outKey) { 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) } /** * Crée une texture `outKey` de dimensions ITEM_CARD_W × ITEM_IMAGE_H * (uniquement la zone portrait en haut d'une carte objet/bâtiment, coins * arrondis en haut seulement — le bas de la carte est une zone de texte * gérée par ItemBoardToken, pas par cette texture). Réutilisée pour les * objets ET les bâtiments, qui partagent le même layout de carte. */ _generatePortraitTexture(rawKey, outKey) { if (!this.textures.exists(rawKey)) return if (this.textures.exists(outKey)) return const srcImage = this.textures.get(rawKey).getSourceImage() const w = ITEM_CARD_W const h = ITEM_IMAGE_H const r = ITEM_RADIUS const canvas = document.createElement('canvas') canvas.width = w canvas.height = h const ctx = canvas.getContext('2d') // Chemin arrondi seulement en haut (bas droit, ce sera raccordé à la // zone de texte qui suit immédiatement en dessous dans ItemObject) ctx.beginPath() ctx.moveTo(r, 0) ctx.lineTo(w - r, 0) ctx.quadraticCurveTo(w, 0, w, r) ctx.lineTo(w, h) ctx.lineTo(0, h) ctx.lineTo(0, r) ctx.quadraticCurveTo(0, 0, r, 0) ctx.closePath() ctx.clip() // Dessiner l'image en mode "cover" (ratio conservé, centré) const scaleX = w / srcImage.width const scaleY = h / srcImage.height const scale = Math.max(scaleX, scaleY) const dw = srcImage.width * scale const dh = srcImage.height * scale const ox = (w - dw) / 2 const oy = (h - dh) / 2 ctx.drawImage(srcImage, ox, oy, dw, dh) // Léger dégradé bas pour raccorder proprement à la zone de texte const gBot = ctx.createLinearGradient(0, h - 24, 0, h) gBot.addColorStop(0, 'rgba(0,0,0,0)') gBot.addColorStop(1, 'rgba(0,0,0,0.55)') ctx.fillStyle = gBot ctx.fillRect(0, h - 24, w, 24) this.textures.addCanvas(outKey, canvas) } }