import * as Phaser from "phaser"; import type { TrayItem } from "../logic/types"; import { colorToTextureKey } from "../logic/types"; import { GameConfig } from "../GameConfig"; /** * 底部篮子(Tray)渲染器 * 显示已收集的汽车,支持插入/消除动画 */ export class TrayRenderer { private scene: Phaser.Scene; private container!: Phaser.GameObjects.Container; private bgGraphics!: Phaser.GameObjects.Graphics; private sprites: Map = new Map(); // 布局参数 private trayX = 0; private trayY = 0; private trayWidth = 0; private trayHeight = 0; private slotWidth = 0; private capacity = 7; constructor(scene: Phaser.Scene) { this.scene = scene; } setup( areaRect: { x: number; y: number; width: number; height: number }, capacity: number, ): void { this.capacity = capacity; this.trayX = areaRect.x; this.trayY = areaRect.y; this.trayWidth = areaRect.width; this.trayHeight = areaRect.height; this.slotWidth = this.trayWidth / capacity; this.container = this.scene.add.container(0, 0); this.bgGraphics = this.scene.add.graphics(); this.drawBackground(); this.container.add(this.bgGraphics); } private drawBackground(): void { const g = this.bgGraphics; g.clear(); g.fillStyle(0x16213e, 0.9); g.fillRoundedRect(this.trayX, this.trayY, this.trayWidth, this.trayHeight, 12); g.lineStyle(1, 0x3d3d5c, 0.5); for (let i = 1; i < this.capacity; i++) { const x = this.trayX + i * this.slotWidth; g.beginPath(); g.moveTo(x, this.trayY + 8); g.lineTo(x, this.trayY + this.trayHeight - 8); g.strokePath(); } } getSlotWorldPos(index: number): { x: number; y: number } { return { x: this.trayX + index * this.slotWidth + this.slotWidth / 2, y: this.trayY + this.trayHeight / 2, }; } addItemSprite(item: TrayItem, index: number): Phaser.GameObjects.Image { const pos = this.getSlotWorldPos(index); const textureKey = colorToTextureKey(item.color); const targetSize = Math.min(this.slotWidth * 0.7, this.trayHeight * 0.7); const sprite = this.scene.add.image(pos.x, pos.y, textureKey); sprite.setOrigin(0.5); const scale = Math.min(targetSize / sprite.width, targetSize / sprite.height); sprite.setScale(scale); this.container.add(sprite); this.sprites.set(item.id, sprite); return sprite; } rearrangeItems(tray: TrayItem[], animate: boolean = true): void { for (let i = 0; i < tray.length; i++) { const item = tray[i]; const sprite = this.sprites.get(item.id); if (!sprite) continue; const pos = this.getSlotWorldPos(i); if (animate) { this.scene.tweens.add({ targets: sprite, x: pos.x, y: pos.y, duration: 200, ease: "Power2", }); } else { sprite.setPosition(pos.x, pos.y); } } } removeMatchedItems(matchedIds: string[], onComplete: () => void): void { let completed = 0; const total = matchedIds.length; for (const id of matchedIds) { const sprite = this.sprites.get(id); if (!sprite) { completed++; if (completed >= total) onComplete(); continue; } this.scene.tweens.add({ targets: sprite, scaleX: sprite.scaleX * 1.3, scaleY: sprite.scaleY * 1.3, alpha: 0, duration: GameConfig.MATCH_DURATION, ease: "Power2", onComplete: () => { sprite.destroy(); this.sprites.delete(id); completed++; if (completed >= total) onComplete(); }, }); } } shake(): void { const originalX = this.container.x; this.scene.tweens.add({ targets: this.container, x: originalX + 8, duration: 50, yoyo: true, repeat: 5, ease: "Sine.inOut", onComplete: () => { this.container.x = originalX; }, }); } destroy(): void { this.container?.destroy(); this.sprites.clear(); } }