Add whale hunting mini-game with crosshair mechanics
- Created HuntingScene with mouse and keyboard controls - Crosshair can be controlled by mouse or WASD/Arrow keys - TAB key toggles between control modes - Whales spawn and swim across screen from both directions - Harpoon shooting mechanic (click or SPACE) - Collision detection and whale hit animations - Whale oil rewards (+1 per successful hunt) - HUD displays fuel, oil, and whales hunted - Return to map button - Updated MapScene to transition to HuntingScene 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
405
src/scenes/HuntingScene.js
Normal file
405
src/scenes/HuntingScene.js
Normal file
@@ -0,0 +1,405 @@
|
||||
import Phaser from 'phaser';
|
||||
|
||||
export default class HuntingScene extends Phaser.Scene {
|
||||
constructor() {
|
||||
super({ key: 'HuntingScene' });
|
||||
}
|
||||
|
||||
init(data) {
|
||||
this.inventory = data.inventory || { whaleOil: 0, fuel: 100, penguins: 0 };
|
||||
this.whalesHunted = 0;
|
||||
this.harpoons = [];
|
||||
this.whales = [];
|
||||
this.crosshairX = 400;
|
||||
this.crosshairY = 300;
|
||||
this.crosshairSpeed = 5;
|
||||
this.useKeyboard = false; // Toggle between mouse and keyboard
|
||||
}
|
||||
|
||||
create() {
|
||||
// Ocean background
|
||||
this.add.rectangle(400, 300, 800, 600, 0x1e5a8e);
|
||||
this.createOceanWaves();
|
||||
|
||||
// UI elements
|
||||
this.createHUD();
|
||||
this.createCrosshair();
|
||||
this.createInstructions();
|
||||
|
||||
// Spawn whales periodically
|
||||
this.time.addEvent({
|
||||
delay: 3000,
|
||||
callback: this.spawnWhale,
|
||||
callbackScope: this,
|
||||
loop: true
|
||||
});
|
||||
|
||||
// Spawn initial whale
|
||||
this.spawnWhale();
|
||||
|
||||
// Input setup
|
||||
this.setupInput();
|
||||
|
||||
// Message display
|
||||
this.showMessage('Hunt whales! Click or press SPACE to shoot harpoon.');
|
||||
}
|
||||
|
||||
update() {
|
||||
// Update crosshair position based on input mode
|
||||
if (this.useKeyboard) {
|
||||
this.updateCrosshairKeyboard();
|
||||
} else {
|
||||
this.updateCrosshairMouse();
|
||||
}
|
||||
|
||||
// Update crosshair sprite position
|
||||
this.crosshair.setPosition(this.crosshairX, this.crosshairY);
|
||||
|
||||
// Update harpoons
|
||||
this.updateHarpoons();
|
||||
|
||||
// Update whales
|
||||
this.updateWhales();
|
||||
|
||||
// Check collisions
|
||||
this.checkCollisions();
|
||||
}
|
||||
|
||||
setupInput() {
|
||||
// Keyboard controls
|
||||
this.cursors = this.input.keyboard.createCursorKeys();
|
||||
this.wasd = this.input.keyboard.addKeys({
|
||||
up: Phaser.Input.Keyboard.KeyCodes.W,
|
||||
down: Phaser.Input.Keyboard.KeyCodes.S,
|
||||
left: Phaser.Input.Keyboard.KeyCodes.A,
|
||||
right: Phaser.Input.Keyboard.KeyCodes.D
|
||||
});
|
||||
this.spaceKey = this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.SPACE);
|
||||
this.tabKey = this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.TAB);
|
||||
|
||||
// Mouse click to shoot
|
||||
this.input.on('pointerdown', (pointer) => {
|
||||
if (!this.useKeyboard) {
|
||||
this.shootHarpoon();
|
||||
}
|
||||
});
|
||||
|
||||
// Space to shoot when using keyboard
|
||||
this.spaceKey.on('down', () => {
|
||||
this.shootHarpoon();
|
||||
});
|
||||
|
||||
// Tab to toggle control mode
|
||||
this.tabKey.on('down', () => {
|
||||
this.useKeyboard = !this.useKeyboard;
|
||||
this.controlModeText.setText(`Controls: ${this.useKeyboard ? 'KEYBOARD' : 'MOUSE'} (TAB to switch)`);
|
||||
if (this.useKeyboard) {
|
||||
this.input.setDefaultCursor('default');
|
||||
} else {
|
||||
this.input.setDefaultCursor('none');
|
||||
}
|
||||
});
|
||||
|
||||
// Start with mouse control (hide cursor)
|
||||
this.input.setDefaultCursor('none');
|
||||
}
|
||||
|
||||
updateCrosshairMouse() {
|
||||
this.crosshairX = this.input.x;
|
||||
this.crosshairY = this.input.y;
|
||||
}
|
||||
|
||||
updateCrosshairKeyboard() {
|
||||
// Arrow keys or WASD
|
||||
if (this.cursors.left.isDown || this.wasd.left.isDown) {
|
||||
this.crosshairX -= this.crosshairSpeed;
|
||||
}
|
||||
if (this.cursors.right.isDown || this.wasd.right.isDown) {
|
||||
this.crosshairX += this.crosshairSpeed;
|
||||
}
|
||||
if (this.cursors.up.isDown || this.wasd.up.isDown) {
|
||||
this.crosshairY -= this.crosshairSpeed;
|
||||
}
|
||||
if (this.cursors.down.isDown || this.wasd.down.isDown) {
|
||||
this.crosshairY += this.crosshairSpeed;
|
||||
}
|
||||
|
||||
// Keep within bounds
|
||||
this.crosshairX = Phaser.Math.Clamp(this.crosshairX, 0, 800);
|
||||
this.crosshairY = Phaser.Math.Clamp(this.crosshairY, 0, 600);
|
||||
}
|
||||
|
||||
createCrosshair() {
|
||||
const graphics = this.add.graphics();
|
||||
graphics.lineStyle(3, 0xff0000, 1);
|
||||
|
||||
// Crosshair lines
|
||||
graphics.beginPath();
|
||||
graphics.moveTo(-20, 0);
|
||||
graphics.lineTo(-5, 0);
|
||||
graphics.moveTo(5, 0);
|
||||
graphics.lineTo(20, 0);
|
||||
graphics.moveTo(0, -20);
|
||||
graphics.lineTo(0, -5);
|
||||
graphics.moveTo(0, 5);
|
||||
graphics.lineTo(0, 20);
|
||||
graphics.strokePath();
|
||||
|
||||
// Center circle
|
||||
graphics.lineStyle(2, 0xff0000, 1);
|
||||
graphics.strokeCircle(0, 0, 3);
|
||||
|
||||
// Convert to texture and create sprite
|
||||
graphics.generateTexture('crosshair', 40, 40);
|
||||
graphics.destroy();
|
||||
|
||||
this.crosshair = this.add.sprite(400, 300, 'crosshair');
|
||||
this.crosshair.setDepth(100);
|
||||
}
|
||||
|
||||
createOceanWaves() {
|
||||
const graphics = this.add.graphics();
|
||||
graphics.lineStyle(2, 0x4a90c4, 0.5);
|
||||
|
||||
for (let i = 0; i < 12; i++) {
|
||||
const y = 50 + i * 50;
|
||||
graphics.beginPath();
|
||||
for (let x = 0; x < 800; x += 20) {
|
||||
const waveY = y + Math.sin((x + i * 30) * 0.02) * 10;
|
||||
if (x === 0) {
|
||||
graphics.moveTo(x, waveY);
|
||||
} else {
|
||||
graphics.lineTo(x, waveY);
|
||||
}
|
||||
}
|
||||
graphics.strokePath();
|
||||
}
|
||||
}
|
||||
|
||||
createHUD() {
|
||||
// HUD background
|
||||
const hudBg = this.add.rectangle(400, 30, 780, 50, 0x000000, 0.7);
|
||||
hudBg.setStrokeStyle(2, 0xffffff);
|
||||
|
||||
// Stats
|
||||
this.statsText = this.add.text(20, 15, '', {
|
||||
fontSize: '16px',
|
||||
fill: '#fff'
|
||||
});
|
||||
|
||||
// Control mode indicator
|
||||
this.controlModeText = this.add.text(400, 15, 'Controls: MOUSE (TAB to switch)', {
|
||||
fontSize: '16px',
|
||||
fill: '#ffff00'
|
||||
}).setOrigin(0.5, 0);
|
||||
|
||||
// Return button
|
||||
const returnBtn = this.add.rectangle(750, 30, 80, 35, 0x8B0000);
|
||||
returnBtn.setInteractive({ useHandCursor: true });
|
||||
returnBtn.setStrokeStyle(2, 0xffffff);
|
||||
|
||||
this.add.text(750, 30, 'RETURN', {
|
||||
fontSize: '14px',
|
||||
fill: '#fff'
|
||||
}).setOrigin(0.5);
|
||||
|
||||
returnBtn.on('pointerdown', () => {
|
||||
this.returnToMap();
|
||||
});
|
||||
|
||||
this.updateStats();
|
||||
}
|
||||
|
||||
updateStats() {
|
||||
this.statsText.setText([
|
||||
`Fuel: ${this.inventory.fuel}`,
|
||||
`Oil: ${this.inventory.whaleOil}`,
|
||||
`Whales: ${this.whalesHunted}`
|
||||
]);
|
||||
}
|
||||
|
||||
createInstructions() {
|
||||
this.messageText = this.add.text(400, 570, '', {
|
||||
fontSize: '16px',
|
||||
fill: '#fff',
|
||||
backgroundColor: '#000000',
|
||||
padding: { x: 10, y: 5 }
|
||||
}).setOrigin(0.5);
|
||||
}
|
||||
|
||||
showMessage(text) {
|
||||
this.messageText.setText(text);
|
||||
}
|
||||
|
||||
spawnWhale() {
|
||||
// Random position and direction
|
||||
const fromLeft = Math.random() > 0.5;
|
||||
const x = fromLeft ? -50 : 850;
|
||||
const y = 150 + Math.random() * 300;
|
||||
const direction = fromLeft ? 1 : -1;
|
||||
const speed = 1 + Math.random() * 1.5;
|
||||
|
||||
// Create whale body
|
||||
const whale = this.add.container(x, y);
|
||||
|
||||
// Whale shape
|
||||
const body = this.add.ellipse(0, 0, 80, 40, 0x2a2a2a);
|
||||
const tail = this.add.triangle(
|
||||
direction > 0 ? -45 : 45, 0,
|
||||
0, -15,
|
||||
0, 15,
|
||||
direction > 0 ? -20 : 20, 0,
|
||||
0x1a1a1a
|
||||
);
|
||||
const fin = this.add.triangle(
|
||||
direction > 0 ? 20 : -20, -5,
|
||||
direction > 0 ? 10 : -10, -25,
|
||||
direction > 0 ? 30 : -30, -5,
|
||||
0x1a1a1a
|
||||
);
|
||||
|
||||
whale.add([body, tail, fin]);
|
||||
whale.setData('direction', direction);
|
||||
whale.setData('speed', speed);
|
||||
whale.setData('alive', true);
|
||||
whale.setData('bodyWidth', 80);
|
||||
whale.setData('bodyHeight', 40);
|
||||
|
||||
// Flip if moving right
|
||||
if (direction > 0) {
|
||||
whale.setScale(-1, 1);
|
||||
}
|
||||
|
||||
this.whales.push(whale);
|
||||
}
|
||||
|
||||
updateWhales() {
|
||||
for (let i = this.whales.length - 1; i >= 0; i--) {
|
||||
const whale = this.whales[i];
|
||||
|
||||
if (!whale.getData('alive')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Move whale
|
||||
const direction = whale.getData('direction');
|
||||
const speed = whale.getData('speed');
|
||||
whale.x += direction * speed;
|
||||
|
||||
// Remove if off screen
|
||||
if (whale.x < -100 || whale.x > 900) {
|
||||
whale.destroy();
|
||||
this.whales.splice(i, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
shootHarpoon() {
|
||||
// Create harpoon at crosshair position
|
||||
const harpoon = this.add.rectangle(this.crosshairX, this.crosshairY, 4, 20, 0x8B4513);
|
||||
harpoon.setData('speed', 8);
|
||||
harpoon.setData('active', true);
|
||||
|
||||
this.harpoons.push(harpoon);
|
||||
|
||||
// Sound effect simulation (flash)
|
||||
const flash = this.add.circle(this.crosshairX, this.crosshairY, 10, 0xffff00, 0.8);
|
||||
this.tweens.add({
|
||||
targets: flash,
|
||||
alpha: 0,
|
||||
scale: 2,
|
||||
duration: 200,
|
||||
onComplete: () => flash.destroy()
|
||||
});
|
||||
}
|
||||
|
||||
updateHarpoons() {
|
||||
for (let i = this.harpoons.length - 1; i >= 0; i--) {
|
||||
const harpoon = this.harpoons[i];
|
||||
|
||||
if (!harpoon.getData('active')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Move harpoon up
|
||||
harpoon.y -= harpoon.getData('speed');
|
||||
|
||||
// Remove if off screen
|
||||
if (harpoon.y < -20) {
|
||||
harpoon.destroy();
|
||||
this.harpoons.splice(i, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
checkCollisions() {
|
||||
for (let h = this.harpoons.length - 1; h >= 0; h--) {
|
||||
const harpoon = this.harpoons[h];
|
||||
|
||||
if (!harpoon.getData('active')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (let w = this.whales.length - 1; w >= 0; w--) {
|
||||
const whale = this.whales[w];
|
||||
|
||||
if (!whale.getData('alive')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Simple collision detection
|
||||
const distance = Phaser.Math.Distance.Between(
|
||||
harpoon.x, harpoon.y,
|
||||
whale.x, whale.y
|
||||
);
|
||||
|
||||
if (distance < 40) {
|
||||
// Hit!
|
||||
this.hitWhale(whale, harpoon);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
hitWhale(whale, harpoon) {
|
||||
whale.setData('alive', false);
|
||||
harpoon.setData('active', false);
|
||||
|
||||
// Whale death animation
|
||||
this.tweens.add({
|
||||
targets: whale,
|
||||
alpha: 0,
|
||||
y: whale.y + 50,
|
||||
angle: 90,
|
||||
duration: 1000,
|
||||
onComplete: () => {
|
||||
whale.destroy();
|
||||
}
|
||||
});
|
||||
|
||||
// Remove harpoon
|
||||
harpoon.destroy();
|
||||
|
||||
// Reward
|
||||
this.inventory.whaleOil += 1;
|
||||
this.whalesHunted++;
|
||||
this.updateStats();
|
||||
|
||||
// Show success message
|
||||
this.showMessage(`Whale hunted! +1 Whale Oil (Total: ${this.inventory.whaleOil})`);
|
||||
|
||||
// Success flash
|
||||
const flash = this.add.rectangle(400, 300, 800, 600, 0xffffff, 0.3);
|
||||
this.tweens.add({
|
||||
targets: flash,
|
||||
alpha: 0,
|
||||
duration: 300,
|
||||
onComplete: () => flash.destroy()
|
||||
});
|
||||
}
|
||||
|
||||
returnToMap() {
|
||||
this.scene.start('MapScene', { inventory: this.inventory });
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user