import * as Phaser from 'phaser'; import { GAME_WIDTH, GAME_HEIGHT } from '../config.js'; // Reusable "arcade cabinet" screen dressing: animated scanlines + a vector // TV-style bevel/vignette. Drop this onto any arcade/console game scene. // // (src/games/balatro/BalatroCrtPipeline.js is a different, WebGL-shader-based // CRT effect that's single-consumer and Canvas-incompatible — not reused here; // this module is GameObject-based so it works on any renderer.) // // Usage: // this.crt = applyArcadeCRTOverlay(this, { accentTint: 0xff6b3a }); // this.events.once('shutdown', () => this.crt.destroy()); // this.crt.pulse(1.0, 400); // one-off impact flash // this.crt.setIntensity({ accentTint: C.accent }); // re-skin on palette change const SCAN_TEXTURE_KEY = 'arcade-crt-scan'; const SCAN_STRIP_HEIGHT = 12; const SCAN_LINE_HEIGHT = 6; function ensureScanTexture(scene) { if (scene.textures.exists(SCAN_TEXTURE_KEY)) return; const g = scene.make.graphics({ x: 0, y: 0, add: false }); g.fillStyle(0xffffff, 0.18); g.fillRect(0, 0, GAME_WIDTH, SCAN_LINE_HEIGHT); g.generateTexture(SCAN_TEXTURE_KEY, GAME_WIDTH, SCAN_STRIP_HEIGHT); g.destroy(); } export function applyArcadeCRTOverlay(scene, options = {}) { const opts = { scanlineAlpha: 0.65, scanlineTint: 0x00f0ff, scanlineSpeedMs: 9000, bevelThickness: 78, bevelColor: 0x05060a, bevelRadius: 32, vignetteStrength: 0.78, accentTint: 0xc8a84b, depth: { scan: 58, bevel: 59 }, ...options, }; ensureScanTexture(scene); const scan = scene.add .tileSprite(GAME_WIDTH / 2, GAME_HEIGHT / 2, GAME_WIDTH, GAME_HEIGHT, SCAN_TEXTURE_KEY) .setDepth(opts.depth.scan) .setAlpha(opts.scanlineAlpha) .setTint(opts.scanlineTint); scan.setBlendMode(Phaser.BlendModes.ADD); const scanTween = scene.tweens.add({ targets: scan, tilePositionY: GAME_HEIGHT, duration: opts.scanlineSpeedMs, repeat: -1, ease: 'Linear', }); const bevel = scene.add.graphics().setDepth(opts.depth.bevel); function drawVignette(g) { const corners = [ [0, 0], [GAME_WIDTH, 0], [0, GAME_HEIGHT], [GAME_WIDTH, GAME_HEIGHT], ]; for (const [cx, cy] of corners) { g.fillStyle(0x000000, opts.vignetteStrength * 0.42); g.fillCircle(cx, cy, 480); g.fillStyle(0x000000, opts.vignetteStrength * 0.3); g.fillCircle(cx, cy, 320); } const rings = 7; for (let i = 0; i < rings; i += 1) { const t = i / (rings - 1); const inset = opts.bevelThickness + t * 180; const alpha = opts.vignetteStrength * (1 - t) * 0.16; g.lineStyle(52, 0x000000, alpha); g.strokeRoundedRect(inset, inset, GAME_WIDTH - inset * 2, GAME_HEIGHT - inset * 2, opts.bevelRadius + 8); } } function drawBevel() { bevel.clear(); drawVignette(bevel); const inset = opts.bevelThickness / 2; bevel.lineStyle(opts.bevelThickness, opts.bevelColor, 1); bevel.strokeRoundedRect(inset, inset, GAME_WIDTH - opts.bevelThickness, GAME_HEIGHT - opts.bevelThickness, opts.bevelRadius); bevel.lineStyle(9, opts.accentTint, 0.65); bevel.strokeRoundedRect( opts.bevelThickness, opts.bevelThickness, GAME_WIDTH - opts.bevelThickness * 2, GAME_HEIGHT - opts.bevelThickness * 2, Math.max(0, opts.bevelRadius - 8), ); } drawBevel(); let flashTween = null; const controller = { pulse(strength = 0.5, durationMs = 150) { scene.cameras.main.shake(durationMs, 0.004 * strength); if (flashTween) flashTween.stop(); scan.setAlpha(Math.min(1, opts.scanlineAlpha + 0.5 * strength)); flashTween = scene.tweens.add({ targets: scan, alpha: opts.scanlineAlpha, duration: durationMs * 2, }); }, setIntensity(patch = {}) { Object.assign(opts, patch); scan.setAlpha(opts.scanlineAlpha).setTint(opts.scanlineTint); drawBevel(); }, destroy() { scanTween.stop(); if (flashTween) flashTween.stop(); scan.destroy(); bevel.destroy(); }, }; return controller; }