Merge splash and start screen in to menu screen
This commit is contained in:
@@ -1,8 +1,7 @@
|
|||||||
import Phaser from "phaser";
|
import Phaser from "phaser";
|
||||||
import GameUI from "./ui/GameUI";
|
import GameUI from "./ui/GameUI";
|
||||||
import { GameScene } from "./scenes/GameScene";
|
import { GameScene } from "./scenes/GameScene";
|
||||||
import { SplashScene } from "./scenes/SplashScene";
|
import { MenuScene } from "./scenes/MenuScene";
|
||||||
import { StartScene } from "./scenes/StartScene";
|
|
||||||
|
|
||||||
new Phaser.Game({
|
new Phaser.Game({
|
||||||
type: Phaser.AUTO,
|
type: Phaser.AUTO,
|
||||||
@@ -15,5 +14,5 @@ new Phaser.Game({
|
|||||||
backgroundColor: "#111",
|
backgroundColor: "#111",
|
||||||
pixelArt: true,
|
pixelArt: true,
|
||||||
roundPixels: true,
|
roundPixels: true,
|
||||||
scene: [SplashScene, StartScene, GameScene, GameUI]
|
scene: [MenuScene, GameScene, GameUI]
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ export class GameScene extends Phaser.Scene {
|
|||||||
private playerId!: EntityId;
|
private playerId!: EntityId;
|
||||||
|
|
||||||
private floorIndex = 1;
|
private floorIndex = 1;
|
||||||
private gameState: "playing" | "player-turn" | "enemy-turn" = "player-turn";
|
|
||||||
|
|
||||||
private runState: RunState = {
|
private runState: RunState = {
|
||||||
stats: { ...GAME_CONFIG.player.initialStats },
|
stats: { ...GAME_CONFIG.player.initialStats },
|
||||||
@@ -52,6 +51,7 @@ export class GameScene extends Phaser.Scene {
|
|||||||
|
|
||||||
// Camera
|
// Camera
|
||||||
this.cameras.main.setZoom(GAME_CONFIG.rendering.cameraZoom);
|
this.cameras.main.setZoom(GAME_CONFIG.rendering.cameraZoom);
|
||||||
|
this.cameras.main.fadeIn(1000, 0, 0, 0);
|
||||||
|
|
||||||
// Initialize Sub-systems
|
// Initialize Sub-systems
|
||||||
this.dungeonRenderer = new DungeonRenderer(this);
|
this.dungeonRenderer = new DungeonRenderer(this);
|
||||||
|
|||||||
255
src/scenes/MenuScene.ts
Normal file
255
src/scenes/MenuScene.ts
Normal file
@@ -0,0 +1,255 @@
|
|||||||
|
import Phaser from "phaser";
|
||||||
|
|
||||||
|
export class MenuScene extends Phaser.Scene {
|
||||||
|
private background!: Phaser.GameObjects.Image;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super("MenuScene");
|
||||||
|
}
|
||||||
|
|
||||||
|
preload() {
|
||||||
|
this.load.image('splash_bg', 'splash_bg.png');
|
||||||
|
}
|
||||||
|
|
||||||
|
create() {
|
||||||
|
const { width, height } = this.scale;
|
||||||
|
|
||||||
|
// Restore Splash Background
|
||||||
|
if (this.textures.exists('splash_bg')) {
|
||||||
|
this.background = this.add.image(width / 2, height / 2, 'splash_bg');
|
||||||
|
const scale = Math.max(width / this.background.width, height / this.background.height);
|
||||||
|
this.background.setScale(scale);
|
||||||
|
|
||||||
|
// Add a slight tint to make the UI pop more
|
||||||
|
this.background.setTint(0xcccccc);
|
||||||
|
} else {
|
||||||
|
// Fallback gradient if image fails
|
||||||
|
const graphics = this.add.graphics();
|
||||||
|
graphics.fillGradientStyle(0x0a0510, 0x0a0510, 0x1a0a2a, 0x1a0a2a, 1);
|
||||||
|
graphics.fillRect(0, 0, width, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Atmospheric Effects
|
||||||
|
this.createWindEffect();
|
||||||
|
this.createSmokeEffect(width, height);
|
||||||
|
this.createAtmosphere(width, height);
|
||||||
|
|
||||||
|
this.cameras.main.fadeIn(1000, 0, 0, 0);
|
||||||
|
|
||||||
|
// Stylish Title
|
||||||
|
const title = this.add.text(width / 2, height * 0.35, "ROGUE", {
|
||||||
|
fontSize: "96px",
|
||||||
|
color: "#ff2266",
|
||||||
|
fontStyle: "bold",
|
||||||
|
fontFamily: "Georgia, serif",
|
||||||
|
stroke: "#111",
|
||||||
|
strokeThickness: 10,
|
||||||
|
shadow: { blur: 30, color: "#ff0044", fill: true, offsetX: 0, offsetY: 0 }
|
||||||
|
}).setOrigin(0.5);
|
||||||
|
|
||||||
|
// Animate title (Slight float)
|
||||||
|
this.tweens.add({
|
||||||
|
targets: title,
|
||||||
|
y: height * 0.33,
|
||||||
|
duration: 3000,
|
||||||
|
ease: 'Sine.easeInOut',
|
||||||
|
yoyo: true,
|
||||||
|
loop: -1
|
||||||
|
});
|
||||||
|
|
||||||
|
// Buttons
|
||||||
|
const buttonYStart = height * 0.65;
|
||||||
|
const startBtn = this.createButton(width / 2, buttonYStart, "ENTER DUNGEON", 0x2288ff);
|
||||||
|
const optBtn = this.createButton(width / 2, buttonYStart + 80, "OPTIONS", 0x444444);
|
||||||
|
|
||||||
|
startBtn.on("pointerdown", () => {
|
||||||
|
this.cameras.main.fadeOut(1000, 0, 0, 0);
|
||||||
|
this.cameras.main.once(Phaser.Cameras.Scene2D.Events.FADE_OUT_COMPLETE, () => {
|
||||||
|
this.scene.start("GameScene");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
optBtn.on("pointerdown", () => {
|
||||||
|
console.log("Options clicked");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private createWindEffect() {
|
||||||
|
if (!this.background) return;
|
||||||
|
|
||||||
|
// Subtle swaying of the background to simulate wind/heat haze
|
||||||
|
this.tweens.add({
|
||||||
|
targets: this.background,
|
||||||
|
x: (this.scale.width / 2) + 10,
|
||||||
|
duration: 4000,
|
||||||
|
ease: 'Sine.easeInOut',
|
||||||
|
yoyo: true,
|
||||||
|
loop: -1
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private createSmokeEffect(width: number, height: number) {
|
||||||
|
// Create many tiny, soft smoke particles instead of big circles
|
||||||
|
for (let i = 0; i < 60; i++) {
|
||||||
|
const x = Phaser.Math.Between(0, width);
|
||||||
|
const y = height + Phaser.Math.Between(0, 400);
|
||||||
|
const size = Phaser.Math.Between(15, 40);
|
||||||
|
const smoke = this.add.circle(x, y, size, 0xdddddd, 0.03);
|
||||||
|
|
||||||
|
this.tweens.add({
|
||||||
|
targets: smoke,
|
||||||
|
y: -200,
|
||||||
|
x: x + Phaser.Math.Between(-150, 150),
|
||||||
|
alpha: 0,
|
||||||
|
scale: 2.5,
|
||||||
|
duration: Phaser.Math.Between(6000, 12000),
|
||||||
|
ease: 'Linear',
|
||||||
|
loop: -1,
|
||||||
|
delay: Phaser.Math.Between(0, 10000)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add "Heat Haze" / Distant Smoke
|
||||||
|
for (let i = 0; i < 30; i++) {
|
||||||
|
const x = Phaser.Math.Between(0, width);
|
||||||
|
const y = Phaser.Math.Between(height * 0.4, height);
|
||||||
|
const haze = this.add.circle(x, y, Phaser.Math.Between(40, 80), 0xeeeeee, 0.01);
|
||||||
|
|
||||||
|
this.tweens.add({
|
||||||
|
targets: haze,
|
||||||
|
alpha: 0.04,
|
||||||
|
scale: 1.2,
|
||||||
|
x: x + 20,
|
||||||
|
duration: Phaser.Math.Between(3000, 6000),
|
||||||
|
ease: 'Sine.easeInOut',
|
||||||
|
yoyo: true,
|
||||||
|
loop: -1,
|
||||||
|
delay: Phaser.Math.Between(0, 3000)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private createAtmosphere(width: number, height: number) {
|
||||||
|
// Drifting Embers (Fire sparks)
|
||||||
|
for (let i = 0; i < 40; i++) {
|
||||||
|
const x = Phaser.Math.Between(0, width);
|
||||||
|
const y = height + Phaser.Math.Between(0, 200);
|
||||||
|
const color = Phaser.Math.RND.pick([0xff4400, 0xffaa00, 0xffffff]);
|
||||||
|
const ember = this.add.circle(x, y, Phaser.Math.Between(1, 2), color, 0.8);
|
||||||
|
|
||||||
|
// Drift diagonally to simulate wind
|
||||||
|
this.tweens.add({
|
||||||
|
targets: ember,
|
||||||
|
y: -100,
|
||||||
|
x: x - Phaser.Math.Between(100, 300),
|
||||||
|
alpha: 0,
|
||||||
|
duration: Phaser.Math.Between(4000, 7000),
|
||||||
|
ease: 'Cubic.easeOut',
|
||||||
|
loop: -1,
|
||||||
|
delay: Phaser.Math.Between(0, 5000)
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add a little flicker/wobble
|
||||||
|
this.tweens.add({
|
||||||
|
targets: ember,
|
||||||
|
alpha: 0.2,
|
||||||
|
duration: 200,
|
||||||
|
yoyo: true,
|
||||||
|
loop: -1,
|
||||||
|
delay: Phaser.Math.Between(0, 500)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Subtle Distant Ash (White particles)
|
||||||
|
for (let i = 0; i < 20; i++) {
|
||||||
|
const x = Phaser.Math.Between(0, width);
|
||||||
|
const y = -10;
|
||||||
|
const ash = this.add.circle(x, y, 1, 0xffffff, 0.3);
|
||||||
|
|
||||||
|
this.tweens.add({
|
||||||
|
targets: ash,
|
||||||
|
y: height + 10,
|
||||||
|
x: x - 100,
|
||||||
|
duration: Phaser.Math.Between(8000, 15000),
|
||||||
|
ease: 'Linear',
|
||||||
|
loop: -1,
|
||||||
|
delay: Phaser.Math.Between(0, 8000)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private createButton(x: number, y: number, text: string, accentColor: number) {
|
||||||
|
const width = 280;
|
||||||
|
const height = 58;
|
||||||
|
|
||||||
|
const bg = this.add.graphics();
|
||||||
|
this.drawButtonShape(bg, width, height, 0x000000);
|
||||||
|
bg.setAlpha(0.7);
|
||||||
|
|
||||||
|
const border = this.add.graphics();
|
||||||
|
border.lineStyle(2, 0x666666, 0.8);
|
||||||
|
this.drawButtonBorder(border, width, height);
|
||||||
|
|
||||||
|
const accent = this.add.graphics();
|
||||||
|
accent.fillStyle(accentColor, 1);
|
||||||
|
accent.fillRect(-width/2, -height/2, 4, height);
|
||||||
|
accent.setAlpha(0.6);
|
||||||
|
|
||||||
|
const txt = this.add.text(0, 0, text, {
|
||||||
|
fontSize: "18px",
|
||||||
|
color: "#ffffff",
|
||||||
|
fontFamily: "Verdana, Geneva, sans-serif",
|
||||||
|
letterSpacing: 3,
|
||||||
|
fontStyle: "bold"
|
||||||
|
}).setOrigin(0.5);
|
||||||
|
|
||||||
|
const container = this.add.container(x, y, [bg, border, accent, txt]);
|
||||||
|
container.setSize(width, height);
|
||||||
|
container.setInteractive({ useHandCursor: true });
|
||||||
|
|
||||||
|
container.on("pointerover", () => {
|
||||||
|
this.tweens.add({
|
||||||
|
targets: [bg, border],
|
||||||
|
alpha: 1,
|
||||||
|
duration: 200
|
||||||
|
});
|
||||||
|
this.tweens.add({
|
||||||
|
targets: accent,
|
||||||
|
alpha: 1,
|
||||||
|
scaleX: 2,
|
||||||
|
duration: 200
|
||||||
|
});
|
||||||
|
border.clear();
|
||||||
|
border.lineStyle(2, accentColor, 1);
|
||||||
|
this.drawButtonBorder(border, width, height);
|
||||||
|
});
|
||||||
|
|
||||||
|
container.on("pointerout", () => {
|
||||||
|
this.tweens.add({
|
||||||
|
targets: [bg, border],
|
||||||
|
alpha: 0.7,
|
||||||
|
duration: 200
|
||||||
|
});
|
||||||
|
this.tweens.add({
|
||||||
|
targets: accent,
|
||||||
|
alpha: 0.6,
|
||||||
|
scaleX: 1,
|
||||||
|
duration: 200
|
||||||
|
});
|
||||||
|
border.clear();
|
||||||
|
border.lineStyle(2, 0x666666, 0.8);
|
||||||
|
this.drawButtonBorder(border, width, height);
|
||||||
|
});
|
||||||
|
|
||||||
|
return container;
|
||||||
|
}
|
||||||
|
|
||||||
|
private drawButtonShape(g: Phaser.GameObjects.Graphics, w: number, h: number, color: number) {
|
||||||
|
g.fillStyle(color, 1);
|
||||||
|
g.fillRect(-w/2, -h/2, w, h);
|
||||||
|
}
|
||||||
|
|
||||||
|
private drawButtonBorder(g: Phaser.GameObjects.Graphics, w: number, h: number) {
|
||||||
|
g.strokeRect(-w/2, -h/2, w, h);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
import Phaser from "phaser";
|
|
||||||
import { Scene } from 'phaser';
|
|
||||||
|
|
||||||
export class SplashScene extends Scene {
|
|
||||||
constructor() {
|
|
||||||
super("SplashScene");
|
|
||||||
}
|
|
||||||
|
|
||||||
preload() {
|
|
||||||
this.load.image('splash', 'splash_bg.png');
|
|
||||||
}
|
|
||||||
|
|
||||||
create() {
|
|
||||||
const { width, height } = this.scale;
|
|
||||||
|
|
||||||
// Background (Placeholder for Image)
|
|
||||||
// If we successfully load the image 'splash', we use it.
|
|
||||||
if (this.textures.exists('splash')) {
|
|
||||||
const splash = this.add.image(width / 2, height / 2, 'splash');
|
|
||||||
|
|
||||||
// Scale to cover the screen while maintaining aspect ratio
|
|
||||||
const scaleX = width / splash.width;
|
|
||||||
const scaleY = height / splash.height;
|
|
||||||
const scale = Math.max(scaleX, scaleY);
|
|
||||||
splash.setScale(scale);
|
|
||||||
} else {
|
|
||||||
this.add.rectangle(0, 0, width, height, 0x110022).setOrigin(0);
|
|
||||||
this.add.text(width/2, height/2, "ROGUE LEGACY", {
|
|
||||||
fontSize: "48px",
|
|
||||||
color: "#ffffff",
|
|
||||||
fontStyle: "bold"
|
|
||||||
}).setOrigin(0.5);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fade In
|
|
||||||
this.cameras.main.fadeIn(1000, 0, 0, 0);
|
|
||||||
|
|
||||||
// Fade Out after delay
|
|
||||||
this.time.delayedCall(2500, () => {
|
|
||||||
this.cameras.main.fadeOut(1000, 0, 0, 0);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.cameras.main.once(Phaser.Cameras.Scene2D.Events.FADE_OUT_COMPLETE, () => {
|
|
||||||
this.scene.start("StartScene");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
import Phaser from "phaser";
|
|
||||||
|
|
||||||
export class StartScene extends Phaser.Scene {
|
|
||||||
constructor() {
|
|
||||||
super("StartScene");
|
|
||||||
}
|
|
||||||
|
|
||||||
create() {
|
|
||||||
const { width, height } = this.scale;
|
|
||||||
|
|
||||||
this.cameras.main.fadeIn(500, 0, 0, 0);
|
|
||||||
|
|
||||||
// Title
|
|
||||||
this.add.text(width / 2, height * 0.3, "ROGUE", {
|
|
||||||
fontSize: "64px",
|
|
||||||
color: "#ff0044",
|
|
||||||
fontStyle: "bold",
|
|
||||||
stroke: "#ffffff",
|
|
||||||
strokeThickness: 4
|
|
||||||
}).setOrigin(0.5);
|
|
||||||
|
|
||||||
// Buttons
|
|
||||||
const startBtn = this.createButton(width / 2, height * 0.55, "Start Game");
|
|
||||||
const optBtn = this.createButton(width / 2, height * 0.65, "Options");
|
|
||||||
|
|
||||||
startBtn.on("pointerdown", () => {
|
|
||||||
this.scene.start("GameScene");
|
|
||||||
});
|
|
||||||
|
|
||||||
optBtn.on("pointerdown", () => {
|
|
||||||
console.log("Options clicked");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private createButton(x: number, y: number, text: string) {
|
|
||||||
const bg = this.add.rectangle(0, 0, 200, 50, 0x333333).setStrokeStyle(2, 0xffffff);
|
|
||||||
const txt = this.add.text(0, 0, text, { fontSize: "24px", color: "#ffffff" }).setOrigin(0.5);
|
|
||||||
|
|
||||||
const container = this.add.container(x, y, [bg, txt]);
|
|
||||||
container.setSize(200, 50);
|
|
||||||
container.setInteractive({ useHandCursor: true });
|
|
||||||
|
|
||||||
container.on("pointerover", () => {
|
|
||||||
bg.setFillStyle(0x555555);
|
|
||||||
});
|
|
||||||
container.on("pointerout", () => {
|
|
||||||
bg.setFillStyle(0x333333);
|
|
||||||
});
|
|
||||||
|
|
||||||
return container;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -27,6 +27,7 @@ vi.mock('phaser', () => {
|
|||||||
setZoom: vi.fn(),
|
setZoom: vi.fn(),
|
||||||
setBounds: vi.fn(),
|
setBounds: vi.fn(),
|
||||||
centerOn: vi.fn(),
|
centerOn: vi.fn(),
|
||||||
|
fadeIn: vi.fn(),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
scene = {
|
scene = {
|
||||||
|
|||||||
Reference in New Issue
Block a user