Implemented full mobile optimization to make the game mobile-ready:
**Core Mobile Support:**
- Updated viewport meta tag to prevent zoom and improve touch responsiveness
- Added mobile web app meta tags for iOS/Android standalone mode
- Enhanced CSS to prevent text selection, pull-to-refresh, and tap highlights
- Configured Phaser scale system with min/max bounds (320x240 to 1920x1080)
- Added fullscreen support for game container
**Touch Controls:**
- Added mobile device detection to HuntingScene
- Automatically default to touch controls on mobile devices
- Hide keyboard control instructions on mobile
- Only hide cursor on desktop (preserve default cursor on mobile)
- Adjusted messaging for mobile ("Tap to shoot" vs "Click to shoot")
**Touch Feedback:**
- Added pointerdown/pointerup handlers to all interactive buttons
- Buttons now scale down when pressed (0.95x) for tactile feedback
- Maintained hover effects for desktop compatibility
- Updated IntroScene "SET SAIL" button with touch feedback
- Updated MapScene location markers and close button
- Updated TransitionScene continue button
- All touch feedback works on both touch and mouse inputs
**Backward Compatibility:**
- All desktop functionality preserved
- Hover effects still work on desktop
- Keyboard controls available on desktop
- Touch and mouse inputs work seamlessly together
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
304 lines
9.9 KiB
JavaScript
304 lines
9.9 KiB
JavaScript
import Phaser from 'phaser';
|
|
|
|
export default class TransitionScene extends Phaser.Scene {
|
|
constructor() {
|
|
super({ key: 'TransitionScene' });
|
|
}
|
|
|
|
init(data) {
|
|
// Receive data about the destination
|
|
this.inventory = data.inventory || { whaleOil: 0, fuel: 100, penguins: 0 };
|
|
this.destination = data.destination || 'unknown';
|
|
this.fuelCost = data.fuelCost || 0;
|
|
this.returnScene = data.returnScene || 'MapScene';
|
|
this.nextScene = data.nextScene || 'MapScene';
|
|
}
|
|
|
|
create() {
|
|
// Get destination-specific content
|
|
const content = this.getDestinationContent(this.destination);
|
|
|
|
// Background based on destination
|
|
this.add.rectangle(400, 300, 800, 600, content.backgroundColor);
|
|
|
|
// Add destination-specific visual elements
|
|
this.createDestinationVisuals(this.destination, content);
|
|
|
|
// Journey text container
|
|
const textBox = this.add.rectangle(400, 450, 700, 200, 0x000000, 0.85);
|
|
textBox.setStrokeStyle(3, 0xffffff);
|
|
|
|
// Destination title
|
|
this.add.text(400, 360, content.title, {
|
|
fontSize: '32px',
|
|
fill: '#ffffff',
|
|
fontStyle: 'bold',
|
|
stroke: '#000000',
|
|
strokeThickness: 4
|
|
}).setOrigin(0.5);
|
|
|
|
// Journey description
|
|
this.add.text(400, 430, content.description, {
|
|
fontSize: '18px',
|
|
fill: '#ffffff',
|
|
align: 'center',
|
|
wordWrap: { width: 660 }
|
|
}).setOrigin(0.5);
|
|
|
|
// Fuel cost display (only if there's a cost)
|
|
if (this.fuelCost > 0) {
|
|
this.add.text(400, 500, `Fuel consumed: ${this.fuelCost} units`, {
|
|
fontSize: '16px',
|
|
fill: '#ffff00'
|
|
}).setOrigin(0.5);
|
|
} else {
|
|
this.add.text(400, 500, 'The wind carries your sails...', {
|
|
fontSize: '16px',
|
|
fill: '#cccccc',
|
|
fontStyle: 'italic'
|
|
}).setOrigin(0.5);
|
|
}
|
|
|
|
// Continue button
|
|
const continueBtn = this.add.rectangle(400, 540, 200, 50, 0x2d5f8e);
|
|
continueBtn.setInteractive({ useHandCursor: true });
|
|
continueBtn.setStrokeStyle(3, 0xffffff);
|
|
|
|
const btnText = this.add.text(400, 540, 'CONTINUE', {
|
|
fontSize: '20px',
|
|
fill: '#fff',
|
|
fontStyle: 'bold'
|
|
}).setOrigin(0.5);
|
|
|
|
// Hover effect (desktop)
|
|
continueBtn.on('pointerover', () => {
|
|
continueBtn.setFillStyle(0x4a90c4);
|
|
});
|
|
|
|
continueBtn.on('pointerout', () => {
|
|
continueBtn.setFillStyle(0x2d5f8e);
|
|
});
|
|
|
|
// Touch feedback (works on both touch and mouse)
|
|
continueBtn.on('pointerdown', () => {
|
|
continueBtn.setFillStyle(0x4a90c4);
|
|
continueBtn.setScale(0.95);
|
|
btnText.setScale(0.95);
|
|
});
|
|
|
|
continueBtn.on('pointerup', () => {
|
|
continueBtn.setFillStyle(0x2d5f8e);
|
|
continueBtn.setScale(1.0);
|
|
btnText.setScale(1.0);
|
|
|
|
// Deduct fuel (if any cost)
|
|
if (this.fuelCost > 0) {
|
|
this.inventory.fuel -= this.fuelCost;
|
|
}
|
|
|
|
// Proceed to next scene
|
|
this.scene.start(this.nextScene, { inventory: this.inventory });
|
|
});
|
|
}
|
|
|
|
getDestinationContent(destination) {
|
|
const destinations = {
|
|
'hunting': {
|
|
title: 'Approaching the Hunting Grounds',
|
|
description: 'Your vessel cuts through the icy waters. The crew prepares the harpoons.\nIn the distance, spouts of water mark the presence of whales.\nThe hunt is about to begin...',
|
|
backgroundColor: 0x1e3a5f,
|
|
visualType: 'ocean_whales'
|
|
},
|
|
'antarctic': {
|
|
title: 'Arriving at Antarctic Island',
|
|
description: 'The ship navigates through floating ice. A desolate, frozen island emerges.\nThousands of penguins waddle along the rocky shores.\nYour crew exchanges uneasy glances. They know why you\'re here...',
|
|
backgroundColor: 0x4a5f7f,
|
|
visualType: 'ice_penguins'
|
|
},
|
|
'port': {
|
|
title: 'Returning to Port',
|
|
description: 'The familiar harbor comes into view. Seagulls circle overhead.\nYou can already smell the taverns and hear the merchants haggling.\nTime to resupply and sell your cargo.',
|
|
backgroundColor: 0x654321,
|
|
visualType: 'harbor'
|
|
}
|
|
};
|
|
|
|
return destinations[destination] || {
|
|
title: 'Traveling...',
|
|
description: 'Your journey continues across the vast ocean.',
|
|
backgroundColor: 0x2d5f8e,
|
|
visualType: 'ocean'
|
|
};
|
|
}
|
|
|
|
createDestinationVisuals(destination, content) {
|
|
switch (content.visualType) {
|
|
case 'ocean_whales':
|
|
this.createOceanWithWhales();
|
|
break;
|
|
case 'ice_penguins':
|
|
this.createIceWithPenguins();
|
|
break;
|
|
case 'harbor':
|
|
this.createHarbor();
|
|
break;
|
|
default:
|
|
this.createOcean();
|
|
break;
|
|
}
|
|
}
|
|
|
|
createOceanWithWhales() {
|
|
// Ocean waves
|
|
this.createOcean();
|
|
|
|
// Whale spouts in the distance
|
|
for (let i = 0; i < 3; i++) {
|
|
const x = 150 + i * 250;
|
|
const y = 150 + Math.random() * 100;
|
|
|
|
// Whale body (barely visible)
|
|
this.add.ellipse(x, y + 20, 60, 30, 0x2a2a2a, 0.6);
|
|
|
|
// Water spout
|
|
const spout = this.add.triangle(
|
|
x, y,
|
|
0, 0,
|
|
-8, -30,
|
|
8, -30,
|
|
0x87CEEB, 0.7
|
|
);
|
|
|
|
// Animate spout
|
|
this.tweens.add({
|
|
targets: spout,
|
|
alpha: { from: 0.7, to: 0.2 },
|
|
y: y - 10,
|
|
duration: 1000,
|
|
yoyo: true,
|
|
repeat: -1,
|
|
ease: 'Sine.inOut'
|
|
});
|
|
}
|
|
|
|
// Ship silhouette
|
|
this.add.rectangle(700, 280, 80, 40, 0x654321);
|
|
this.add.polygon(700, 260, [0, 0, -40, 40, 40, 40], 0x8B4513);
|
|
}
|
|
|
|
createIceWithPenguins() {
|
|
// Icy background effects
|
|
for (let i = 0; i < 20; i++) {
|
|
const x = Math.random() * 800;
|
|
const y = Math.random() * 300;
|
|
const size = 5 + Math.random() * 10;
|
|
this.add.circle(x, y, size, 0xE0E0E0, 0.3);
|
|
}
|
|
|
|
// Ice floes
|
|
this.add.ellipse(200, 250, 150, 80, 0xF0F0F0);
|
|
this.add.ellipse(500, 200, 120, 70, 0xE8E8E8);
|
|
this.add.ellipse(650, 270, 100, 60, 0xF5F5F5);
|
|
|
|
// Penguins on ice floes
|
|
const penguinPositions = [
|
|
{x: 180, y: 240}, {x: 200, y: 245}, {x: 220, y: 238},
|
|
{x: 490, y: 195}, {x: 510, y: 200},
|
|
{x: 645, y: 265}
|
|
];
|
|
|
|
penguinPositions.forEach(pos => {
|
|
// Penguin body
|
|
this.add.ellipse(pos.x, pos.y, 12, 18, 0x000000);
|
|
this.add.ellipse(pos.x, pos.y + 2, 8, 12, 0xFFFFFF);
|
|
// Penguin eyes
|
|
this.add.circle(pos.x - 2, pos.y - 4, 1.5, 0xFFFFFF);
|
|
this.add.circle(pos.x + 2, pos.y - 4, 1.5, 0xFFFFFF);
|
|
});
|
|
|
|
// Ship in the background
|
|
this.add.rectangle(100, 100, 60, 30, 0x654321, 0.7);
|
|
this.add.polygon(100, 85, [0, 0, -30, 30, 30, 30], 0x8B4513, 0.7);
|
|
}
|
|
|
|
createHarbor() {
|
|
// Harbor water
|
|
this.add.rectangle(400, 400, 800, 400, 0x2d5f8e);
|
|
|
|
// Dock
|
|
this.add.rectangle(400, 320, 700, 60, 0x654321);
|
|
for (let i = 0; i < 10; i++) {
|
|
this.add.rectangle(100 + i * 70, 320, 50, 55, 0x4a3621);
|
|
}
|
|
|
|
// Buildings in background
|
|
const buildings = [
|
|
{x: 100, y: 180, w: 80, h: 120, color: 0x8B4513},
|
|
{x: 220, y: 200, w: 100, h: 100, color: 0x654321},
|
|
{x: 360, y: 170, w: 90, h: 130, color: 0x8B6914},
|
|
{x: 490, y: 190, w: 110, h: 110, color: 0x654321},
|
|
{x: 640, y: 180, w: 95, h: 120, color: 0x8B4513}
|
|
];
|
|
|
|
buildings.forEach(b => {
|
|
this.add.rectangle(b.x, b.y, b.w, b.h, b.color);
|
|
this.add.rectangle(b.x, b.y, b.w, b.h).setStrokeStyle(2, 0x000000);
|
|
// Windows
|
|
for (let i = 0; i < 3; i++) {
|
|
for (let j = 0; j < 2; j++) {
|
|
this.add.rectangle(
|
|
b.x - b.w/4 + j * b.w/2,
|
|
b.y - b.h/3 + i * b.h/3,
|
|
10, 12, 0xFFDD44
|
|
);
|
|
}
|
|
}
|
|
});
|
|
|
|
// Barrels on dock
|
|
for (let i = 0; i < 5; i++) {
|
|
this.add.circle(150 + i * 120, 310, 15, 0x8B0000);
|
|
}
|
|
|
|
// Seagulls
|
|
for (let i = 0; i < 3; i++) {
|
|
const x = 200 + i * 200;
|
|
const y = 80 + Math.random() * 40;
|
|
const bird = this.add.text(x, y, 'v', {
|
|
fontSize: '20px',
|
|
fill: '#ffffff'
|
|
});
|
|
|
|
this.tweens.add({
|
|
targets: bird,
|
|
x: x + 50,
|
|
y: y - 20,
|
|
duration: 2000 + Math.random() * 1000,
|
|
yoyo: true,
|
|
repeat: -1,
|
|
ease: 'Sine.inOut'
|
|
});
|
|
}
|
|
}
|
|
|
|
createOcean() {
|
|
// Simple ocean waves
|
|
const graphics = this.add.graphics();
|
|
graphics.lineStyle(3, 0x4a90c4, 0.7);
|
|
|
|
for (let i = 0; i < 8; i++) {
|
|
const y = 100 + i * 40;
|
|
graphics.beginPath();
|
|
for (let x = 0; x < 800; x += 20) {
|
|
const waveY = y + Math.sin((x + i * 30) * 0.03) * 15;
|
|
if (x === 0) {
|
|
graphics.moveTo(x, waveY);
|
|
} else {
|
|
graphics.lineTo(x, waveY);
|
|
}
|
|
}
|
|
graphics.strokePath();
|
|
}
|
|
}
|
|
}
|