125 lines
4.0 KiB
JavaScript
125 lines
4.0 KiB
JavaScript
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;
|
|
}
|