import * as Phaser from 'phaser'; import { GAME_WIDTH, GAME_HEIGHT } from '../config.js'; import { attachArcadeCurve } from './ArcadeCurvePipeline.js'; // Reusable "arcade cabinet" screen dressing: animated scanlines (GameObject- // based, works on any renderer) plus a real barrel-distorted curved-CRT look // (WebGL post-FX pipeline, see ArcadeCurvePipeline.js — Canvas gets a no-op // stub for that half, scanlines still show). Drop this onto any // arcade/console game scene. // // (src/games/balatro/BalatroCrtPipeline.js is a separate, single-consumer // WebGL CRT effect — not reused here, but this module follows the same // PostFXPipeline convention for its curvature half.) // // Usage: // this.crt = applyArcadeCRTOverlay(this, { accentTint: 0xff6b3a, curveAmount: 0.4 }); // 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, accentTint: 0xc8a84b, depth: { scan: 58 }, curveAmount: 0.35, curveBezelColor: 0x05060a, curveVignetteStrength: 0.35, curveSheenStrength: 0.08, curveAberration: 0.0035, ...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 curve = attachArcadeCurve(scene, { amount: opts.curveAmount, bezelColor: opts.curveBezelColor, vignetteStrength: opts.curveVignetteStrength, sheenStrength: opts.curveSheenStrength, aberration: opts.curveAberration, }); 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, }); curve.pulse(strength); }, setIntensity(patch = {}) { Object.assign(opts, patch); scan.setAlpha(opts.scanlineAlpha).setTint(opts.scanlineTint); curve.setUniforms({ amount: opts.curveAmount, bezelColor: opts.curveBezelColor, vignetteStrength: opts.curveVignetteStrength, sheenStrength: opts.curveSheenStrength, aberration: opts.curveAberration, }); }, destroy() { scanTween.stop(); if (flashTween) flashTween.stop(); scan.destroy(); curve.destroy(); }, }; return controller; }