diff --git a/src/main.js b/src/main.js index 83a93f6..ecfa924 100644 --- a/src/main.js +++ b/src/main.js @@ -2,6 +2,7 @@ import Phaser from 'phaser'; import ShipDeckScene from './scenes/ShipDeckScene.js'; import MapScene from './scenes/MapScene.js'; import TransitionScene from './scenes/TransitionScene.js'; +import HuntingScene from './scenes/HuntingScene.js'; const config = { type: Phaser.AUTO, @@ -9,7 +10,7 @@ const config = { height: 600, parent: 'game-container', backgroundColor: '#2d5f8e', - scene: [ShipDeckScene, MapScene, TransitionScene], + scene: [ShipDeckScene, MapScene, TransitionScene, HuntingScene], physics: { default: 'arcade', arcade: { diff --git a/src/scenes/HuntingScene.js b/src/scenes/HuntingScene.js new file mode 100644 index 0000000..327362d --- /dev/null +++ b/src/scenes/HuntingScene.js @@ -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 }); + } +} diff --git a/src/scenes/MapScene.js b/src/scenes/MapScene.js index 763ea59..8cbdc47 100644 --- a/src/scenes/MapScene.js +++ b/src/scenes/MapScene.js @@ -159,12 +159,12 @@ export default class MapScene extends Phaser.Scene { this.showMessage('Not enough fuel to reach the hunting grounds! You need at least 20 units.'); return; } - // Go to transition scene, then to map (hunting scene not yet implemented) + // Go to transition scene, then to hunting scene this.scene.start('TransitionScene', { inventory: this.inventory, destination: 'hunting', fuelCost: 20, - nextScene: 'MapScene' // Will change to 'HuntingScene' when implemented + nextScene: 'HuntingScene' }); }