Polish opponent select scene and restore category on return

- Replace flat title/subtitle pills with brass Plaque headers in OpponentSelectScene for visual consistency with the game menu
- Shift opponent scroll area down (OPP_SCROLL_TOP 155 -> 180, height 440 -> 415) to clear the subtitle plaque band
- Pass selected category back to GameMenu via scene.start data on Back so the active grid is restored instead of resetting to default
- Add GameMenuScene.init() to capture incoming category and reset stale instance props (_currentCategory, _gridAnim) since scene.start reuses the existing instance
- Guard fadeIn target list with .filter(Boolean) to skip any uncreated elements
This commit is contained in:
Brian Fertig 2026-08-31 22:21:03 -06:00
parent 74cd4df85b
commit 1298648e97
2 changed files with 38 additions and 10 deletions

View File

@ -27,6 +27,16 @@ const GRID_TRAVEL = GAME_WIDTH + 800;
export default class GameMenuScene extends Phaser.Scene { export default class GameMenuScene extends Phaser.Scene {
constructor() { super('GameMenu'); } constructor() { super('GameMenu'); }
init(data) {
// Set when coming back from the opponent picker — restore that category.
this._initialCategory = (data && data.category) || null;
// scene.start() reuses the existing scene instance, so instance props from
// the previous visit survive — reset them so this create() starts clean
// (otherwise showCategory()'s "same category" guard swallows the restore).
this._currentCategory = null;
this._gridAnim = null;
}
async create() { async create() {
playMenuMusic(); playMenuMusic();
const cx = GAME_WIDTH / 2; const cx = GAME_WIDTH / 2;
@ -121,9 +131,15 @@ export default class GameMenuScene extends Phaser.Scene {
this._backBtn = new Button(this, cx, GAME_HEIGHT - 60, 'Back', () => this.scene.start('Landing'), { variant: 'ghost' }); this._backBtn = new Button(this, cx, GAME_HEIGHT - 60, 'Back', () => this.scene.start('Landing'), { variant: 'ghost' });
// Returning from the opponent picker: restore the category that was
// active when the game was chosen — its grid flies back in from the right.
if (this._initialCategory) {
this.showCategory(this._initialCategory);
}
// The landing scene zooms the logo out before starting us, so fade the // The landing scene zooms the logo out before starting us, so fade the
// menu controls in to complete the handoff. // menu controls in to complete the handoff.
const fadeIn = [...Object.values(this._tabs), ...Object.values(this._tabIcons), this._hintText, this._backBtn]; const fadeIn = [...Object.values(this._tabs), ...Object.values(this._tabIcons), this._hintText, this._backBtn].filter(Boolean);
for (const obj of fadeIn) obj.setAlpha(0); for (const obj of fadeIn) obj.setAlpha(0);
this.tweens.add({ this.tweens.add({
targets: fadeIn, targets: fadeIn,

View File

@ -1,6 +1,7 @@
import * as Phaser from 'phaser'; import * as Phaser from 'phaser';
import { GAME_HEIGHT, GAME_WIDTH, COLORS } from '../config.js'; import { GAME_HEIGHT, GAME_WIDTH, COLORS } from '../config.js';
import { Button } from '../ui/Button.js'; import { Button } from '../ui/Button.js';
import { Plaque } from '../ui/Plaque.js';
import { playMenuMusic, stopMenuMusic, setMenuMusicVolume } from '../ui/MenuMusic.js'; import { playMenuMusic, stopMenuMusic, setMenuMusicVolume } from '../ui/MenuMusic.js';
import { enqueue as enqueueSpeech, resetQueue as resetSpeechQueue } from '../ui/SpeechQueue.js'; import { enqueue as enqueueSpeech, resetQueue as resetSpeechQueue } from '../ui/SpeechQueue.js';
@ -17,8 +18,8 @@ const CARD_TILE_GAP = 14;
// Opponent grid scroll area // Opponent grid scroll area
const OPP_SCROLL_W = 1780; const OPP_SCROLL_W = 1780;
const OPP_SCROLL_H = 440; const OPP_SCROLL_H = 415;
const OPP_SCROLL_TOP = 155; // top edge of scroll area const OPP_SCROLL_TOP = 180; // top edge of scroll area (below the subtitle plaque)
export default class OpponentSelectScene extends Phaser.Scene { export default class OpponentSelectScene extends Phaser.Scene {
constructor() { super('OpponentSelect'); } constructor() { super('OpponentSelect'); }
@ -58,21 +59,32 @@ export default class OpponentSelectScene extends Phaser.Scene {
this.add.image(cx, GAME_HEIGHT / 2, bgKey).setDisplaySize(GAME_WIDTH, GAME_HEIGHT).setDepth(-1); this.add.image(cx, GAME_HEIGHT / 2, bgKey).setDisplaySize(GAME_WIDTH, GAME_HEIGHT).setDepth(-1);
this.add.rectangle(cx, GAME_HEIGHT / 2, GAME_WIDTH, GAME_HEIGHT, 0x000000, 0.45).setDepth(-1); this.add.rectangle(cx, GAME_HEIGHT / 2, GAME_WIDTH, GAME_HEIGHT, 0x000000, 0.45).setDepth(-1);
const titleText = this.add.text(cx, 60, this.gameDef.name, { // Brass-plaque headers, same treatment as the game menu headings. The
// subtitle plaque sits in the band between the title plaque and the
// opponent list (OPP_SCROLL_TOP) — keep those in sync if you move either.
const TITLE_Y = 55;
const PLATE_GAP = 10;
const titleText = this.add.text(cx, TITLE_Y, this.gameDef.name, {
fontFamily: 'Righteous', fontFamily: 'Righteous',
fontSize: '52px', fontSize: '52px',
color: COLORS.textHex, color: COLORS.textHex,
}).setOrigin(0.5); }).setOrigin(0.5);
const titlePill = this.add.rectangle(cx, 60, titleText.width + 48, titleText.height + 20, 0x000000, 0.72); titleText.setLetterSpacing(2);
this.children.moveBelow(titlePill, titleText); titleText.setShadow(0, 4, 'rgba(0,0,0,0.9)', 8);
const titlePlate = new Plaque(this, titleText.width + 64, titleText.height + 28).setPosition(cx, TITLE_Y);
this.children.moveBelow(titlePlate, titleText);
const subtitleText = this.add.text(cx, 122, 'Choose your opponent', { const subtitleText = this.add.text(cx, 0, 'Choose your opponent', {
fontFamily: 'Righteous', fontFamily: 'Righteous',
fontSize: '36px', fontSize: '36px',
color: COLORS.mutedHex, color: COLORS.mutedHex,
}).setOrigin(0.5); }).setOrigin(0.5);
const subtitlePill = this.add.rectangle(cx, 122, subtitleText.width + 48, subtitleText.height + 20, 0x000000, 0.72); subtitleText.setShadow(0, 3, 'rgba(0,0,0,0.8)', 5);
this.children.moveBelow(subtitlePill, subtitleText); const subPlateH = subtitleText.height + 28;
const subtitleY = TITLE_Y + (titleText.height + 28) / 2 + PLATE_GAP + subPlateH / 2;
subtitleText.setY(subtitleY);
const subtitlePlate = new Plaque(this, subtitleText.width + 64, subPlateH, { radius: 16 }).setPosition(cx, subtitleY);
this.children.moveBelow(subtitlePlate, subtitleText);
let opponents = []; let opponents = [];
try { try {
@ -87,7 +99,7 @@ export default class OpponentSelectScene extends Phaser.Scene {
} }
const min = this.gameDef.minOpponents ?? 1; const min = this.gameDef.minOpponents ?? 1;
new Button(this, cx - 150, 1013, 'Back', () => this.scene.start('GameMenu'), { new Button(this, cx - 150, 1013, 'Back', () => this.scene.start('GameMenu', { category: this.gameDef.category }), {
variant: 'ghost', variant: 'ghost',
width: 280, width: 280,
}); });