Merge splash and start screen in to menu screen
This commit is contained in:
@@ -1,8 +1,7 @@
|
||||
import Phaser from "phaser";
|
||||
import GameUI from "./ui/GameUI";
|
||||
import { GameScene } from "./scenes/GameScene";
|
||||
import { SplashScene } from "./scenes/SplashScene";
|
||||
import { StartScene } from "./scenes/StartScene";
|
||||
import { MenuScene } from "./scenes/MenuScene";
|
||||
|
||||
new Phaser.Game({
|
||||
type: Phaser.AUTO,
|
||||
@@ -15,5 +14,5 @@ new Phaser.Game({
|
||||
backgroundColor: "#111",
|
||||
pixelArt: 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 floorIndex = 1;
|
||||
private gameState: "playing" | "player-turn" | "enemy-turn" = "player-turn";
|
||||
|
||||
private runState: RunState = {
|
||||
stats: { ...GAME_CONFIG.player.initialStats },
|
||||
@@ -52,6 +51,7 @@ export class GameScene extends Phaser.Scene {
|
||||
|
||||
// Camera
|
||||
this.cameras.main.setZoom(GAME_CONFIG.rendering.cameraZoom);
|
||||
this.cameras.main.fadeIn(1000, 0, 0, 0);
|
||||
|
||||
// Initialize Sub-systems
|
||||
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(),
|
||||
setBounds: vi.fn(),
|
||||
centerOn: vi.fn(),
|
||||
fadeIn: vi.fn(),
|
||||
},
|
||||
};
|
||||
scene = {
|
||||
|
||||
Reference in New Issue
Block a user