124 lines
4.9 KiB
JavaScript
124 lines
4.9 KiB
JavaScript
import Phaser from 'phaser';
|
|
import { GAME_WIDTH, GAME_HEIGHT, WORLD_SCALE } from '../config.js';
|
|
import { playMenuMusic } from '../util/music.js';
|
|
|
|
// bg_main.png (the poster art) already has the "MONSTERPLEX" wordmark baked
|
|
// into its top-left corner, tilted a few degrees. logo.png is that same
|
|
// wordmark as a standalone transparent-background graphic, meant to sit
|
|
// exactly on top of the baked version so it can be the one animated
|
|
// element - but logo.png's own art has a slightly different built-in tilt
|
|
// than the baked version, so simply overlaying it flat left a visible
|
|
// double edge. The constants below come from fitting a similarity
|
|
// transform (rotation + uniform scale + translation) between the "M" and
|
|
// "X" letter centroids measured in each image - the exact position/scale/
|
|
// rotation that maps logo.png's wordmark onto bg_main's - expressed as
|
|
// fractions of bg_main.png's native 1672x941 pixels (position/width) or
|
|
// plain degrees (rotation) so they scale correctly regardless of
|
|
// WORLD_SCALE/canvas size.
|
|
const LOGO_CENTER_X_FRAC = 0.25205;
|
|
const LOGO_CENTER_Y_FRAC = 0.16541;
|
|
const LOGO_WIDTH_FRAC = 0.75215;
|
|
const LOGO_ASPECT = 752 / 1392; // logo.png's own native height/width
|
|
// Negative = counter-clockwise (Phaser's angle is positive-clockwise) -
|
|
// logo.png's own art is tilted ~2.4deg CCW already; the baked wordmark is
|
|
// tilted ~7.6deg CCW, so this is the remaining ~5.2deg to close the gap.
|
|
const LOGO_ROTATION_DEG = -8.22351;
|
|
|
|
// Centered horizontally on the 5-icon row (DRIVE/JUMP/etc, itself centered
|
|
// around x=878 of bg_main.png's 1672px width), vertically in the gap
|
|
// between the jumping bus's tires (bottom edge ~y=700) and that icon row's
|
|
// own top edge (~y=760).
|
|
const PROMPT_X_FRAC = 0.45511;
|
|
const PROMPT_Y_FRAC = 0.77077;
|
|
|
|
export default class IntroScene extends Phaser.Scene {
|
|
constructor() {
|
|
super('Intro');
|
|
}
|
|
|
|
create() {
|
|
this.cameras.main.setBackgroundColor('#8fc7e8');
|
|
playMenuMusic(this);
|
|
|
|
this.add.image(0, 0, 'bg_main').setOrigin(0, 0).setDisplaySize(GAME_WIDTH, GAME_HEIGHT);
|
|
|
|
this._buildLogo();
|
|
this._buildPrompt();
|
|
|
|
// No auto-advance timer - this screen now waits indefinitely for the
|
|
// player to actually press/tap.
|
|
this._advance = () => this.scene.start('MainMenu');
|
|
this.input.keyboard.once('keydown', this._advance);
|
|
this.input.once('pointerdown', this._advance);
|
|
}
|
|
|
|
// Layers logo.png exactly over the wordmark already painted into
|
|
// bg_main.png, then breathes it (grow/shrink, looping) so it's the one
|
|
// part of the poster that visibly moves.
|
|
_buildLogo() {
|
|
const logo = this.add.image(GAME_WIDTH * LOGO_CENTER_X_FRAC, GAME_HEIGHT * LOGO_CENTER_Y_FRAC, 'logo').setOrigin(0.5);
|
|
logo.setAngle(LOGO_ROTATION_DEG);
|
|
const targetWidth = GAME_WIDTH * LOGO_WIDTH_FRAC;
|
|
logo.setDisplaySize(targetWidth, targetWidth * LOGO_ASPECT);
|
|
|
|
// Starts at exactly this size (matching the baked wordmark), grows, then
|
|
// returns to it - never smaller, never settling at the grown size.
|
|
const baseScale = logo.scale;
|
|
this.tweens.add({
|
|
targets: logo,
|
|
scale: baseScale * 1.08,
|
|
duration: 1400,
|
|
yoyo: true,
|
|
repeat: -1,
|
|
ease: 'Sine.easeInOut',
|
|
});
|
|
}
|
|
|
|
// Phaser's Text only supports one stroke pass, so a black-then-white
|
|
// double outline (readable against literally any part of the busy poster
|
|
// art behind it) is built from two stacked Text objects at the same
|
|
// position: a white one (its stroke forms the outer ring) behind a
|
|
// slightly less-stroked black one (whose own stroke/fill covers the
|
|
// inner ring) - the exposed sliver of the white layer around the black
|
|
// one's edge is what reads as the white outline.
|
|
_buildPrompt() {
|
|
const x = GAME_WIDTH * PROMPT_X_FRAC;
|
|
const y = GAME_HEIGHT * PROMPT_Y_FRAC;
|
|
const text = 'Press any key or tap to continue';
|
|
// A thick outer stroke relative to font size was filling in letterform
|
|
// counters (the holes in a/e/o) on a bold monospace font, which read as
|
|
// "blobby" rather than crisp - thinner strokes on a slightly bigger font
|
|
// keep the outline readable without eating the letter shapes themselves.
|
|
const fontSize = `${20 * WORLD_SCALE}px`;
|
|
|
|
const outline = this.add.text(x, y, text, {
|
|
fontFamily: 'monospace',
|
|
fontSize,
|
|
fontStyle: 'bold',
|
|
color: '#ffffff',
|
|
stroke: '#ffffff',
|
|
strokeThickness: 5 * WORLD_SCALE,
|
|
}).setOrigin(0.5);
|
|
|
|
const fill = this.add.text(x, y, text, {
|
|
fontFamily: 'monospace',
|
|
fontSize,
|
|
fontStyle: 'bold',
|
|
color: '#000000',
|
|
stroke: '#000000',
|
|
strokeThickness: 2 * WORLD_SCALE,
|
|
}).setOrigin(0.5);
|
|
|
|
// Shallow range (was dipping to 0.3) - low enough to still read as a
|
|
// pulse, never low enough to actually wash the double-stroke out against
|
|
// the bright sky behind it.
|
|
this.tweens.add({
|
|
targets: [outline, fill],
|
|
alpha: { from: 1, to: 0.65 },
|
|
duration: 700,
|
|
yoyo: true,
|
|
repeat: -1,
|
|
});
|
|
}
|
|
}
|