feat(SCOPONE-0010): redesign main menu layout
This commit is contained in:
@@ -2,12 +2,7 @@ import Phaser from 'phaser';
|
||||
import { BootScene } from './scenes/BootScene';
|
||||
import { MenuScene } from './scenes/MenuScene';
|
||||
import { GameScene } from './scenes/GameScene';
|
||||
|
||||
class SettingsScene extends Phaser.Scene {
|
||||
constructor() {
|
||||
super({ key: 'SettingsScene' });
|
||||
}
|
||||
}
|
||||
import { SettingsScene } from './scenes/SettingsScene';
|
||||
|
||||
const installFullscreenRequest = (host: HTMLElement): void => {
|
||||
const canRequestFullscreen =
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import Phaser from 'phaser';
|
||||
import { Difficulty } from '../game/types';
|
||||
import { GameSceneData, loadAudioPreferences } from '../game/preferences';
|
||||
import { SettingsScene } from './SettingsScene';
|
||||
|
||||
type MenuButtonPalette = {
|
||||
base: number;
|
||||
@@ -20,6 +19,62 @@ type RuleSection = {
|
||||
lines: string[];
|
||||
};
|
||||
|
||||
type PanelBounds = {
|
||||
x: number;
|
||||
y: number;
|
||||
width: number;
|
||||
height: number;
|
||||
centerX: number;
|
||||
centerY: number;
|
||||
right: number;
|
||||
bottom: number;
|
||||
};
|
||||
|
||||
type DecorativeCardLayout = {
|
||||
x: number;
|
||||
y: number;
|
||||
angle: number;
|
||||
scale: number;
|
||||
alpha: number;
|
||||
};
|
||||
|
||||
type MenuLayout = {
|
||||
frameInset: number;
|
||||
isCompactViewport: boolean;
|
||||
cameraZoom: number;
|
||||
visibleBounds: PanelBounds;
|
||||
titleX: number;
|
||||
titleY: number;
|
||||
titleFontSize: number;
|
||||
subtitleY: number;
|
||||
subtitleFontSize: number;
|
||||
subtitleWrapWidth: number;
|
||||
panelPaddingX: number;
|
||||
panelPaddingY: number;
|
||||
panelTitleFontSize: number;
|
||||
rulesHeadingFontSize: number;
|
||||
rulesBodyFontSize: number;
|
||||
rulesLineSpacing: number;
|
||||
rulesLineGap: number;
|
||||
rulesSectionGap: number;
|
||||
rulesWrapWidth: number;
|
||||
footerFontSize: number;
|
||||
controlIntroFontSize: number;
|
||||
controlIntroWrapWidth: number;
|
||||
buttonWidth: number;
|
||||
buttonHeight: number;
|
||||
buttonGap: number;
|
||||
buttonTextWrapWidth: number;
|
||||
buttonLabelFontSize: number;
|
||||
buttonSubtitleFontSize: number;
|
||||
buttonLabelOffset: number;
|
||||
buttonSubtitleOffset: number;
|
||||
showButtonSubtitle: boolean;
|
||||
rulesPanel: PanelBounds;
|
||||
controlPanel: PanelBounds;
|
||||
decorativeCards: DecorativeCardLayout[];
|
||||
};
|
||||
|
||||
const TITLE_STYLE: Phaser.Types.GameObjects.Text.TextStyle = {
|
||||
fontFamily: 'Georgia, serif',
|
||||
fontSize: '52px',
|
||||
@@ -52,107 +107,335 @@ export class MenuScene extends Phaser.Scene {
|
||||
const width = this.scale.width;
|
||||
const height = this.scale.height;
|
||||
const audioPreferences = loadAudioPreferences();
|
||||
const layout = this.createLayout(width, height);
|
||||
|
||||
this.drawBackground(width, height);
|
||||
this.drawDecorativeCards(width, height);
|
||||
this.ensureSettingsSceneAvailable();
|
||||
this.cameras.main.setZoom(layout.cameraZoom);
|
||||
this.cameras.main.centerOn(width / 2, height / 2);
|
||||
|
||||
this.add.text(width / 2, 92, 'Scopone Scientifico', TITLE_STYLE).setOrigin(0.5);
|
||||
this.add.text(width / 2, 142, 'Due squadre da due, una mano intera e lettura del tavolo fino all’ultimo punto.', {
|
||||
fontFamily: 'Georgia, serif',
|
||||
fontSize: '21px',
|
||||
color: '#d9f2d2',
|
||||
resolution: 2,
|
||||
this.drawBackground(width, height, layout);
|
||||
this.drawDecorativeCards(layout);
|
||||
|
||||
this.add.text(layout.titleX, layout.titleY, 'Scopone Scientifico', {
|
||||
...TITLE_STYLE,
|
||||
fontSize: `${layout.titleFontSize}px`,
|
||||
}).setOrigin(0.5);
|
||||
|
||||
this.createRulesPanel(width, height);
|
||||
this.createControlPanel(width, height, audioPreferences);
|
||||
if (!layout.isCompactViewport) {
|
||||
this.add.text(layout.titleX, layout.subtitleY, 'Due squadre da due, una mano intera e lettura del tavolo fino all’ultimo punto.', {
|
||||
fontFamily: 'Georgia, serif',
|
||||
fontSize: `${layout.subtitleFontSize}px`,
|
||||
color: '#d9f2d2',
|
||||
resolution: 2,
|
||||
align: 'center',
|
||||
wordWrap: { width: layout.subtitleWrapWidth },
|
||||
}).setOrigin(0.5, 0);
|
||||
}
|
||||
|
||||
this.createRulesPanel(layout);
|
||||
this.createControlPanel(layout, audioPreferences);
|
||||
}
|
||||
|
||||
private drawBackground(width: number, height: number): void {
|
||||
private createLayout(width: number, height: number): MenuLayout {
|
||||
const viewportWidth = this.scale.parentSize.width || width;
|
||||
const viewportHeight = this.scale.parentSize.height || height;
|
||||
const displayWidth = this.scale.displaySize.width || width;
|
||||
const displayHeight = this.scale.displaySize.height || height;
|
||||
const viewportAspect = viewportWidth / Math.max(viewportHeight, 1);
|
||||
const displayScale = Math.min(displayWidth / width, displayHeight / height);
|
||||
const isCompactViewport = viewportWidth <= 720 || viewportAspect < 1;
|
||||
const cameraZoom = isCompactViewport
|
||||
? Phaser.Math.Clamp(0.43 / Math.max(displayScale, 0.001), 1.38, 1.46)
|
||||
: 1;
|
||||
const visibleBounds = this.createPanelBounds(
|
||||
(width - width / cameraZoom) / 2,
|
||||
(height - height / cameraZoom) / 2,
|
||||
width / cameraZoom,
|
||||
height / cameraZoom,
|
||||
);
|
||||
|
||||
if (isCompactViewport) {
|
||||
const frameInset = 22;
|
||||
const panelGap = 12;
|
||||
const panelPaddingX = 18;
|
||||
const panelPaddingY = 14;
|
||||
const contentWidth = visibleBounds.width - frameInset * 2;
|
||||
const contentTop = visibleBounds.y + 74;
|
||||
const contentBottom = visibleBounds.bottom - 14;
|
||||
const contentHeight = contentBottom - contentTop;
|
||||
const rulesHeight = Math.round(contentHeight * 0.25);
|
||||
const controlHeight = contentHeight - rulesHeight - panelGap;
|
||||
const rulesPanel = this.createPanelBounds(
|
||||
visibleBounds.x + frameInset,
|
||||
contentTop,
|
||||
contentWidth,
|
||||
rulesHeight,
|
||||
);
|
||||
const controlPanel = this.createPanelBounds(
|
||||
visibleBounds.x + frameInset,
|
||||
rulesPanel.bottom + panelGap,
|
||||
contentWidth,
|
||||
controlHeight,
|
||||
);
|
||||
|
||||
return {
|
||||
frameInset,
|
||||
isCompactViewport,
|
||||
cameraZoom,
|
||||
visibleBounds,
|
||||
titleX: width / 2,
|
||||
titleY: visibleBounds.y + 28,
|
||||
titleFontSize: 34,
|
||||
subtitleY: visibleBounds.y + 56,
|
||||
subtitleFontSize: 18,
|
||||
subtitleWrapWidth: contentWidth - 32,
|
||||
panelPaddingX,
|
||||
panelPaddingY,
|
||||
panelTitleFontSize: 19,
|
||||
rulesHeadingFontSize: 14,
|
||||
rulesBodyFontSize: 15,
|
||||
rulesLineSpacing: 2,
|
||||
rulesLineGap: 6,
|
||||
rulesSectionGap: 8,
|
||||
rulesWrapWidth: rulesPanel.width - panelPaddingX * 2,
|
||||
footerFontSize: 12,
|
||||
controlIntroFontSize: 13,
|
||||
controlIntroWrapWidth: controlPanel.width - panelPaddingX * 2,
|
||||
buttonWidth: controlPanel.width - panelPaddingX * 2,
|
||||
buttonHeight: 38,
|
||||
buttonGap: 6,
|
||||
buttonTextWrapWidth: controlPanel.width - panelPaddingX * 2 - 24,
|
||||
buttonLabelFontSize: 20,
|
||||
buttonSubtitleFontSize: 12,
|
||||
buttonLabelOffset: 0,
|
||||
buttonSubtitleOffset: 0,
|
||||
showButtonSubtitle: false,
|
||||
rulesPanel,
|
||||
controlPanel,
|
||||
decorativeCards: [],
|
||||
};
|
||||
}
|
||||
|
||||
const frameInset = 34;
|
||||
const panelGap = 34;
|
||||
const contentTop = 186;
|
||||
const contentBottom = height - 48;
|
||||
const contentHeight = contentBottom - contentTop;
|
||||
const contentWidth = width - frameInset * 2;
|
||||
const rulesWidth = Math.round(contentWidth * 0.57);
|
||||
const controlWidth = contentWidth - rulesWidth - panelGap;
|
||||
const panelPaddingX = 34;
|
||||
const panelPaddingY = 28;
|
||||
const rulesPanel = this.createPanelBounds(frameInset, contentTop, rulesWidth, contentHeight);
|
||||
const controlPanel = this.createPanelBounds(rulesPanel.right + panelGap, contentTop, controlWidth, contentHeight);
|
||||
|
||||
return {
|
||||
frameInset,
|
||||
isCompactViewport,
|
||||
cameraZoom,
|
||||
visibleBounds,
|
||||
titleX: width / 2,
|
||||
titleY: 82,
|
||||
titleFontSize: 52,
|
||||
subtitleY: 124,
|
||||
subtitleFontSize: 21,
|
||||
subtitleWrapWidth: width * 0.46,
|
||||
panelPaddingX,
|
||||
panelPaddingY,
|
||||
panelTitleFontSize: 24,
|
||||
rulesHeadingFontSize: 20,
|
||||
rulesBodyFontSize: 17,
|
||||
rulesLineSpacing: 5,
|
||||
rulesLineGap: 12,
|
||||
rulesSectionGap: 18,
|
||||
rulesWrapWidth: rulesPanel.width - panelPaddingX * 2,
|
||||
footerFontSize: 16,
|
||||
controlIntroFontSize: 16,
|
||||
controlIntroWrapWidth: controlPanel.width - panelPaddingX * 2,
|
||||
buttonWidth: controlPanel.width - panelPaddingX * 2,
|
||||
buttonHeight: 66,
|
||||
buttonGap: 18,
|
||||
buttonTextWrapWidth: controlPanel.width - panelPaddingX * 2 - 28,
|
||||
buttonLabelFontSize: 22,
|
||||
buttonSubtitleFontSize: 13,
|
||||
buttonLabelOffset: -10,
|
||||
buttonSubtitleOffset: 15,
|
||||
showButtonSubtitle: true,
|
||||
rulesPanel,
|
||||
controlPanel,
|
||||
decorativeCards: [
|
||||
{
|
||||
x: frameInset + 70,
|
||||
y: 104,
|
||||
angle: -18,
|
||||
scale: 0.072,
|
||||
alpha: 0.72,
|
||||
},
|
||||
{
|
||||
x: frameInset + 132,
|
||||
y: 136,
|
||||
angle: -6,
|
||||
scale: 0.069,
|
||||
alpha: 0.64,
|
||||
},
|
||||
{
|
||||
x: width - frameInset - 70,
|
||||
y: 104,
|
||||
angle: 18,
|
||||
scale: 0.072,
|
||||
alpha: 0.72,
|
||||
},
|
||||
{
|
||||
x: width - frameInset - 132,
|
||||
y: 136,
|
||||
angle: 6,
|
||||
scale: 0.069,
|
||||
alpha: 0.64,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
private createPanelBounds(x: number, y: number, width: number, height: number): PanelBounds {
|
||||
return {
|
||||
x,
|
||||
y,
|
||||
width,
|
||||
height,
|
||||
centerX: x + width / 2,
|
||||
centerY: y + height / 2,
|
||||
right: x + width,
|
||||
bottom: y + height,
|
||||
};
|
||||
}
|
||||
|
||||
private drawBackground(width: number, height: number, layout: MenuLayout): void {
|
||||
this.add.rectangle(0, 0, width, height, 0x123d22).setOrigin(0);
|
||||
this.add.rectangle(width / 2, height / 2, width - 60, height - 60, 0x0b2916, 0.28)
|
||||
this.add.rectangle(
|
||||
layout.visibleBounds.centerX,
|
||||
layout.visibleBounds.centerY,
|
||||
layout.visibleBounds.width - layout.frameInset * 2,
|
||||
layout.visibleBounds.height - layout.frameInset * 2,
|
||||
0x0b2916,
|
||||
0.28,
|
||||
)
|
||||
.setStrokeStyle(2, 0xe8c25d, 0.35);
|
||||
this.add.rectangle(width * 0.34, height * 0.58, width * 0.46, height * 0.50, 0x0d2215, 0.82)
|
||||
|
||||
this.add.rectangle(
|
||||
layout.rulesPanel.centerX,
|
||||
layout.rulesPanel.centerY,
|
||||
layout.rulesPanel.width,
|
||||
layout.rulesPanel.height,
|
||||
0x0d2215,
|
||||
0.84,
|
||||
)
|
||||
.setStrokeStyle(2, 0xc8a445, 0.4);
|
||||
this.add.rectangle(width * 0.77, height * 0.58, width * 0.24, height * 0.50, 0x10261b, 0.86)
|
||||
|
||||
this.add.rectangle(
|
||||
layout.controlPanel.centerX,
|
||||
layout.controlPanel.centerY,
|
||||
layout.controlPanel.width,
|
||||
layout.controlPanel.height,
|
||||
0x10261b,
|
||||
0.88,
|
||||
)
|
||||
.setStrokeStyle(2, 0xc8a445, 0.4);
|
||||
}
|
||||
|
||||
private drawDecorativeCards(width: number, height: number): void {
|
||||
const positions = [
|
||||
[width * 0.08, height * 0.85],
|
||||
[width * 0.14, height * 0.87],
|
||||
[width * 0.92, height * 0.85],
|
||||
[width * 0.86, height * 0.87],
|
||||
];
|
||||
|
||||
positions.forEach(([x, y]) => {
|
||||
this.add.image(x, y, 'retro').setScale(0.08).setAngle(Phaser.Math.Between(-15, 15)).setAlpha(0.9);
|
||||
private drawDecorativeCards(layout: MenuLayout): void {
|
||||
layout.decorativeCards.forEach((card) => {
|
||||
this.add.image(card.x, card.y, 'retro').setScale(card.scale).setAngle(card.angle).setAlpha(card.alpha);
|
||||
});
|
||||
}
|
||||
|
||||
private createRulesPanel(width: number, height: number): void {
|
||||
const panelX = width * 0.12;
|
||||
const panelY = 206;
|
||||
const sections: RuleSection[] = [
|
||||
{
|
||||
heading: 'Tavolo e squadre',
|
||||
lines: [
|
||||
'Si gioca in coppia: Sud e Nord contro Ovest ed Est.',
|
||||
'Nel vero scopone si distribuiscono tutte le 40 carte, 10 a giocatore.',
|
||||
],
|
||||
},
|
||||
{
|
||||
heading: 'Come si prende',
|
||||
lines: [
|
||||
'Ogni carta cattura una carta dello stesso valore oppure una combinazione equivalente.',
|
||||
'Se sul tavolo c’è una presa diretta dello stesso valore, quella ha sempre la precedenza.',
|
||||
],
|
||||
},
|
||||
{
|
||||
heading: 'Punteggio partita',
|
||||
lines: [
|
||||
'A fine mano contano carte, denari, settebello, primiera e scope.',
|
||||
'La sfida prosegue mano dopo mano finché una squadra arriva ad almeno 11 punti.',
|
||||
],
|
||||
},
|
||||
];
|
||||
private createRulesPanel(layout: MenuLayout): void {
|
||||
const panelX = layout.rulesPanel.x + layout.panelPaddingX;
|
||||
const panelY = layout.rulesPanel.y + layout.panelPaddingY;
|
||||
const sections: RuleSection[] = layout.isCompactViewport
|
||||
? []
|
||||
: [
|
||||
{
|
||||
heading: 'Tavolo e squadre',
|
||||
lines: [
|
||||
'Si gioca in coppia: Sud e Nord contro Ovest ed Est.',
|
||||
'Nel vero scopone si distribuiscono tutte le 40 carte, 10 a giocatore.',
|
||||
],
|
||||
},
|
||||
{
|
||||
heading: 'Come si prende',
|
||||
lines: [
|
||||
'Ogni carta cattura una carta dello stesso valore oppure una combinazione equivalente.',
|
||||
'Se sul tavolo c’è una presa diretta dello stesso valore, quella ha sempre la precedenza.',
|
||||
],
|
||||
},
|
||||
{
|
||||
heading: 'Punteggio partita',
|
||||
lines: [
|
||||
'A fine mano contano carte, denari, settebello, primiera e scope.',
|
||||
'La sfida prosegue mano dopo mano finché una squadra arriva ad almeno 11 punti.',
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
this.add.text(panelX, panelY, 'Regolamento essenziale', PANEL_TITLE_STYLE).setOrigin(0, 0.5);
|
||||
this.add.text(panelX, panelY, layout.isCompactViewport ? 'Regole rapide' : 'Regolamento essenziale', {
|
||||
...PANEL_TITLE_STYLE,
|
||||
fontSize: `${layout.panelTitleFontSize}px`,
|
||||
}).setOrigin(0, 0);
|
||||
|
||||
let currentY = panelY + layout.panelTitleFontSize + (layout.isCompactViewport ? 10 : 18);
|
||||
|
||||
if (layout.isCompactViewport) {
|
||||
const summaryLines = [
|
||||
'• Sud e Nord contro Ovest ed Est, con 10 carte per giocatore.',
|
||||
'• Prendi lo stesso valore o una somma: la presa diretta viene prima.',
|
||||
'• Carte, denari, settebello, primiera e scope: vince chi tocca 11 punti.',
|
||||
];
|
||||
|
||||
summaryLines.forEach((line) => {
|
||||
const ruleText = this.add.text(panelX, currentY, line, {
|
||||
...BODY_STYLE,
|
||||
fontSize: `${layout.rulesBodyFontSize}px`,
|
||||
wordWrap: { width: layout.rulesWrapWidth },
|
||||
lineSpacing: layout.rulesLineSpacing,
|
||||
}).setOrigin(0, 0);
|
||||
currentY += ruleText.height + layout.rulesLineGap;
|
||||
});
|
||||
}
|
||||
|
||||
let currentY = panelY + 44;
|
||||
sections.forEach((section) => {
|
||||
this.add.text(panelX, currentY, section.heading, {
|
||||
const sectionTitle = this.add.text(panelX, currentY, section.heading, {
|
||||
fontFamily: 'Georgia, serif',
|
||||
fontSize: '20px',
|
||||
fontSize: `${layout.rulesHeadingFontSize}px`,
|
||||
color: '#ffffff',
|
||||
resolution: 2,
|
||||
}).setOrigin(0, 0.5);
|
||||
currentY += 30;
|
||||
}).setOrigin(0, 0);
|
||||
currentY += sectionTitle.height + (layout.isCompactViewport ? 6 : 10);
|
||||
|
||||
section.lines.forEach((line) => {
|
||||
this.add.text(panelX, currentY, `• ${line}`, {
|
||||
const ruleText = this.add.text(panelX, currentY, `• ${line}`, {
|
||||
...BODY_STYLE,
|
||||
wordWrap: { width: width * 0.40 },
|
||||
lineSpacing: 5,
|
||||
fontSize: `${layout.rulesBodyFontSize}px`,
|
||||
wordWrap: { width: layout.rulesWrapWidth },
|
||||
lineSpacing: layout.rulesLineSpacing,
|
||||
}).setOrigin(0, 0);
|
||||
currentY += 54;
|
||||
currentY += ruleText.height + layout.rulesLineGap;
|
||||
});
|
||||
|
||||
currentY += 6;
|
||||
currentY += layout.rulesSectionGap;
|
||||
});
|
||||
|
||||
this.add.text(panelX, height - 92, 'Scegli la difficoltà quando vuoi iniziare: le preferenze audio vengono lette al momento della partita.', {
|
||||
this.add.text(panelX, layout.rulesPanel.bottom - layout.panelPaddingY - 4, layout.isCompactViewport
|
||||
? 'Le preferenze audio salvate si applicano appena inizi.'
|
||||
: 'Scegli la difficoltà quando vuoi iniziare: le preferenze audio vengono lette al momento della partita.', {
|
||||
...BODY_STYLE,
|
||||
fontSize: '16px',
|
||||
fontSize: `${layout.footerFontSize}px`,
|
||||
color: '#cfe5cd',
|
||||
wordWrap: { width: width * 0.41 },
|
||||
}).setOrigin(0, 0.5);
|
||||
wordWrap: { width: layout.rulesWrapWidth },
|
||||
lineSpacing: layout.rulesLineSpacing,
|
||||
}).setOrigin(0, 1);
|
||||
}
|
||||
|
||||
private createControlPanel(width: number, height: number, audioPreferences: ReturnType<typeof loadAudioPreferences>): void {
|
||||
const panelCenterX = width * 0.77;
|
||||
private createControlPanel(layout: MenuLayout, audioPreferences: ReturnType<typeof loadAudioPreferences>): void {
|
||||
const panelCenterX = layout.controlPanel.centerX;
|
||||
const difficultyOptions: DifficultyOption[] = [
|
||||
{
|
||||
label: 'Principiante',
|
||||
@@ -174,18 +457,37 @@ export class MenuScene extends Phaser.Scene {
|
||||
},
|
||||
];
|
||||
|
||||
this.add.text(panelCenterX, 214, 'Inizia una partita', PANEL_TITLE_STYLE).setOrigin(0.5);
|
||||
this.add.text(panelCenterX, 250, 'Ogni partita usa la difficoltà scelta qui sotto e le preferenze audio salvate.', {
|
||||
...BODY_STYLE,
|
||||
fontSize: '16px',
|
||||
color: '#d7ead1',
|
||||
align: 'center',
|
||||
wordWrap: { width: 250 },
|
||||
const panelTop = layout.controlPanel.y + layout.panelPaddingY;
|
||||
this.add.text(panelCenterX, panelTop, layout.isCompactViewport ? 'Gioca subito' : 'Inizia una partita', {
|
||||
...PANEL_TITLE_STYLE,
|
||||
fontSize: `${layout.panelTitleFontSize}px`,
|
||||
}).setOrigin(0.5, 0);
|
||||
|
||||
const introText = layout.isCompactViewport
|
||||
? null
|
||||
: this.add.text(panelCenterX, panelTop + layout.panelTitleFontSize + 14, 'Ogni partita usa la difficoltà scelta qui sotto e le preferenze audio salvate.', {
|
||||
...BODY_STYLE,
|
||||
fontSize: `${layout.controlIntroFontSize}px`,
|
||||
color: '#d7ead1',
|
||||
align: 'center',
|
||||
wordWrap: { width: layout.controlIntroWrapWidth },
|
||||
}).setOrigin(0.5, 0);
|
||||
|
||||
const settingsButtonCenterY = layout.controlPanel.bottom - layout.panelPaddingY - layout.buttonHeight / 2;
|
||||
const audioStatusY = settingsButtonCenterY - layout.buttonHeight / 2 - (layout.isCompactViewport ? 12 : 24);
|
||||
const difficultyAreaTop = layout.isCompactViewport
|
||||
? panelTop + layout.panelTitleFontSize + 10
|
||||
: introText!.y + introText!.height + 28;
|
||||
const difficultyAreaBottom = audioStatusY - (layout.isCompactViewport ? 18 : 34);
|
||||
const maxButtonHeight = (difficultyAreaBottom - difficultyAreaTop - layout.buttonGap * (difficultyOptions.length - 1)) / difficultyOptions.length;
|
||||
const buttonHeight = Math.min(layout.buttonHeight, maxButtonHeight);
|
||||
const buttonGap = difficultyOptions.length > 1
|
||||
? (difficultyAreaBottom - difficultyAreaTop - buttonHeight * difficultyOptions.length) / (difficultyOptions.length - 1)
|
||||
: 0;
|
||||
|
||||
difficultyOptions.forEach((option, index) => {
|
||||
const y = 340 + index * 96;
|
||||
this.createButton(panelCenterX, y, 260, 64, option.label, option.subtitle, option.palette, () => {
|
||||
const y = difficultyAreaTop + buttonHeight / 2 + index * (buttonHeight + buttonGap);
|
||||
this.createButton(panelCenterX, y, layout.buttonWidth, buttonHeight, option.label, layout.showButtonSubtitle ? option.subtitle : null, option.palette, layout, () => {
|
||||
this.startGame(option.value);
|
||||
});
|
||||
});
|
||||
@@ -193,21 +495,23 @@ export class MenuScene extends Phaser.Scene {
|
||||
const musicLabel = audioPreferences.musicEnabled ? 'attiva' : 'disattivata';
|
||||
const effectsLabel = audioPreferences.effectsEnabled ? 'attivi' : 'disattivati';
|
||||
|
||||
this.add.text(panelCenterX, height - 154, `Musica ${musicLabel} · Effetti ${effectsLabel}`, {
|
||||
this.add.text(panelCenterX, audioStatusY, `Musica ${musicLabel} · Effetti ${effectsLabel}`, {
|
||||
...BODY_STYLE,
|
||||
fontSize: '16px',
|
||||
fontSize: `${layout.footerFontSize}px`,
|
||||
color: '#cfe5cd',
|
||||
align: 'center',
|
||||
wordWrap: { width: layout.controlIntroWrapWidth },
|
||||
}).setOrigin(0.5);
|
||||
|
||||
this.createButton(
|
||||
panelCenterX,
|
||||
height - 100,
|
||||
260,
|
||||
58,
|
||||
settingsButtonCenterY,
|
||||
layout.buttonWidth,
|
||||
layout.buttonHeight,
|
||||
'Impostazioni audio',
|
||||
'Modifica musica ed effetti in modo indipendente',
|
||||
layout.showButtonSubtitle ? 'Modifica musica ed effetti in modo indipendente' : null,
|
||||
{ base: 0x1f6f78, hover: 0x2f8f99 },
|
||||
layout,
|
||||
() => {
|
||||
this.openSettings();
|
||||
},
|
||||
@@ -220,30 +524,34 @@ export class MenuScene extends Phaser.Scene {
|
||||
width: number,
|
||||
height: number,
|
||||
label: string,
|
||||
subtitle: string,
|
||||
subtitle: string | null,
|
||||
palette: MenuButtonPalette,
|
||||
layout: MenuLayout,
|
||||
onClick: () => void,
|
||||
): void {
|
||||
const background = this.add.rectangle(x, y, width, height, palette.base, 1)
|
||||
.setStrokeStyle(2, 0xf5e1a4, 0.4)
|
||||
.setInteractive({ useHandCursor: true });
|
||||
|
||||
this.add.text(x, y - 10, label, {
|
||||
this.add.text(x, y + layout.buttonLabelOffset, label, {
|
||||
fontFamily: 'Georgia, serif',
|
||||
fontSize: '21px',
|
||||
fontSize: `${layout.buttonLabelFontSize}px`,
|
||||
color: '#ffffff',
|
||||
stroke: '#000000',
|
||||
strokeThickness: 2,
|
||||
resolution: 2,
|
||||
}).setOrigin(0.5);
|
||||
|
||||
this.add.text(x, y + 14, subtitle, {
|
||||
fontFamily: 'Georgia, serif',
|
||||
fontSize: '13px',
|
||||
color: '#f7f1d5',
|
||||
resolution: 2,
|
||||
align: 'center',
|
||||
}).setOrigin(0.5);
|
||||
if (subtitle) {
|
||||
this.add.text(x, y + layout.buttonSubtitleOffset, subtitle, {
|
||||
fontFamily: 'Georgia, serif',
|
||||
fontSize: `${layout.buttonSubtitleFontSize}px`,
|
||||
color: '#f7f1d5',
|
||||
resolution: 2,
|
||||
align: 'center',
|
||||
wordWrap: { width: layout.buttonTextWrapWidth },
|
||||
}).setOrigin(0.5);
|
||||
}
|
||||
|
||||
background.on('pointerover', () => background.setFillStyle(palette.hover));
|
||||
background.on('pointerout', () => background.setFillStyle(palette.base));
|
||||
@@ -263,30 +571,9 @@ export class MenuScene extends Phaser.Scene {
|
||||
}
|
||||
|
||||
private openSettings(): void {
|
||||
this.ensureSettingsSceneAvailable();
|
||||
this.cameras.main.fadeOut(250, 0, 30, 0);
|
||||
this.cameras.main.once('camerafadeoutcomplete', () => {
|
||||
this.scene.start('SettingsScene', { returnSceneKey: 'MenuScene' });
|
||||
});
|
||||
}
|
||||
|
||||
private ensureSettingsSceneAvailable(): void {
|
||||
let existingScene: Phaser.Scene | null = null;
|
||||
|
||||
try {
|
||||
existingScene = this.scene.get('SettingsScene');
|
||||
} catch {
|
||||
existingScene = null;
|
||||
}
|
||||
|
||||
if (existingScene instanceof SettingsScene) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (existingScene) {
|
||||
this.scene.remove('SettingsScene');
|
||||
}
|
||||
|
||||
this.scene.add('SettingsScene', SettingsScene, false);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user