285 lines
8.6 KiB
JavaScript
285 lines
8.6 KiB
JavaScript
import Phaser from 'phaser';
|
|
import { fontSize } from '../utils/responsive.js';
|
|
import { createFullscreenButton } from '../utils/fullscreen.js';
|
|
|
|
export default class DeepSeaHuntingScene extends Phaser.Scene {
|
|
constructor() {
|
|
super({ key: 'DeepSeaHuntingScene' });
|
|
}
|
|
|
|
init(data) {
|
|
this.inventory = data.inventory || { whaleOil: 0, fuel: 100, penguins: 0 };
|
|
this.whalesHunted = 0;
|
|
this.currentWhale = null;
|
|
}
|
|
|
|
create() {
|
|
// Dark deep ocean background
|
|
this.add.rectangle(400, 300, 800, 600, 0x0a1a2a);
|
|
|
|
// Create atmospheric effects
|
|
this.createDeepOceanAtmosphere();
|
|
|
|
// UI elements
|
|
this.createHUD();
|
|
this.createInstructions();
|
|
|
|
// Fullscreen button
|
|
createFullscreenButton(this);
|
|
|
|
// Spawn first whale
|
|
this.spawnWhale();
|
|
|
|
// Show initial message
|
|
this.showMessage('The leviathan approaches... Watch the depths!');
|
|
}
|
|
|
|
createDeepOceanAtmosphere() {
|
|
// Gradient darkness
|
|
for (let i = 0; i < 6; i++) {
|
|
const alpha = 0.05 + i * 0.03;
|
|
this.add.rectangle(400, 500 - i * 80, 800, 100, 0x000000, alpha);
|
|
}
|
|
|
|
// Subtle waves
|
|
const graphics = this.add.graphics();
|
|
graphics.lineStyle(2, 0x1a3a5a, 0.3);
|
|
|
|
for (let i = 0; i < 8; i++) {
|
|
const y = 80 + i * 70;
|
|
graphics.beginPath();
|
|
for (let x = 0; x < 800; x += 30) {
|
|
const waveY = y + Math.sin((x + i * 50) * 0.012) * 8;
|
|
if (x === 0) {
|
|
graphics.moveTo(x, waveY);
|
|
} else {
|
|
graphics.lineTo(x, waveY);
|
|
}
|
|
}
|
|
graphics.strokePath();
|
|
}
|
|
|
|
// Bioluminescent particles
|
|
for (let i = 0; i < 20; i++) {
|
|
const x = Math.random() * 800;
|
|
const y = 100 + Math.random() * 400;
|
|
const particle = this.add.circle(x, y, 2 + Math.random() * 2, 0x4a9fff, 0.5);
|
|
|
|
this.tweens.add({
|
|
targets: particle,
|
|
alpha: { from: 0.5, to: 0.1 },
|
|
y: y - 30,
|
|
duration: 3000 + Math.random() * 2000,
|
|
yoyo: true,
|
|
repeat: -1,
|
|
ease: 'Sine.inOut'
|
|
});
|
|
}
|
|
}
|
|
|
|
spawnWhale() {
|
|
// Get random entry and exit points on different edges
|
|
const edges = ['top', 'bottom', 'left', 'right'];
|
|
const entryEdge = edges[Math.floor(Math.random() * edges.length)];
|
|
|
|
// Pick a different edge for exit
|
|
const exitEdges = edges.filter(e => e !== entryEdge);
|
|
const exitEdge = exitEdges[Math.floor(Math.random() * exitEdges.length)];
|
|
|
|
const entry = this.getEdgePoint(entryEdge);
|
|
const exit = this.getEdgePoint(exitEdge);
|
|
|
|
// Calculate angle for whale rotation
|
|
const angle = Phaser.Math.Angle.Between(entry.x, entry.y, exit.x, exit.y);
|
|
|
|
// Create whale container
|
|
const whale = this.add.container(entry.x, entry.y);
|
|
|
|
// Whale body (large deep sea whale)
|
|
const body = this.add.ellipse(0, 0, 180, 70, 0x1a2a3a);
|
|
body.setStrokeStyle(2, 0x2a4a6a);
|
|
body.disableInteractive();
|
|
|
|
// Whale underbelly
|
|
const belly = this.add.ellipse(0, 10, 140, 40, 0x2a3a4a);
|
|
|
|
// Tail
|
|
const tail = this.add.triangle(-100, 0, 0, -35, 0, 35, -50, 0, 0x1a2a3a);
|
|
|
|
// Dorsal fin
|
|
const dorsalFin = this.add.triangle(20, -25, 0, 0, 30, 0, 15, -30, 0x1a2a3a);
|
|
|
|
// Eye (bioluminescent glow)
|
|
const eyeGlow = this.add.circle(60, -10, 12, 0x4a9fff, 0.3);
|
|
const eye = this.add.circle(60, -10, 6, 0x6abfff);
|
|
|
|
// Add glow effect to eye
|
|
this.tweens.add({
|
|
targets: eyeGlow,
|
|
alpha: { from: 0.3, to: 0.6 },
|
|
scale: { from: 1, to: 1.3 },
|
|
duration: 1000,
|
|
yoyo: true,
|
|
repeat: -1,
|
|
ease: 'Sine.inOut'
|
|
});
|
|
|
|
whale.add([tail, body, belly, dorsalFin, eyeGlow, eye]);
|
|
whale.setRotation(angle);
|
|
whale.setDepth(10);
|
|
|
|
this.currentWhale = whale;
|
|
|
|
// Subtle bobbing animation during movement
|
|
this.tweens.add({
|
|
targets: whale,
|
|
scaleY: { from: 1, to: 1.05 },
|
|
duration: 800,
|
|
yoyo: true,
|
|
repeat: -1,
|
|
ease: 'Sine.inOut'
|
|
});
|
|
|
|
// Create curved path from entry to exit
|
|
const path = new Phaser.Curves.Path(entry.x, entry.y);
|
|
|
|
// Calculate midpoint and perpendicular offset for curve
|
|
const midX = (entry.x + exit.x) / 2;
|
|
const midY = (entry.y + exit.y) / 2;
|
|
|
|
// Create perpendicular offset for natural swimming curve
|
|
const dx = exit.x - entry.x;
|
|
const dy = exit.y - entry.y;
|
|
const dist = Math.sqrt(dx * dx + dy * dy);
|
|
|
|
// Perpendicular direction (normalized)
|
|
const perpX = -dy / dist;
|
|
const perpY = dx / dist;
|
|
|
|
// Random curve amplitude (positive or negative for variety)
|
|
const curveAmount = (Math.random() > 0.5 ? 1 : -1) * (80 + Math.random() * 120);
|
|
|
|
// Control points for smooth S-curve
|
|
const ctrl1X = entry.x + dx * 0.25 + perpX * curveAmount;
|
|
const ctrl1Y = entry.y + dy * 0.25 + perpY * curveAmount;
|
|
const ctrl2X = entry.x + dx * 0.75 - perpX * curveAmount * 0.7;
|
|
const ctrl2Y = entry.y + dy * 0.75 - perpY * curveAmount * 0.7;
|
|
|
|
// Add cubic bezier curve
|
|
path.cubicBezierTo(exit.x, exit.y, ctrl1X, ctrl1Y, ctrl2X, ctrl2Y);
|
|
|
|
// Store path progress
|
|
const pathData = { t: 0 };
|
|
|
|
// Animate along curved path
|
|
this.tweens.add({
|
|
targets: pathData,
|
|
t: 1,
|
|
duration: 15000,
|
|
ease: 'Sine.inOut',
|
|
onUpdate: () => {
|
|
const point = path.getPoint(pathData.t);
|
|
const tangent = path.getTangent(pathData.t);
|
|
|
|
whale.setPosition(point.x, point.y);
|
|
whale.setRotation(Math.atan2(tangent.y, tangent.x));
|
|
},
|
|
onComplete: () => {
|
|
this.onWhaleExit(whale);
|
|
}
|
|
});
|
|
}
|
|
|
|
getEdgePoint(edge) {
|
|
const margin = 100; // How far off-screen to spawn
|
|
const padding = 100; // Padding from corners
|
|
|
|
switch (edge) {
|
|
case 'top':
|
|
return {
|
|
x: padding + Math.random() * (800 - padding * 2),
|
|
y: -margin
|
|
};
|
|
case 'bottom':
|
|
return {
|
|
x: padding + Math.random() * (800 - padding * 2),
|
|
y: 600 + margin
|
|
};
|
|
case 'left':
|
|
return {
|
|
x: -margin,
|
|
y: padding + Math.random() * (600 - padding * 2)
|
|
};
|
|
case 'right':
|
|
return {
|
|
x: 800 + margin,
|
|
y: padding + Math.random() * (600 - padding * 2)
|
|
};
|
|
}
|
|
}
|
|
|
|
onWhaleExit(whale) {
|
|
// Destroy the whale
|
|
whale.destroy();
|
|
this.currentWhale = null;
|
|
|
|
// Respawn after 2 seconds
|
|
this.time.delayedCall(2000, () => {
|
|
this.spawnWhale();
|
|
});
|
|
}
|
|
|
|
createHUD() {
|
|
// HUD background
|
|
const hudBg = this.add.rectangle(400, 30, 780, 50, 0x000000, 0.7);
|
|
hudBg.setStrokeStyle(2, 0x4a9fff);
|
|
|
|
// Stats
|
|
this.statsText = this.add.text(20, 15, '', {
|
|
fontSize: fontSize(16),
|
|
fill: '#4a9fff'
|
|
});
|
|
|
|
// Return button
|
|
const returnBtn = this.add.rectangle(750, 30, 80, 35, 0x1a3a5a);
|
|
returnBtn.setInteractive({ useHandCursor: true });
|
|
returnBtn.setStrokeStyle(2, 0x4a9fff);
|
|
|
|
const returnText = this.add.text(750, 30, 'RETURN', {
|
|
fontSize: fontSize(14),
|
|
fill: '#4a9fff'
|
|
}).setOrigin(0.5);
|
|
|
|
returnBtn.on('pointerdown', () => {
|
|
this.returnToMap();
|
|
});
|
|
|
|
this.updateStats();
|
|
}
|
|
|
|
updateStats() {
|
|
this.statsText.setText([
|
|
`Fuel: ${this.inventory.fuel}/100`,
|
|
`Oil: ${this.inventory.whaleOil}/50`,
|
|
`Leviathans: ${this.whalesHunted}`
|
|
]);
|
|
}
|
|
|
|
createInstructions() {
|
|
this.messageText = this.add.text(400, 570, '', {
|
|
fontSize: fontSize(16),
|
|
fill: '#4a9fff',
|
|
backgroundColor: '#000000',
|
|
padding: { x: 10, y: 5 }
|
|
}).setOrigin(0.5);
|
|
}
|
|
|
|
showMessage(text) {
|
|
this.messageText.setText(text);
|
|
}
|
|
|
|
returnToMap() {
|
|
this.scene.start('MapScene', { inventory: this.inventory });
|
|
}
|
|
}
|