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:
@@ -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,39 +258,53 @@ 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;
|
||||||
|
}
|
||||||
|
|
||||||
if (!whale.getData('alive')) {
|
const whale = this.currentWhale;
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Move whale
|
if (!whale.getData('alive')) {
|
||||||
const direction = whale.getData('direction');
|
return;
|
||||||
const speed = whale.getData('speed');
|
}
|
||||||
whale.x += direction * speed;
|
|
||||||
|
|
||||||
// Remove if off screen
|
// Move whale
|
||||||
if (whale.x < -100 || whale.x > 900) {
|
const direction = whale.getData('direction');
|
||||||
whale.destroy();
|
const speed = whale.getData('speed');
|
||||||
this.whales.splice(i, 1);
|
whale.x += direction * speed;
|
||||||
}
|
|
||||||
|
// If whale goes off screen, spawn a new one
|
||||||
|
if (whale.x < -100 || whale.x > 900) {
|
||||||
|
whale.destroy();
|
||||||
|
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,31 +360,74 @@ export default class HuntingScene extends Phaser.Scene {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let w = this.whales.length - 1; w >= 0; w--) {
|
// Simple collision detection
|
||||||
const whale = this.whales[w];
|
const distance = Phaser.Math.Distance.Between(
|
||||||
|
harpoon.x, harpoon.y,
|
||||||
|
whale.x, whale.y
|
||||||
|
);
|
||||||
|
|
||||||
if (!whale.getData('alive')) {
|
if (distance < 40) {
|
||||||
continue;
|
// Hit!
|
||||||
}
|
this.hitWhale(whale, harpoon);
|
||||||
|
break;
|
||||||
// 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) {
|
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);
|
||||||
|
|||||||
Reference in New Issue
Block a user