diff --git a/public/assets/icons/chevron-down.svg b/public/assets/icons/chevron-down.svg new file mode 100644 index 0000000..5e7cbee --- /dev/null +++ b/public/assets/icons/chevron-down.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/assets/maps/intro/layer5.png b/public/assets/maps/intro/layer5.png index b938045..bcf9dbc 100644 Binary files a/public/assets/maps/intro/layer5.png and b/public/assets/maps/intro/layer5.png differ diff --git a/public/assets/maps/intro/train.png b/public/assets/maps/intro/train.png index dbe0d4a..6c61cd1 100644 Binary files a/public/assets/maps/intro/train.png and b/public/assets/maps/intro/train.png differ diff --git a/src/classes/UI/Message.ts b/src/classes/UI/Message.ts index 724791e..0885046 100644 --- a/src/classes/UI/Message.ts +++ b/src/classes/UI/Message.ts @@ -19,6 +19,7 @@ const nameOffset = 40; const maxLines = 5; const timeout = 350; +const fadeDuration = 125; export class Message extends GameObjects.Container { textWidth: number; @@ -28,7 +29,6 @@ export class Message extends GameObjects.Container { target?: any; npcName: GameObjects.Text; text: GameObjects.Text; - box: GameObjects.Rectangle; portrait: GameObjects.Image; options?: string[]; @@ -51,6 +51,7 @@ export class Message extends GameObjects.Container { this.setScrollFactor(0); this.setPosition(padding, height - padding - boxHeight); this.setDepth(Layer.Overlay); + this.setAlpha(0); this.setVisible(false); // Player is not necessary to show basic dialog, but might crash on complex dialogs @@ -73,42 +74,57 @@ export class Message extends GameObjects.Container { color: '#' + Colors.Tan, }); - this.text = new GameObjects.Text(this.scene, padding + portraitOffset, padding + nameOffset, '', fontStyle); + this.text = this.scene.add.text(padding + portraitOffset, padding + nameOffset, '', fontStyle); this.text.width = this.textWidth; this.text.height = this.textHeight; this.text.setOrigin(0).setMaxLines(maxLines); - this.portrait = new GameObjects.Image(this.scene, padding, padding, '').setOrigin(0).setScale(1.5); + this.portrait = this.scene.add.image(padding, padding, '').setOrigin(0).setScale(1.5); - this.box = new GameObjects.Rectangle( - this.scene, - 0, - 0, - Config.width - padding * 2, - boxHeight, - getColorNumber(Colors.Black), - 0.8 - ); - this.box.setStrokeStyle(2, getColorNumber(Colors.Tan), 1); - this.box.setOrigin(0, 0); + const box = this.scene.add + .rectangle(0, 0, Config.width - padding * 2, boxHeight, getColorNumber(Colors.Black), 0.8) + .setStrokeStyle(2, getColorNumber(Colors.Tan), 1) + .setOrigin(0, 0) + .setScrollFactor(0) + .setInteractive({ useHandCursor: true }) + .on('pointerdown', () => { + if (!this.options) this.updateDialog(); + }); this.optionsContainer = new ButtonGroup(this.scene).setDepth(Layer.Overlay); - this.add([this.box, this.npcName, this.text, this.portrait]); + const arrow = this.scene.add.image(Config.width - padding * 2 - 20, boxHeight - 16, 'chevron-down').setScale(0.5); + this.scene.tweens.add({ + targets: arrow, + y: boxHeight - 22, + scale: 0.4, + duration: 1000, + ease: 'Sine.easeInOut', + yoyo: true, + repeat: -1, + }); + + this.add([box, this.npcName, this.text, this.portrait, arrow]); } setDialog(dialog?: Dialog, target?: T, portrait?: string) { if (!this.npcName) this.createUI(); - this.setVisible(dialog !== undefined); + this.setVisible(true); + this.scene.tweens.add({ + targets: this, + alpha: dialog !== undefined ? 1 : 0, + duration: fadeDuration, + onComplete: () => this.setVisible(dialog !== undefined), + }); this.target = target; this.messageIndex = 0; this.dialog = dialog; this.interactionTimeout = Date.now() + timeout; - (this.scene as Game).gamepad?.offsetButtons(this.dialog !== undefined); + (this.scene as Game).gamepad?.setVisible(this.dialog === undefined); if (!dialog) { return; @@ -147,7 +163,20 @@ export class Message extends GameObjects.Container { const message = messages && messages[this.messageIndex]; if (message) { - this.text.setText(message); + this.scene.tweens.add({ + targets: this.text, + alpha: 0, + duration: fadeDuration, + onComplete: () => { + this.text.setText(message); + this.scene.tweens.add({ + targets: this.text, + alpha: 1, + duration: fadeDuration, + }); + }, + }); + if (this.text.getWrappedText().length > maxLines) console.error('Message too long!', message); } @@ -205,14 +234,20 @@ export class Message extends GameObjects.Container { this.dialog.onCompleted(this.player, this.target); } this.dialog = undefined; - this.setVisible(false); + + this.scene.tweens.add({ + targets: this, + alpha: 0, + duration: fadeDuration, + onComplete: () => this.setVisible(false), + }); (this.scene as Game).gamepad?.resetButtons(); } else { this.showMessage(); } - (this.scene as Game).gamepad?.offsetButtons(this.dialog !== undefined); + (this.scene as Game).gamepad?.setVisible(this.dialog === undefined); this.interactionTimeout = Date.now() + timeout; } diff --git a/src/scenes/Intro.ts b/src/scenes/Intro.ts index 48c6faf..4be7c85 100644 --- a/src/scenes/Intro.ts +++ b/src/scenes/Intro.ts @@ -4,6 +4,17 @@ import { Config } from '../config'; import { trainIntro } from '../data/cutscene'; import { fadeIn } from '../utils/util'; +// Export the preload function (so it can be used in the main Preloader) +export function preloadIntro(scene: Scene) { + scene.load.image('train', 'maps/intro/train.png'); + + scene.load.image('layer1', 'maps/intro/layer1.png'); + scene.load.image('layer2', 'maps/intro/layer2.png'); + scene.load.image('layer3', 'maps/intro/layer3.png'); + scene.load.image('layer4', 'maps/intro/layer4.png'); + scene.load.image('layer5', 'maps/intro/layer5.png'); +} + export class Intro extends Scene { player: GameObjects.Sprite; @@ -16,18 +27,7 @@ export class Intro extends Scene { } preload() { - this.load.setPath('assets'); - - this.load.image('train', 'maps/intro/train.png'); - - this.load.image('layer1', 'maps/intro/layer1.png'); - this.load.image('layer2', 'maps/intro/layer2.png'); - this.load.image('layer3', 'maps/intro/layer3.png'); - this.load.image('layer4', 'maps/intro/layer4.png'); - this.load.image('layer5', 'maps/intro/layer5.png'); - - this.load.spritesheet('character', 'characters/player.png', { frameWidth: 128, frameHeight: 80 }); - this.load.image('player_portrait', 'characters/player_portrait.png'); + preloadIntro(this); } create() { @@ -114,7 +114,4 @@ export class Intro extends Scene { trainIntro(this, this.player); } - - // update(time: number, delta: number): void { - // } } diff --git a/src/scenes/Preloader.ts b/src/scenes/Preloader.ts index ddab0e8..b8d5b61 100644 --- a/src/scenes/Preloader.ts +++ b/src/scenes/Preloader.ts @@ -3,6 +3,7 @@ import { GameObjects, Scene } from 'phaser'; import { Config } from '../config'; import { saveKey } from '../data/saves'; import { fadeOut } from '../utils/util'; +import { preloadIntro } from './Intro'; export class Preloader extends Scene { container: GameObjects.Container; @@ -72,6 +73,7 @@ export class Preloader extends Scene { this.load.svg('award', 'icons/award.svg', { width: 64, height: 64 }); this.load.svg('tv', 'icons/tv.svg', { width: 64, height: 64 }); this.load.svg('save', 'icons/save.svg', { width: 64, height: 64 }); + this.load.svg('chevron-down', 'icons/chevron-down.svg', { width: 64, height: 64 }); // fontawesome icons this.load.svg('gamepad', 'icons/gamepad-solid.svg', { width: 64, height: 64 }); @@ -141,6 +143,11 @@ export class Preloader extends Scene { // puzzles this.load.image('arrow', 'puzzles/arrow.png'); + + // optionally preload intro + if (!localStorage.getItem(saveKey)) { + preloadIntro(this); + } } create() { diff --git a/src/scenes/dialogs/Paused.ts b/src/scenes/dialogs/Paused.ts index 3d02f86..ae93074 100644 --- a/src/scenes/dialogs/Paused.ts +++ b/src/scenes/dialogs/Paused.ts @@ -55,7 +55,7 @@ export class Paused extends Scene { } ) .setOrigin(1, 1) - .setInteractive({ useHandCursor: false }) + .setInteractive({ useHandCursor: !import.meta.env.PROD }) .on('pointerdown', () => { this.debugCount++; if (this.debugCount > 10) {