Improve whale hunting mechanics with multi-hit system

- Changed to single whale spawning (one at a time)
- Added crosshair shake effect for increased difficulty (2px random shake)
- Implemented whale health system (3 hits to kill)
- Added dynamic health bar above whales (green -> yellow -> red)
- Visual feedback on hits: red flash, whale shake animation
- Health bar updates in real-time as whale takes damage
- New whales spawn 1.5 seconds after previous whale dies or leaves
- Separated hitWhale() and killWhale() functions for damage vs death
- Shows health status messages on each hit
- Much more challenging and engaging hunting gameplay

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Thomas Richter
2025-12-15 03:42:39 +01:00
parent 09a1b7a537
commit 9e6f1d24ea

View File

@@ -9,10 +9,11 @@ export default class HuntingScene extends Phaser.Scene {
this.inventory = data.inventory || { whaleOil: 0, fuel: 100, penguins: 0 }; this.inventory = data.inventory || { whaleOil: 0, fuel: 100, penguins: 0 };
this.whalesHunted = 0; this.whalesHunted = 0;
this.harpoons = []; this.harpoons = [];
this.whales = []; this.currentWhale = null;
this.crosshairX = 400; this.crosshairX = 400;
this.crosshairY = 300; this.crosshairY = 300;
this.crosshairSpeed = 5; this.crosshairSpeed = 5;
this.crosshairShakeAmount = 2; // Pixels of shake
this.useKeyboard = false; // Toggle between mouse and keyboard this.useKeyboard = false; // Toggle between mouse and keyboard
} }
@@ -26,15 +27,7 @@ export default class HuntingScene extends Phaser.Scene {
this.createCrosshair(); this.createCrosshair();
this.createInstructions(); this.createInstructions();
// Spawn whales periodically // Spawn single whale
this.time.addEvent({
delay: 3000,
callback: this.spawnWhale,
callbackScope: this,
loop: true
});
// Spawn initial whale
this.spawnWhale(); this.spawnWhale();
// Input setup // Input setup
@@ -52,14 +45,16 @@ export default class HuntingScene extends Phaser.Scene {
this.updateCrosshairMouse(); this.updateCrosshairMouse();
} }
// Update crosshair sprite position // Update crosshair sprite position with shake
this.crosshair.setPosition(this.crosshairX, this.crosshairY); const shakeX = (Math.random() - 0.5) * this.crosshairShakeAmount * 2;
const shakeY = (Math.random() - 0.5) * this.crosshairShakeAmount * 2;
this.crosshair.setPosition(this.crosshairX + shakeX, this.crosshairY + shakeY);
// Update harpoons // Update harpoons
this.updateHarpoons(); this.updateHarpoons();
// Update whales // Update whale
this.updateWhales(); this.updateWhale();
// Check collisions // Check collisions
this.checkCollisions(); this.checkCollisions();
@@ -232,12 +227,17 @@ export default class HuntingScene extends Phaser.Scene {
} }
spawnWhale() { spawnWhale() {
// Don't spawn if there's already a whale
if (this.currentWhale && this.currentWhale.getData('alive')) {
return;
}
// Random position and direction // Random position and direction
const fromLeft = Math.random() > 0.5; const fromLeft = Math.random() > 0.5;
const x = fromLeft ? -50 : 850; const x = fromLeft ? -50 : 850;
const y = 150 + Math.random() * 300; const y = 150 + Math.random() * 300;
const direction = fromLeft ? 1 : -1; const direction = fromLeft ? 1 : -1;
const speed = 1 + Math.random() * 1.5; const speed = 0.8 + Math.random() * 0.7; // Slightly slower for single whale
// Create whale body // Create whale body
const whale = this.add.container(x, y); const whale = this.add.container(x, y);
@@ -258,27 +258,38 @@ export default class HuntingScene extends Phaser.Scene {
0x1a1a1a 0x1a1a1a
); );
whale.add([body, tail, fin]); // Health bar background
const healthBg = this.add.rectangle(0, -35, 60, 6, 0x000000);
// Health bar (will be updated as whale takes damage)
const healthBar = this.add.rectangle(0, -35, 60, 6, 0x00ff00);
whale.add([body, tail, fin, healthBg, healthBar]);
whale.setData('direction', direction); whale.setData('direction', direction);
whale.setData('speed', speed); whale.setData('speed', speed);
whale.setData('alive', true); whale.setData('alive', true);
whale.setData('health', 3); // Requires 3 hits
whale.setData('maxHealth', 3);
whale.setData('bodyWidth', 80); whale.setData('bodyWidth', 80);
whale.setData('bodyHeight', 40); whale.setData('bodyHeight', 40);
whale.setData('healthBar', healthBar);
// Flip if moving right // Flip if moving right
if (direction > 0) { if (direction > 0) {
whale.setScale(-1, 1); whale.setScale(-1, 1);
} }
this.whales.push(whale); this.currentWhale = whale;
} }
updateWhales() { updateWhale() {
for (let i = this.whales.length - 1; i >= 0; i--) { if (!this.currentWhale) {
const whale = this.whales[i]; return;
}
const whale = this.currentWhale;
if (!whale.getData('alive')) { if (!whale.getData('alive')) {
continue; return;
} }
// Move whale // Move whale
@@ -286,11 +297,14 @@ export default class HuntingScene extends Phaser.Scene {
const speed = whale.getData('speed'); const speed = whale.getData('speed');
whale.x += direction * speed; whale.x += direction * speed;
// Remove if off screen // If whale goes off screen, spawn a new one
if (whale.x < -100 || whale.x > 900) { if (whale.x < -100 || whale.x > 900) {
whale.destroy(); whale.destroy();
this.whales.splice(i, 1); this.currentWhale = null;
} // Spawn new whale after a short delay
this.time.delayedCall(1500, () => {
this.spawnWhale();
});
} }
} }
@@ -333,6 +347,12 @@ export default class HuntingScene extends Phaser.Scene {
} }
checkCollisions() { checkCollisions() {
if (!this.currentWhale || !this.currentWhale.getData('alive')) {
return;
}
const whale = this.currentWhale;
for (let h = this.harpoons.length - 1; h >= 0; h--) { for (let h = this.harpoons.length - 1; h >= 0; h--) {
const harpoon = this.harpoons[h]; const harpoon = this.harpoons[h];
@@ -340,13 +360,6 @@ export default class HuntingScene extends Phaser.Scene {
continue; continue;
} }
for (let w = this.whales.length - 1; w >= 0; w--) {
const whale = this.whales[w];
if (!whale.getData('alive')) {
continue;
}
// Simple collision detection // Simple collision detection
const distance = Phaser.Math.Distance.Between( const distance = Phaser.Math.Distance.Between(
harpoon.x, harpoon.y, harpoon.x, harpoon.y,
@@ -360,11 +373,61 @@ export default class HuntingScene extends Phaser.Scene {
} }
} }
} }
}
hitWhale(whale, harpoon) { hitWhale(whale, harpoon) {
whale.setData('alive', false);
harpoon.setData('active', false); harpoon.setData('active', false);
harpoon.destroy();
// Reduce whale health
let health = whale.getData('health');
health -= 1;
whale.setData('health', health);
// Update health bar
const healthBar = whale.getData('healthBar');
const maxHealth = whale.getData('maxHealth');
const healthPercent = health / maxHealth;
healthBar.setDisplaySize(60 * healthPercent, 6);
// Change health bar color based on health
if (healthPercent > 0.6) {
healthBar.setFillStyle(0x00ff00); // Green
} else if (healthPercent > 0.3) {
healthBar.setFillStyle(0xffff00); // Yellow
} else {
healthBar.setFillStyle(0xff0000); // Red
}
// Hit flash effect
const hitFlash = this.add.circle(whale.x, whale.y, 50, 0xff0000, 0.6);
this.tweens.add({
targets: hitFlash,
alpha: 0,
scale: 1.5,
duration: 300,
onComplete: () => hitFlash.destroy()
});
// Whale damaged animation (shake)
this.tweens.add({
targets: whale,
x: whale.x + 5,
yoyo: true,
repeat: 2,
duration: 50
});
if (health <= 0) {
// Whale is dead!
this.killWhale(whale);
} else {
// Whale is still alive
this.showMessage(`Hit! Whale health: ${health}/${maxHealth}`);
}
}
killWhale(whale) {
whale.setData('alive', false);
// Check if enough fuel to process the whale // Check if enough fuel to process the whale
if (this.inventory.fuel < 2) { if (this.inventory.fuel < 2) {
@@ -376,11 +439,15 @@ export default class HuntingScene extends Phaser.Scene {
duration: 1000, duration: 1000,
onComplete: () => { onComplete: () => {
whale.destroy(); whale.destroy();
this.currentWhale = null;
// Spawn new whale
this.time.delayedCall(1500, () => {
this.spawnWhale();
});
} }
}); });
harpoon.destroy();
this.showMessage('Whale hit but no fuel to process it! Need 2 fuel to cook whale oil.'); this.showMessage('Whale killed but no fuel to process it! Need 2 fuel to cook whale oil.');
return; return;
} }
@@ -393,12 +460,14 @@ export default class HuntingScene extends Phaser.Scene {
duration: 1000, duration: 1000,
onComplete: () => { onComplete: () => {
whale.destroy(); whale.destroy();
this.currentWhale = null;
// Spawn new whale after delay
this.time.delayedCall(1500, () => {
this.spawnWhale();
});
} }
}); });
// Remove harpoon
harpoon.destroy();
// Consume fuel for processing // Consume fuel for processing
this.inventory.fuel -= 2; this.inventory.fuel -= 2;
@@ -408,7 +477,7 @@ export default class HuntingScene extends Phaser.Scene {
this.updateStats(); this.updateStats();
// Show success message // Show success message
this.showMessage(`Whale hunted! +1 Whale Oil, -2 Fuel (Oil: ${this.inventory.whaleOil}, Fuel: ${this.inventory.fuel})`); this.showMessage(`Whale killed! +1 Whale Oil, -2 Fuel (Oil: ${this.inventory.whaleOil}, Fuel: ${this.inventory.fuel})`);
// Success flash // Success flash
const flash = this.add.rectangle(400, 300, 800, 600, 0xffffff, 0.3); const flash = this.add.rectangle(400, 300, 800, 600, 0xffffff, 0.3);