Class_generator/src/gameHelpers/MarioEducational/enemies/FlyingEye.js
StillHammer 4714a4a1c6 Add TTS support and improve content compatibility system
Major improvements:
- Add TTSHelper utility for text-to-speech functionality
- Enhance content compatibility scoring across all games
- Improve sentence extraction from multiple content sources
- Update all game modules to support diverse content formats
- Refine MarioEducational physics and rendering
- Polish UI styles and remove unused CSS

Games updated: AdventureReader, FillTheBlank, FlashcardLearning,
GrammarDiscovery, MarioEducational, QuizGame, RiverRun, WhackAMole,
WhackAMoleHard, WizardSpellCaster, WordDiscovery, WordStorm

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-18 02:49:48 +08:00

196 lines
7.1 KiB
JavaScript

/**
* FlyingEye.js
* Flying enemy that chases Mario and performs dash attacks
* Appears in level 5+
*/
export class FlyingEye {
/**
* Generate flying eyes for a level
* @param {Object} level - Level data
* @param {number} difficulty - Difficulty level
* @returns {Array} - Array of flying eye objects
*/
static generate(level, difficulty) {
const eyes = [];
const eyeCount = Math.min(4, Math.max(3, difficulty - 2)); // 3-4 flying eyes
console.log(`👁️ Generating ${eyeCount} flying eyes for level 5+`);
for (let i = 0; i < eyeCount; i++) {
// Eyes spawn in the middle-upper area of the level
const eyeX = 300 + (i * 400) + Math.random() * 200; // Spread across level
const eyeY = 100 + Math.random() * 150; // Upper area of screen
eyes.push({
x: eyeX,
y: eyeY,
width: 30,
height: 30,
velocityX: (Math.random() - 0.5) * 2, // Random horizontal drift -1 to +1
velocityY: (Math.random() - 0.5) * 2, // Random vertical drift -1 to +1
color: '#DC143C', // Crimson red
pupilColor: '#000000', // Black pupil
type: 'flying_eye',
health: 1,
maxHealth: 1,
// AI behavior properties
chaseDistance: 200, // Start chasing Mario within 200px
chaseSpeed: 3.5, // Faster chase speed
idleSpeed: 1.2, // Faster idle movement
lastDirectionChange: Date.now(),
directionChangeInterval: 2000 + Math.random() * 3000, // Change direction every 2-5 seconds
isChasing: false,
// Dash behavior
dashCooldown: 0,
dashDuration: 0,
isDashing: false,
dashSpeed: 8, // Very fast dash
lastDashTime: Date.now(),
dashInterval: 3000 + Math.random() * 2000, // Dash every 3-5 seconds
// Visual properties
blinkTimer: 0,
isBlinking: false
});
console.log(`👁️ Flying eye ${i + 1} placed at x=${eyeX.toFixed(0)}, y=${eyeY.toFixed(0)}`);
}
return eyes;
}
/**
* Update all flying eyes
* @param {Array} eyes - Array of flying eyes
* @param {Object} mario - Mario object
* @param {Function} playSound - Sound callback
*/
static update(eyes, mario, playSound) {
const currentTime = Date.now();
eyes.forEach(eye => {
// Calculate distance from eye center to Mario center
const marioCenter = { x: mario.x + mario.width / 2, y: mario.y + mario.height / 2 };
const distanceToMario = Math.sqrt(
Math.pow(eye.x - marioCenter.x, 2) + Math.pow(eye.y - marioCenter.y, 2)
);
// Blinking animation
eye.blinkTimer++;
if (eye.blinkTimer > 120) {
eye.isBlinking = true;
}
if (eye.blinkTimer > 125) {
eye.isBlinking = false;
eye.blinkTimer = 0;
}
// Check if should chase Mario
eye.isChasing = distanceToMario < eye.chaseDistance;
// Dash behavior
if (eye.isDashing) {
eye.dashDuration--;
if (eye.dashDuration <= 0) {
eye.isDashing = false;
eye.dashCooldown = 60; // Cooldown frames after dash
}
} else if (eye.dashCooldown > 0) {
eye.dashCooldown--;
} else if (eye.isChasing && currentTime - eye.lastDashTime > eye.dashInterval) {
// Start dash towards Mario
eye.isDashing = true;
eye.dashDuration = 30; // 30 frames of dash
eye.lastDashTime = currentTime;
// Set dash velocity towards Mario center
const dx = marioCenter.x - eye.x;
const dy = marioCenter.y - eye.y;
const distance = Math.sqrt(dx * dx + dy * dy);
eye.velocityX = (dx / distance) * eye.dashSpeed;
eye.velocityY = (dy / distance) * eye.dashSpeed;
console.log(`👁️ Flying eye dashes towards Mario!`);
}
// Movement behavior
if (eye.isDashing) {
// Continue dash movement
eye.x += eye.velocityX;
eye.y += eye.velocityY;
} else if (eye.isChasing) {
// Chase Mario center
const dx = marioCenter.x - eye.x;
const dy = marioCenter.y - eye.y;
const distance = Math.sqrt(dx * dx + dy * dy);
eye.velocityX = (dx / distance) * eye.chaseSpeed;
eye.velocityY = (dy / distance) * eye.chaseSpeed;
eye.x += eye.velocityX;
eye.y += eye.velocityY;
} else {
// Idle wandering
if (currentTime - eye.lastDirectionChange > eye.directionChangeInterval) {
eye.velocityX = (Math.random() - 0.5) * eye.idleSpeed * 2;
eye.velocityY = (Math.random() - 0.5) * eye.idleSpeed * 2;
eye.lastDirectionChange = currentTime;
}
eye.x += eye.velocityX;
eye.y += eye.velocityY;
}
// Keep eyes within bounds (with some margin)
// Allow eyes to fly anywhere but not too close to edges
if (eye.x < 50) {
eye.x = 50;
eye.velocityX = Math.abs(eye.velocityX);
}
if (eye.y < 50) {
eye.y = 50;
eye.velocityY = Math.abs(eye.velocityY);
}
// Allow eyes to go much lower (near ground level) - 600px is just above ground (640)
if (eye.y > 600) {
eye.y = 600;
eye.velocityY = -Math.abs(eye.velocityY);
}
});
}
/**
* Check collision between Mario and flying eyes
* @param {Object} mario - Mario object
* @param {Array} eyes - Array of flying eyes
* @returns {Object|null} - Colliding eye or null
*/
static checkCollision(mario, eyes) {
for (const eye of eyes) {
// Eye position is CENTER, convert to top-left corner for collision
const eyeLeft = eye.x - eye.width / 2;
const eyeTop = eye.y - eye.height / 2;
// Rectangle collision with centered eye position
if (mario.x < eyeLeft + eye.width &&
mario.x + mario.width > eyeLeft &&
mario.y < eyeTop + eye.height &&
mario.y + mario.height > eyeTop) {
return eye;
}
}
return null;
}
/**
* Damage a flying eye
* @param {Object} eye - Flying eye to damage
* @returns {boolean} - True if eye was killed
*/
static damage(eye) {
eye.health--;
return eye.health <= 0;
}
}
export default FlyingEye;