Working on end-game menu
This commit is contained in:
parent
2e1c73c9dc
commit
93da72f55c
@ -3,8 +3,6 @@ import { Engine } from './Bastion';
|
|||||||
export default class GameUIConstants {
|
export default class GameUIConstants {
|
||||||
public static SidebarRect: PIXI.Rectangle;
|
public static SidebarRect: PIXI.Rectangle;
|
||||||
public static ChangeRoundButtonRect: PIXI.Rectangle;
|
public static ChangeRoundButtonRect: PIXI.Rectangle;
|
||||||
public static StandardDialogWidth: number;
|
|
||||||
public static StandardDialogHeight: number;
|
|
||||||
public static MaximumPlayerNameLength = 20;
|
public static MaximumPlayerNameLength = 20;
|
||||||
|
|
||||||
public static init() {
|
public static init() {
|
||||||
@ -15,7 +13,5 @@ export default class GameUIConstants {
|
|||||||
Engine.app.canvas.height
|
Engine.app.canvas.height
|
||||||
);
|
);
|
||||||
GameUIConstants.ChangeRoundButtonRect = new PIXI.Rectangle(50, Engine.app.canvas.height - 100, 310, 100);
|
GameUIConstants.ChangeRoundButtonRect = new PIXI.Rectangle(50, Engine.app.canvas.height - 100, 310, 100);
|
||||||
GameUIConstants.StandardDialogWidth = 600;
|
|
||||||
GameUIConstants.StandardDialogHeight = 800;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
71
src/classes/game/HighScoreManager.ts
Normal file
71
src/classes/game/HighScoreManager.ts
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
/**
|
||||||
|
* Handles the high score system.
|
||||||
|
*/
|
||||||
|
export class HighScoreManager {
|
||||||
|
private static readonly STORAGE_KEY_PREFIX = 'highscore_';
|
||||||
|
private static readonly MAX_SCORES = 10;
|
||||||
|
|
||||||
|
public readonly missionName: string;
|
||||||
|
private scores: PlayerScore[];
|
||||||
|
|
||||||
|
constructor(missionName: string) {
|
||||||
|
this.missionName = missionName;
|
||||||
|
this.scores = this.loadScores();
|
||||||
|
this.scores.sort((a, b) => b.score - a.score || a.timestamp - b.timestamp);
|
||||||
|
}
|
||||||
|
|
||||||
|
private loadScores(): PlayerScore[] {
|
||||||
|
const storedScores = localStorage.getItem(HighScoreManager.STORAGE_KEY_PREFIX + this.missionName);
|
||||||
|
return HighScoreManager.parseStoredScores(storedScores);
|
||||||
|
}
|
||||||
|
|
||||||
|
private saveScores(): void {
|
||||||
|
localStorage.setItem(HighScoreManager.STORAGE_KEY_PREFIX + this.missionName, JSON.stringify(this.scores));
|
||||||
|
}
|
||||||
|
|
||||||
|
public addScore(playerScore: PlayerScore): void {
|
||||||
|
this.scores.push(playerScore);
|
||||||
|
this.scores.sort((a, b) => b.score - a.score);
|
||||||
|
if (this.scores.length > HighScoreManager.MAX_SCORES) {
|
||||||
|
this.scores.length = HighScoreManager.MAX_SCORES;
|
||||||
|
}
|
||||||
|
this.saveScores();
|
||||||
|
}
|
||||||
|
|
||||||
|
public getScores(): PlayerScore[] {
|
||||||
|
return this.scores;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static parseStoredScores(storedScores: string | null): PlayerScore[] {
|
||||||
|
if (!storedScores) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const parsedScores = JSON.parse(storedScores);
|
||||||
|
if (
|
||||||
|
Array.isArray(parsedScores) &&
|
||||||
|
parsedScores.every(
|
||||||
|
(score) =>
|
||||||
|
typeof score.playerName === 'string' &&
|
||||||
|
typeof score.score === 'number' &&
|
||||||
|
typeof score.timestamp === 'number'
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return parsedScores.map((score) => ({
|
||||||
|
playerName: score.playerName,
|
||||||
|
score: score.score,
|
||||||
|
timestamp: score.timestamp,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Failed to parse stored scores:', e);
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type PlayerScore = {
|
||||||
|
playerName: string;
|
||||||
|
score: number;
|
||||||
|
timestamp: number;
|
||||||
|
};
|
@ -8,14 +8,16 @@ import Gem from './Gem';
|
|||||||
export default class MissionStats extends GameObject {
|
export default class MissionStats extends GameObject {
|
||||||
private hp: number = 100;
|
private hp: number = 100;
|
||||||
private gold: number = 0;
|
private gold: number = 0;
|
||||||
|
private goldEarned: number = 0;
|
||||||
|
private goldSpent: number = 0;
|
||||||
|
private wavesSurvived: number = 0;
|
||||||
|
private damageDealt: number = 0;
|
||||||
|
private creepsKilled: number = 0;
|
||||||
private goldText: PIXI.Text;
|
private goldText: PIXI.Text;
|
||||||
private healthText: PIXI.Text;
|
private healthText: PIXI.Text;
|
||||||
private waveText: PIXI.Text;
|
private waveText: PIXI.Text;
|
||||||
private inventory: Gem[] = [];
|
private inventory: Gem[] = [];
|
||||||
|
|
||||||
// TODO: implement score keeping for leaderboards.
|
|
||||||
private score: number = 0;
|
|
||||||
|
|
||||||
public getHP() {
|
public getHP() {
|
||||||
return this.hp;
|
return this.hp;
|
||||||
}
|
}
|
||||||
@ -147,5 +149,32 @@ export default class MissionStats extends GameObject {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getStats() {
|
||||||
|
return {
|
||||||
|
hp: this.hp,
|
||||||
|
gold: this.gold,
|
||||||
|
wavesSurvived: this.wavesSurvived,
|
||||||
|
goldEarned: this.goldEarned,
|
||||||
|
goldSpent: this.goldSpent,
|
||||||
|
score: this.calculateScore(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private calculateScore() {
|
||||||
|
const uniqueGems = [];
|
||||||
|
for (const gem of this.inventory) {
|
||||||
|
if (!uniqueGems.includes(gem.definition.name)) {
|
||||||
|
uniqueGems.push(gem.definition.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
this.damageDealt * 2 +
|
||||||
|
this.hp * 10 +
|
||||||
|
(this.goldEarned - this.goldSpent) * 3 +
|
||||||
|
this.wavesSurvived * 100 +
|
||||||
|
uniqueGems.length * 100
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
public update() {}
|
public update() {}
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,8 @@ import GameUIConstants from '../GameUIConstants';
|
|||||||
import ModalDialogBase from './ModalDialog';
|
import ModalDialogBase from './ModalDialog';
|
||||||
import TextInput from './TextInput';
|
import TextInput from './TextInput';
|
||||||
import MessageBox from './MessageBox';
|
import MessageBox from './MessageBox';
|
||||||
|
import { HighScoreManager } from '../game/HighScoreManager';
|
||||||
|
import MissionStats from '../game/MissionStats';
|
||||||
|
|
||||||
export const EndGameDialogButtons = {
|
export const EndGameDialogButtons = {
|
||||||
Confirm: 'OK',
|
Confirm: 'OK',
|
||||||
@ -14,14 +16,18 @@ export default class EndGameDialog extends ModalDialogBase {
|
|||||||
private dialogCaption: PIXI.Text;
|
private dialogCaption: PIXI.Text;
|
||||||
private playerNameTextInput: TextInput;
|
private playerNameTextInput: TextInput;
|
||||||
private lost: boolean;
|
private lost: boolean;
|
||||||
|
private highScore: HighScoreManager;
|
||||||
|
private missionStats: MissionStats;
|
||||||
|
|
||||||
constructor(lost: boolean) {
|
constructor(missionName: string, missionStats: MissionStats, lost: boolean) {
|
||||||
super(
|
super(
|
||||||
[EndGameDialogButtons.Confirm, EndGameDialogButtons.Skip],
|
[EndGameDialogButtons.Confirm, EndGameDialogButtons.Skip],
|
||||||
EndGameDialogButtons.Confirm,
|
EndGameDialogButtons.Confirm,
|
||||||
EndGameDialogButtons.Skip
|
EndGameDialogButtons.Skip
|
||||||
);
|
);
|
||||||
this.lost = lost;
|
this.lost = lost;
|
||||||
|
this.highScore = new HighScoreManager(missionName);
|
||||||
|
this.missionStats = missionStats;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override generate(): void {
|
protected override generate(): void {
|
||||||
@ -31,61 +37,118 @@ export default class EndGameDialog extends ModalDialogBase {
|
|||||||
style: new PIXI.TextStyle({
|
style: new PIXI.TextStyle({
|
||||||
fill: 0xffffff,
|
fill: 0xffffff,
|
||||||
fontSize: 36,
|
fontSize: 36,
|
||||||
|
stroke: { color: 0x000000, width: 2 },
|
||||||
|
dropShadow: {
|
||||||
|
color: 0x000000,
|
||||||
|
blur: 8,
|
||||||
|
distance: 0,
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
this.dialogContainer.addChild(this.dialogCaption);
|
|
||||||
this.dialogCaption.anchor.set(0.5, 0.5);
|
this.dialogCaption.anchor.set(0.5, 0.5);
|
||||||
this.dialogCaption.x = this.dialogContainer.width / 2;
|
this.dialogCaption.x = this.dialogContainer.width / 2;
|
||||||
this.dialogCaption.y = 50;
|
this.dialogCaption.y = 50;
|
||||||
|
this.dialogContainer.addChild(this.dialogCaption);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override createDialogBackground(width: number, height: number): PIXI.Container {
|
protected override createDialogBackground(): PIXI.NineSliceSprite {
|
||||||
const background = new PIXI.NineSliceSprite({
|
return new PIXI.NineSliceSprite({
|
||||||
texture: GameAssets.EndScreenDialog,
|
texture: GameAssets.EndScreenDialog,
|
||||||
leftWidth: 50,
|
leftWidth: 50,
|
||||||
topHeight: 100,
|
topHeight: 100,
|
||||||
rightWidth: 50,
|
rightWidth: 50,
|
||||||
bottomHeight: 50,
|
bottomHeight: 50,
|
||||||
});
|
});
|
||||||
background.x = 0;
|
|
||||||
background.y = 0;
|
|
||||||
background.width = width;
|
|
||||||
background.height = height;
|
|
||||||
return background;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override createContent(): PIXI.Container {
|
protected override createContent(): PIXI.Container {
|
||||||
const container = new PIXI.Container();
|
const container = new PIXI.Container();
|
||||||
const caption = new PIXI.Text({
|
const lineHeight = 35;
|
||||||
text: 'Enter your name:',
|
const lblScore = this.createText('Mission details:', '#fee', true);
|
||||||
style: new PIXI.TextStyle({
|
container.addChild(lblScore);
|
||||||
fill: 0xffffff,
|
const stats = this.missionStats.getStats();
|
||||||
fontSize: 24,
|
const width = this.getWidth() - this.background.leftWidth - this.background.rightWidth - 20;
|
||||||
}),
|
const labels = [
|
||||||
});
|
this.createText('HP:'),
|
||||||
container.addChild(caption);
|
this.createText('Gold:'),
|
||||||
this.playerNameTextInput = new TextInput(
|
this.createText('Waves Survived:'),
|
||||||
GameUIConstants.MaximumPlayerNameLength * 20,
|
this.createText('Gold Earned:'),
|
||||||
GameUIConstants.MaximumPlayerNameLength
|
this.createText('Gold Spent:'),
|
||||||
);
|
this.createText('----'),
|
||||||
this.playerNameTextInput.container.y = caption.height + 10;
|
this.createText('Score:'),
|
||||||
|
];
|
||||||
|
const values = [
|
||||||
|
this.createText(stats.hp.toString(), 'yellow'),
|
||||||
|
this.createText(stats.gold.toString(), 'yellow'),
|
||||||
|
this.createText(stats.wavesSurvived.toString(), 'yellow'),
|
||||||
|
this.createText(stats.goldEarned.toString(), 'yellow'),
|
||||||
|
this.createText(stats.goldSpent.toString(), 'yellow'),
|
||||||
|
this.createText('----', 'yellow'),
|
||||||
|
this.createText(stats.score.toString(), 'yellow'),
|
||||||
|
];
|
||||||
|
const valueX = 300;
|
||||||
|
for (let i = 0; i < labels.length; i++) {
|
||||||
|
if (labels[i].text === '----') {
|
||||||
|
const line = new PIXI.Graphics();
|
||||||
|
const y = lblScore.y + lblScore.height + 10 + i * lineHeight + lineHeight / 2;
|
||||||
|
line.moveTo(10, y);
|
||||||
|
line.lineTo(width, y);
|
||||||
|
line.stroke({ color: 'yellow', width: 2 });
|
||||||
|
container.addChild(line);
|
||||||
|
} else {
|
||||||
|
labels[i].x = 10;
|
||||||
|
labels[i].y = lblScore.y + lblScore.height + 10 + i * lineHeight;
|
||||||
|
container.addChild(labels[i]);
|
||||||
|
|
||||||
|
values[i].x = valueX;
|
||||||
|
values[i].y = lblScore.y + lblScore.height + 10 + i * lineHeight;
|
||||||
|
container.addChild(values[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const offsetY = values[values.length - 1].y + lineHeight + 80;
|
||||||
|
|
||||||
|
const lblName = this.createText('Enter your name:');
|
||||||
|
lblName.y = offsetY;
|
||||||
|
container.addChild(lblName);
|
||||||
|
this.playerNameTextInput = new TextInput(width, GameUIConstants.MaximumPlayerNameLength);
|
||||||
|
this.playerNameTextInput.container.y = lblName.y + lblName.height + 10;
|
||||||
container.addChild(this.playerNameTextInput.container);
|
container.addChild(this.playerNameTextInput.container);
|
||||||
return container;
|
return container;
|
||||||
}
|
}
|
||||||
|
|
||||||
override close(button?: string): void {
|
override close(button?: string): void {
|
||||||
if (button === EndGameDialogButtons.Confirm && this.playerNameTextInput.getText().length == 0) {
|
if (button === EndGameDialogButtons.Confirm) {
|
||||||
MessageBox.show('Please enter your name.', ['OK']);
|
if (this.playerNameTextInput.getText().length == 0) {
|
||||||
|
MessageBox.show('Please enter your name.', ['OK']);
|
||||||
|
} else {
|
||||||
|
this.highScore.addScore({
|
||||||
|
playerName: this.playerNameTextInput.getText(),
|
||||||
|
score: this.missionStats.getStats().score,
|
||||||
|
timestamp: Date.now(),
|
||||||
|
});
|
||||||
|
super.close(button);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
super.close(button);
|
super.close(button);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private createText(caption: string, color: string = '#fff', bold = false): PIXI.Text {
|
||||||
|
return new PIXI.Text({
|
||||||
|
text: caption,
|
||||||
|
style: new PIXI.TextStyle({
|
||||||
|
fill: color,
|
||||||
|
fontSize: 24,
|
||||||
|
fontWeight: bold ? 'bold' : 'normal',
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
protected override getWidth(): number | undefined {
|
protected override getWidth(): number | undefined {
|
||||||
return GameUIConstants.StandardDialogWidth;
|
return 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override getHeight(): number | undefined {
|
protected override getHeight(): number | undefined {
|
||||||
return GameUIConstants.StandardDialogHeight;
|
return 800;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
import * as PIXI from 'pixi.js';
|
import * as PIXI from 'pixi.js';
|
||||||
import GameAssets from '../Assets';
|
import GameAssets from '../Assets';
|
||||||
import GameUIConstants from '../GameUIConstants';
|
|
||||||
import ModalDialogBase from './ModalDialog';
|
import ModalDialogBase from './ModalDialog';
|
||||||
import TextInput from './TextInput';
|
import { HighScoreManager } from '../game/HighScoreManager';
|
||||||
|
|
||||||
export const HighScoreDialogButtons = {
|
export const HighScoreDialogButtons = {
|
||||||
Retry: 'Retry',
|
Retry: 'Retry',
|
||||||
@ -12,10 +11,9 @@ export const HighScoreDialogButtons = {
|
|||||||
|
|
||||||
export default class HighScoreDialog extends ModalDialogBase {
|
export default class HighScoreDialog extends ModalDialogBase {
|
||||||
private dialogCaption: PIXI.Text;
|
private dialogCaption: PIXI.Text;
|
||||||
private playerNameTextInput: TextInput;
|
private highScore: HighScoreManager;
|
||||||
private lost: boolean;
|
|
||||||
|
|
||||||
constructor(nextMissionAvailable: boolean) {
|
constructor(missionName: string, nextMissionAvailable: boolean) {
|
||||||
super(
|
super(
|
||||||
nextMissionAvailable
|
nextMissionAvailable
|
||||||
? [HighScoreDialogButtons.Retry, HighScoreDialogButtons.NextMission, HighScoreDialogButtons.MainMenu]
|
? [HighScoreDialogButtons.Retry, HighScoreDialogButtons.NextMission, HighScoreDialogButtons.MainMenu]
|
||||||
@ -23,6 +21,7 @@ export default class HighScoreDialog extends ModalDialogBase {
|
|||||||
nextMissionAvailable ? HighScoreDialogButtons.NextMission : HighScoreDialogButtons.Retry,
|
nextMissionAvailable ? HighScoreDialogButtons.NextMission : HighScoreDialogButtons.Retry,
|
||||||
HighScoreDialogButtons.MainMenu
|
HighScoreDialogButtons.MainMenu
|
||||||
);
|
);
|
||||||
|
this.highScore = new HighScoreManager(missionName);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override generate(): void {
|
protected override generate(): void {
|
||||||
@ -32,47 +31,86 @@ export default class HighScoreDialog extends ModalDialogBase {
|
|||||||
style: new PIXI.TextStyle({
|
style: new PIXI.TextStyle({
|
||||||
fill: 0xffffff,
|
fill: 0xffffff,
|
||||||
fontSize: 36,
|
fontSize: 36,
|
||||||
|
stroke: { color: 0x000000, width: 2 },
|
||||||
|
dropShadow: {
|
||||||
|
color: 0x000000,
|
||||||
|
blur: 8,
|
||||||
|
distance: 0,
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
this.dialogContainer.addChild(this.dialogCaption);
|
|
||||||
this.dialogCaption.anchor.set(0.5, 0.5);
|
this.dialogCaption.anchor.set(0.5, 0.5);
|
||||||
this.dialogCaption.x = this.dialogContainer.width / 2;
|
this.dialogCaption.x = this.dialogContainer.width / 2;
|
||||||
this.dialogCaption.y = 50;
|
this.dialogCaption.y = 50;
|
||||||
|
this.dialogContainer.addChild(this.dialogCaption);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override createDialogBackground(width: number, height: number): PIXI.Container {
|
protected override createDialogBackground(): PIXI.NineSliceSprite {
|
||||||
const background = new PIXI.NineSliceSprite({
|
return new PIXI.NineSliceSprite({
|
||||||
texture: GameAssets.EndScreenDialog,
|
texture: GameAssets.EndScreenDialog,
|
||||||
leftWidth: 50,
|
leftWidth: 50,
|
||||||
topHeight: 100,
|
topHeight: 100,
|
||||||
rightWidth: 50,
|
rightWidth: 50,
|
||||||
bottomHeight: 50,
|
bottomHeight: 50,
|
||||||
});
|
});
|
||||||
background.x = 0;
|
|
||||||
background.y = 0;
|
|
||||||
background.width = width;
|
|
||||||
background.height = height;
|
|
||||||
return background;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override createContent(): PIXI.Container {
|
protected override createContent(): PIXI.Container {
|
||||||
const container = new PIXI.Container();
|
const container = new PIXI.Container();
|
||||||
const caption = new PIXI.Text({
|
const caption = this.createText('Mission: ' + this.highScore.missionName, '#fee', true);
|
||||||
text: 'Leaderboard:',
|
|
||||||
style: new PIXI.TextStyle({
|
|
||||||
fill: 0xffffff,
|
|
||||||
fontSize: 24,
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
container.addChild(caption);
|
container.addChild(caption);
|
||||||
|
const lineHeight = 35;
|
||||||
|
const scores = this.highScore.getScores();
|
||||||
|
while (scores.length < 10) {
|
||||||
|
scores.push({ playerName: '---', score: 0, timestamp: 0 });
|
||||||
|
}
|
||||||
|
|
||||||
|
const numberTexts = [
|
||||||
|
this.createText('#', '#fee'),
|
||||||
|
...scores.map((_, i) => this.createText((i + 1).toString())),
|
||||||
|
];
|
||||||
|
const playerTexts = [
|
||||||
|
this.createText('Player', '#fee', true),
|
||||||
|
...scores.map((score) => this.createText(score.playerName)),
|
||||||
|
];
|
||||||
|
const scoreTexts = [
|
||||||
|
this.createText('Score', '#fee', true),
|
||||||
|
...scores.map((score) => this.createText(score.score.toString())),
|
||||||
|
];
|
||||||
|
const playerX = numberTexts.reduce((maxX, text) => Math.max(maxX, text.width), 0) + 20;
|
||||||
|
const scoreX = playerX + playerTexts.reduce((maxX, text) => Math.max(maxX, text.width), 0) + 20;
|
||||||
|
for (let i = 0; i < playerTexts.length; i++) {
|
||||||
|
numberTexts[i].x = 10;
|
||||||
|
numberTexts[i].y = lineHeight + 10 + i * lineHeight;
|
||||||
|
container.addChild(numberTexts[i]);
|
||||||
|
|
||||||
|
playerTexts[i].x = playerX;
|
||||||
|
playerTexts[i].y = lineHeight + 10 + i * lineHeight;
|
||||||
|
container.addChild(playerTexts[i]);
|
||||||
|
|
||||||
|
scoreTexts[i].x = scoreX;
|
||||||
|
scoreTexts[i].y = lineHeight + 10 + i * lineHeight;
|
||||||
|
container.addChild(scoreTexts[i]);
|
||||||
|
}
|
||||||
return container;
|
return container;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private createText(caption: string, color: string = '#fff', bold = false): PIXI.Text {
|
||||||
|
return new PIXI.Text({
|
||||||
|
text: caption,
|
||||||
|
style: new PIXI.TextStyle({
|
||||||
|
fill: color,
|
||||||
|
fontSize: 24,
|
||||||
|
fontWeight: bold ? 'bold' : 'normal',
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
protected override getWidth(): number | undefined {
|
protected override getWidth(): number | undefined {
|
||||||
return GameUIConstants.StandardDialogWidth;
|
return 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override getHeight(): number | undefined {
|
protected override getHeight(): number | undefined {
|
||||||
return GameUIConstants.StandardDialogHeight;
|
return 800;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,15 +8,12 @@ import KeyboardManager from '../game/KeyboardManager';
|
|||||||
|
|
||||||
export default abstract class ModalDialogBase extends GuiObject {
|
export default abstract class ModalDialogBase extends GuiObject {
|
||||||
protected overlay: PIXI.Graphics;
|
protected overlay: PIXI.Graphics;
|
||||||
protected dialogPadding = 40;
|
protected buttonHeight = 65;
|
||||||
protected contentPadding = 10;
|
|
||||||
protected buttonPadding = 10;
|
|
||||||
protected buttonAreaHeight = 40;
|
|
||||||
protected buttonHeight = 60;
|
|
||||||
protected buttonCaptions: string[];
|
protected buttonCaptions: string[];
|
||||||
protected buttons: Button[] = [];
|
protected buttons: Button[] = [];
|
||||||
protected dialogContent: PIXI.Container;
|
protected dialogContent: PIXI.Container;
|
||||||
protected dialogContainer: PIXI.Container;
|
protected dialogContainer: PIXI.Container;
|
||||||
|
protected background: PIXI.NineSliceSprite;
|
||||||
|
|
||||||
private generated = false;
|
private generated = false;
|
||||||
private escapeKeyButton?: string | null;
|
private escapeKeyButton?: string | null;
|
||||||
@ -43,8 +40,14 @@ export default abstract class ModalDialogBase extends GuiObject {
|
|||||||
*/
|
*/
|
||||||
public show(): Promise<string | null> {
|
public show(): Promise<string | null> {
|
||||||
this.generate();
|
this.generate();
|
||||||
|
const dialogBounds = `x: ${Math.round(this.dialogContainer.x)}, y: ${Math.round(
|
||||||
|
this.dialogContainer.y
|
||||||
|
)}, width: ${Math.round(this.dialogContainer.width)}, height: ${Math.round(this.dialogContainer.height)}`;
|
||||||
|
const contentBounds = `x: ${Math.round(this.dialogContent.x)}, y: ${Math.round(
|
||||||
|
this.dialogContent.y
|
||||||
|
)}, width: ${Math.round(this.dialogContent.width)}, height: ${Math.round(this.dialogContent.height)}`;
|
||||||
console.debug(
|
console.debug(
|
||||||
`ModalDialogBase.show(content: ${this.dialogContainer.width}x${this.dialogContainer.height}, buttons: ${this.buttonCaptions})`
|
`ModalDialogBase.show(dialog: ${dialogBounds}, content: ${contentBounds}, buttons: ${this.buttonCaptions})`
|
||||||
);
|
);
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
Engine.app.stage.addChild(this.container);
|
Engine.app.stage.addChild(this.container);
|
||||||
@ -69,19 +72,14 @@ export default abstract class ModalDialogBase extends GuiObject {
|
|||||||
/**
|
/**
|
||||||
* Creates dialog background.
|
* Creates dialog background.
|
||||||
*/
|
*/
|
||||||
protected createDialogBackground(width: number, height: number): PIXI.Container {
|
protected createDialogBackground(): PIXI.NineSliceSprite {
|
||||||
const background = new PIXI.NineSliceSprite({
|
return new PIXI.NineSliceSprite({
|
||||||
texture: GameAssets.Frame04Texture,
|
texture: GameAssets.Frame04Texture,
|
||||||
leftWidth: 60,
|
leftWidth: 60,
|
||||||
topHeight: 60,
|
topHeight: 60,
|
||||||
rightWidth: 60,
|
rightWidth: 60,
|
||||||
bottomHeight: 60,
|
bottomHeight: 60,
|
||||||
});
|
});
|
||||||
background.x = 0;
|
|
||||||
background.y = 0;
|
|
||||||
background.width = width;
|
|
||||||
background.height = height;
|
|
||||||
return background;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -110,23 +108,31 @@ export default abstract class ModalDialogBase extends GuiObject {
|
|||||||
// Prevent interaction with the underlying scene
|
// Prevent interaction with the underlying scene
|
||||||
this.overlay.interactive = true;
|
this.overlay.interactive = true;
|
||||||
this.container.addChild(this.overlay);
|
this.container.addChild(this.overlay);
|
||||||
|
|
||||||
this.dialogContent = this.createContent();
|
|
||||||
const buttonDefs = this.buttonCaptions.map((btnCaption) => ({
|
const buttonDefs = this.buttonCaptions.map((btnCaption) => ({
|
||||||
caption: btnCaption,
|
caption: btnCaption,
|
||||||
width: btnCaption.length * 16 + 40,
|
width: btnCaption.length * 14 + 60,
|
||||||
height: this.buttonHeight,
|
height: this.buttonHeight,
|
||||||
click: () => this.close(btnCaption),
|
click: () => this.close(btnCaption),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
this.background = this.createDialogBackground();
|
||||||
|
this.dialogContent = this.createContent();
|
||||||
|
|
||||||
let buttonTotalWidth = 0;
|
let buttonTotalWidth = 0;
|
||||||
for (const buttonDef of buttonDefs) {
|
for (const buttonDef of buttonDefs) {
|
||||||
if (buttonTotalWidth > 0) buttonTotalWidth += this.buttonPadding;
|
if (buttonTotalWidth > 0) buttonTotalWidth += 10;
|
||||||
buttonTotalWidth += buttonDef.width;
|
buttonTotalWidth += buttonDef.width;
|
||||||
}
|
}
|
||||||
const contentWidth = this.dialogContent.width + this.contentPadding * 2;
|
const buttonAreaHeight = this.buttonCaptions.length > 0 ? this.buttonHeight + 10 : 0;
|
||||||
const contentHeight = this.dialogContent.height + this.contentPadding * 2;
|
|
||||||
let width = this.getWidth() || Math.max(buttonTotalWidth, contentWidth) + this.dialogPadding * 2;
|
let width =
|
||||||
let height = this.getHeight() || contentHeight + this.buttonAreaHeight + this.dialogPadding * 2;
|
this.getWidth() ||
|
||||||
|
Math.max(buttonTotalWidth, this.dialogContent.width) +
|
||||||
|
this.background.leftWidth +
|
||||||
|
this.background.rightWidth;
|
||||||
|
let height =
|
||||||
|
this.getHeight() ||
|
||||||
|
this.dialogContent.height + buttonAreaHeight + this.background.topHeight + this.background.bottomHeight;
|
||||||
const modalBounds = new PIXI.Rectangle(
|
const modalBounds = new PIXI.Rectangle(
|
||||||
Engine.app.canvas.width / 2 - width / 2,
|
Engine.app.canvas.width / 2 - width / 2,
|
||||||
Engine.app.canvas.height / 2 - height / 2,
|
Engine.app.canvas.height / 2 - height / 2,
|
||||||
@ -137,14 +143,24 @@ export default abstract class ModalDialogBase extends GuiObject {
|
|||||||
this.dialogContainer = new PIXI.Container();
|
this.dialogContainer = new PIXI.Container();
|
||||||
this.dialogContainer.x = modalBounds.x;
|
this.dialogContainer.x = modalBounds.x;
|
||||||
this.dialogContainer.y = modalBounds.y;
|
this.dialogContainer.y = modalBounds.y;
|
||||||
|
this.background.width = width;
|
||||||
|
this.background.height = height;
|
||||||
|
this.dialogContainer.addChild(this.background);
|
||||||
|
|
||||||
const background = this.createDialogBackground(modalBounds.width, modalBounds.height);
|
if (this.dialogContent.width < modalBounds.width) {
|
||||||
this.dialogContainer.addChild(background);
|
|
||||||
|
|
||||||
if (this.dialogContent.width < modalBounds.width)
|
|
||||||
this.dialogContent.x = modalBounds.width / 2 - this.dialogContent.width / 2;
|
this.dialogContent.x = modalBounds.width / 2 - this.dialogContent.width / 2;
|
||||||
if (this.dialogContent.height < modalBounds.height - this.buttonAreaHeight)
|
}
|
||||||
this.dialogContent.y = (modalBounds.height - this.buttonAreaHeight) / 2 - this.dialogContent.height / 2;
|
if (
|
||||||
|
this.dialogContent.height <
|
||||||
|
modalBounds.height - buttonAreaHeight - this.background.topHeight - this.background.bottomHeight
|
||||||
|
) {
|
||||||
|
this.dialogContent.y =
|
||||||
|
this.background.topHeight +
|
||||||
|
(modalBounds.height - buttonAreaHeight - this.background.topHeight - this.background.bottomHeight) / 2 -
|
||||||
|
this.dialogContent.height / 2;
|
||||||
|
} else {
|
||||||
|
this.dialogContent.y = this.background.topHeight;
|
||||||
|
}
|
||||||
this.dialogContainer.addChild(this.dialogContent);
|
this.dialogContainer.addChild(this.dialogContent);
|
||||||
|
|
||||||
let buttonXPos = modalBounds.width / 2 - buttonTotalWidth / 2;
|
let buttonXPos = modalBounds.width / 2 - buttonTotalWidth / 2;
|
||||||
@ -152,7 +168,7 @@ export default abstract class ModalDialogBase extends GuiObject {
|
|||||||
const button = new Button(
|
const button = new Button(
|
||||||
new PIXI.Rectangle(
|
new PIXI.Rectangle(
|
||||||
buttonXPos,
|
buttonXPos,
|
||||||
modalBounds.height - this.buttonAreaHeight - this.dialogPadding,
|
modalBounds.height - this.buttonHeight - this.background.bottomHeight,
|
||||||
buttonDef.width,
|
buttonDef.width,
|
||||||
buttonDef.height
|
buttonDef.height
|
||||||
),
|
),
|
||||||
@ -162,7 +178,7 @@ export default abstract class ModalDialogBase extends GuiObject {
|
|||||||
button.onClick = buttonDef.click;
|
button.onClick = buttonDef.click;
|
||||||
this.buttons.push(button);
|
this.buttons.push(button);
|
||||||
this.dialogContainer.addChild(button.container);
|
this.dialogContainer.addChild(button.container);
|
||||||
buttonXPos += buttonDef.width + this.buttonPadding;
|
buttonXPos += buttonDef.width + 10;
|
||||||
}
|
}
|
||||||
this.container.addChild(this.dialogContainer);
|
this.container.addChild(this.dialogContainer);
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,7 @@ export default class TextInput extends GuiObject {
|
|||||||
this.backgroundSprite.x = 0;
|
this.backgroundSprite.x = 0;
|
||||||
this.backgroundSprite.y = 0;
|
this.backgroundSprite.y = 0;
|
||||||
this.backgroundSprite.width = width;
|
this.backgroundSprite.width = width;
|
||||||
this.backgroundSprite.height = 60;
|
this.backgroundSprite.height = 80;
|
||||||
this.container.addChild(this.backgroundSprite);
|
this.container.addChild(this.backgroundSprite);
|
||||||
this.text = new PIXI.Text({
|
this.text = new PIXI.Text({
|
||||||
text: '',
|
text: '',
|
||||||
@ -36,8 +36,8 @@ export default class TextInput extends GuiObject {
|
|||||||
fontSize: 24,
|
fontSize: 24,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
this.text.x = 20;
|
this.text.x = 30;
|
||||||
this.text.y = 20;
|
this.text.y = 25;
|
||||||
this.container.addChild(this.text);
|
this.container.addChild(this.text);
|
||||||
this.keyboardManagerUnsubscribe = KeyboardManager.onKeyPressed(this.onKeyPress.bind(this));
|
this.keyboardManagerUnsubscribe = KeyboardManager.onKeyPressed(this.onKeyPress.bind(this));
|
||||||
}
|
}
|
||||||
|
@ -245,15 +245,17 @@ export class GameScene extends Scene {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async ShowEndgameDialog(lost) {
|
private async ShowEndgameDialog(lost) {
|
||||||
const endGameDialog = new EndGameDialog(lost);
|
const endGameDialog = new EndGameDialog(this.mission.name, this.MissionStats, lost);
|
||||||
await endGameDialog.show();
|
await endGameDialog.show();
|
||||||
const highScore = new HighScoreDialog(this.missionIndex + 1 < GameAssets.Missions.length);
|
const highScore = new HighScoreDialog(this.mission.name, this.missionIndex + 1 < GameAssets.Missions.length);
|
||||||
const result = await highScore.show();
|
const result = await highScore.show();
|
||||||
if (result === HighScoreDialogButtons.MainMenu) {
|
if (result === HighScoreDialogButtons.MainMenu) {
|
||||||
this.ReturnToMain();
|
this.ReturnToMain();
|
||||||
} else if (result === HighScoreDialogButtons.NextMission) {
|
} else if (result === HighScoreDialogButtons.NextMission) {
|
||||||
|
this.destroy();
|
||||||
Engine.GameMaster.changeScene(new GameScene(GameAssets.Missions[this.missionIndex + 1].name));
|
Engine.GameMaster.changeScene(new GameScene(GameAssets.Missions[this.missionIndex + 1].name));
|
||||||
} else if (result === HighScoreDialogButtons.Retry) {
|
} else if (result === HighScoreDialogButtons.Retry) {
|
||||||
|
this.destroy();
|
||||||
Engine.GameMaster.changeScene(new GameScene(this.mission.name));
|
Engine.GameMaster.changeScene(new GameScene(this.mission.name));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user