MagicPot / main.js
eithney's picture
Upload 3 files
11eb8d2 verified
Raw
History Blame Contribute Delete
51.4 kB
// 主游戏类
class MagicPotGame {
constructor() {
this.canvas = document.getElementById('gameCanvas');
this.ctx = this.canvas.getContext('2d');
this.gameState = 'idle'; // idle, activated, cooking, overflowing, stopped
this.currentFood = null;
this.foodCount = 0;
// this.particles = []; // 不再需要单独的粒子数组,使用particleSystem管理
this.lastTime = 0;
// 游戏配置
this.config = {
maxFoodCount: 100, // 最大食物计数
maxParticles: 150, // 最大粒子数量
maxAnimals: 8, // 最大动物数量
animalSpawnThreshold: 5, // 动物出现阈值(降低到5以便更快测试)
easterEggThreshold: 30, // 彩蛋触发阈值
particleSpawnRate: 1, // 粒子生成速率(调整为1,降低食物生成速度)
cookingDuration: 3000, // 3秒烹饪时间
cleanupInterval: 5000, // 清理间隔(毫秒)
maxGroundedParticles: 80 // 地面最大粒子数
};
// 初始化组件
this.voice = new VoiceRecognition();
this.keyboard = new KeyboardInputHandler();
this.pot = new MagicPot(this.canvas.width / 2, this.canvas.height / 2 + 50);
this.particleSystem = new ParticleSystem();
this.animalSystem = new AnimalSystem(this.canvas); // 传入Canvas引用
this.textToImage = new TextToImageAPI();
this.audio = window.audioManager;
// 食物生成控制
this.lastFoodSpawnTime = 0;
this.foodSpawnInterval = 500; // 每500毫秒生成一次食物(显著降低生成速度)
// 性能监控
this.lastCleanupTime = 0;
this.performanceStats = {
particles: 0,
animals: 0,
fps: 0,
lastFpsTime: 0,
frameCount: 0
};
this.init();
}
init() {
this.setupEventListeners();
this.setupCanvas();
this.gameLoop();
this.updateUI();
// 显示初始提示
this.showDebug('游戏初始化完成,等待语音命令...');
// 启动定期清理
this.startPerformanceMonitoring();
}
setupEventListeners() {
// 语音识别事件
this.voice.onResult = (transcript) => {
this.handleVoiceCommand(transcript);
};
this.voice.onError = (error) => {
this.showDebug(`语音识别错误: ${error}`);
};
this.voice.onStatusChange = (status) => {
this.updateVoiceStatus(status);
};
// 键盘输入事件
this.keyboard.onCommand = (command) => {
this.handleKeyboardCommand(command);
};
// 键盘调试快捷键
document.addEventListener('keydown', (e) => {
if (e.key === 'F12') {
this.toggleDebug();
} else if (e.key === 'F11') {
this.testAudio();
} else if (e.key === 'F10') {
this.toggleAudio();
} else if (e.key === 'F9') {
e.preventDefault(); // 确保阻止浏览器默认行为
this.toggleKeyboardActivation();
} else if (e.key === 'F8') {
this.toggleKeyboardPanel();
} else if (e.key === 'F7') {
this.clearCommandHistory();
} else if (e.key === 'F6') {
this.restartVoice();
} else if (e.key === 'F3') {
this.manualActivateVoice();
} else if (e.key === 'F5') {
this.resetKeyboardPanelPosition();
} else if (e.key === 'F4') {
this.adjustFoodSpawnSpeed();
} else if (e.key === 'a' && e.altKey) {
e.preventDefault();
this.forceSpawnAnimal();
}
});
// 音效按钮
const audioButton = document.getElementById('audioButton');
if (audioButton) {
audioButton.addEventListener('click', () => {
this.toggleAudio();
});
this.updateAudioButton();
}
// 语音激活按钮
const voiceActivateButton = document.getElementById('voiceActivateButton');
if (voiceActivateButton) {
voiceActivateButton.addEventListener('click', () => {
this.manualActivateVoice();
});
// 初始化按钮状态
this.updateVoiceActivateButton();
}
// 帮助按钮
const helpButton = document.getElementById('helpButton');
if (helpButton) {
helpButton.addEventListener('click', () => {
this.showVoiceHelp();
});
}
// 窗口大小调整
window.addEventListener('resize', () => {
this.resizeCanvas();
});
}
setupCanvas() {
// 设置高DPI显示
const dpr = window.devicePixelRatio || 1;
const rect = this.canvas.getBoundingClientRect();
this.canvas.width = rect.width * dpr;
this.canvas.height = rect.height * dpr;
this.ctx.scale(dpr, dpr);
this.canvas.style.width = rect.width + 'px';
this.canvas.style.height = rect.height + 'px';
}
handleVoiceCommand(transcript) {
const command = transcript.toLowerCase().trim();
this.showDebug(`语音命令: "${command}"`);
this.processCommand(command, '语音');
}
handleKeyboardCommand(command) {
const normalizedCommand = command.toLowerCase().trim();
this.showDebug(`键盘命令: "${command}"`);
this.processCommand(normalizedCommand, '键盘');
}
processCommand(command, source) {
// 统一的命令处理逻辑
// 激活魔法锅 - 更灵活的匹配
if (this.matchActivationCommand(command)) {
this.activatePot();
this.showDebug(`通过${source}激活魔法锅`);
return;
}
// 停止烹饪 - 更灵活的匹配
if (this.matchStopCommand(command)) {
this.stopCooking();
this.showDebug(`通过${source}停止烹饪`);
return;
}
// 如果锅已激活,处理食物命令
if (this.gameState === 'activated') {
const foodName = this.extractFoodName(command);
if (foodName) {
this.startCooking(foodName);
this.showDebug(`通过${source}开始烹饪: ${foodName}`);
} else {
this.showDebug(`未识别的食物: "${command}" (来源: ${source})`);
}
} else {
// 如果锅未激活,提示用户先激活
if (this.isValidFoodCommand(command)) {
this.showDebug(`请先激活魔法锅再烹饪食物 (来源: ${source})`);
this.showSpecialMessage('请先说 "cook cook cook pot" 激活魔法锅');
}
}
}
isValidFoodCommand(command) {
// 检查是否是有效的食物命令
const foods = [
'apple', 'banana', 'orange', 'strawberry', 'watermelon', 'grape',
'pizza', 'burger', 'bread', 'rice', 'noodles', 'cake', 'cookie',
'cheese', 'fish', 'chicken', 'carrot', 'tomato', 'corn', 'broccoli',
'potato', 'onion'
];
return foods.some(food => command.includes(food));
}
matchActivationCommand(command) {
// 多种激活命令的匹配模式
const patterns = [
/cook.*cook.*cook.*pot/,
/cook.*pot.*cook.*cook/,
/pot.*cook.*cook.*cook/,
/cook\s+cook\s+cook\s+pot/,
/cook.*three.*times.*pot/,
/magic.*pot.*cook/
];
return patterns.some(pattern => pattern.test(command));
}
matchStopCommand(command) {
// 多种停止命令的匹配模式
const patterns = [
/stop.*stop.*stop.*pot/,
/stop.*pot.*stop.*stop/,
/pot.*stop.*stop.*stop/,
/stop\s+stop\s+stop\s+pot/,
/stop.*three.*times.*pot/,
/magic.*pot.*stop/
];
return patterns.some(pattern => pattern.test(command));
}
extractFoodName(command) {
// 支持的食物列表
const foods = [
'apple', 'banana', 'orange', 'strawberry', 'watermelon', 'grape',
'pizza', 'burger', 'bread', 'rice', 'noodles', 'cake', 'cookie',
'cheese', 'fish', 'chicken', 'carrot', 'tomato', 'corn', 'broccoli',
'potato', 'onion'
];
// 在命令中查找食物名称
for (const food of foods) {
if (command.includes(food)) {
return food;
}
}
// 如果没有找到确切匹配,尝试模糊匹配
for (const food of foods) {
if (this.fuzzyMatch(command, food)) {
return food;
}
}
return null;
}
fuzzyMatch(text, target) {
// 简单的模糊匹配算法
const threshold = 0.7;
const similarity = this.calculateSimilarity(text, target);
return similarity >= threshold;
}
calculateSimilarity(str1, str2) {
// 计算两个字符串的相似度
const longer = str1.length > str2.length ? str1 : str2;
const shorter = str1.length > str2.length ? str2 : str1;
if (longer.length === 0) return 1.0;
const distance = this.levenshteinDistance(longer, shorter);
return (longer.length - distance) / longer.length;
}
levenshteinDistance(str1, str2) {
// 计算编辑距离
const matrix = [];
for (let i = 0; i <= str2.length; i++) {
matrix[i] = [i];
}
for (let j = 0; j <= str1.length; j++) {
matrix[0][j] = j;
}
for (let i = 1; i <= str2.length; i++) {
for (let j = 1; j <= str1.length; j++) {
if (str2.charAt(i - 1) === str1.charAt(j - 1)) {
matrix[i][j] = matrix[i - 1][j - 1];
} else {
matrix[i][j] = Math.min(
matrix[i - 1][j - 1] + 1,
matrix[i][j - 1] + 1,
matrix[i - 1][j] + 1
);
}
}
}
return matrix[str2.length][str1.length];
}
activatePot() {
if (this.gameState === 'idle') {
this.gameState = 'activated';
this.pot.activate();
this.updateUI();
this.showDebug('魔法锅已激活!');
this.showSpecialMessage('🪄 魔法锅已激活!\n现在说出食物名称开始烹饪');
// 播放激活音效
if (this.audio) {
this.audio.playPotActivate();
}
}
}
async startCooking(foodName) {
if (this.gameState !== 'activated') return;
this.gameState = 'cooking';
this.currentFood = foodName;
this.pot.startCooking(foodName);
this.updateUI();
this.showDebug(`开始烹饪: ${foodName}`);
// 播放烹饪音效
if (this.audio) {
this.audio.playCooking();
// 烹饪过程中定期播放冒泡声
this.cookingSoundInterval = setInterval(() => {
if (this.gameState === 'cooking') {
this.audio.playCooking();
}
}, 1000);
}
try {
// 生成食物和动物素材
await this.generateAssets(foodName);
// 烹饪完成,开始溢出
setTimeout(() => {
this.startOverflowing();
}, this.config.cookingDuration);
} catch (error) {
this.showDebug(`生成素材失败: ${error.message}`);
// 即使生成失败也继续游戏
setTimeout(() => {
this.startOverflowing();
}, this.config.cookingDuration);
}
}
async generateAssets(foodName) {
// 显示生成进度
this.updateCookingProgress(0);
// 生成食物图片
this.updateCookingProgress(30);
const foodImage = await this.textToImage.generateFood(foodName);
// 生成动物图片
this.updateCookingProgress(60);
const animalImage = await this.textToImage.generateAnimal(foodName);
// 保存生成的素材
this.pot.setFoodAssets(foodImage, animalImage);
this.updateCookingProgress(100);
this.showDebug(`素材生成完成: ${foodName}`);
}
startOverflowing() {
this.gameState = 'overflowing';
this.pot.startOverflowing();
this.foodCount = 0; // 重置计数器开始计数
this.updateUI();
this.showDebug('食物开始涌出!');
console.log(`开始溢出,食物计数重置为: ${this.foodCount}`);
// 停止烹饪音效
if (this.cookingSoundInterval) {
clearInterval(this.cookingSoundInterval);
this.cookingSoundInterval = null;
}
}
stopCooking() {
if (this.gameState === 'overflowing') {
this.gameState = 'stopped';
this.pot.stopOverflowing();
this.updateUI();
this.showDebug('停止烹饪');
// 播放停止音效
if (this.audio) {
this.audio.playStopCooking();
}
// 停止烹饪音效
if (this.cookingSoundInterval) {
clearInterval(this.cookingSoundInterval);
this.cookingSoundInterval = null;
}
// 显示统计信息
this.showGameStats();
// 5秒后重置游戏状态
setTimeout(() => {
this.resetGame();
}, 5000);
}
}
resetGame() {
this.gameState = 'idle';
this.currentFood = null;
this.foodCount = 0;
this.pot.reset();
this.particleSystem.clear();
this.animalSystem.clear();
this.updateUI();
this.showDebug('游戏已重置,可以重新开始');
}
showGameStats() {
const stats = {
food: this.currentFood,
count: this.foodCount,
animals: this.animalSystem.getAnimalCount(),
particles: this.particleSystem.getParticleCount()
};
const message = `本轮统计:\n食物:${stats.food}\n数量:${stats.count}\n动物:${stats.animals}只`;
this.showSpecialMessage(message);
this.showDebug(`游戏统计: ${JSON.stringify(stats)}`);
}
gameLoop(currentTime = 0) {
const deltaTime = currentTime - this.lastTime;
this.lastTime = currentTime;
this.update(deltaTime);
this.render();
// 性能监控和清理
this.updatePerformanceStats(currentTime);
this.performCleanupIfNeeded(currentTime);
requestAnimationFrame((time) => this.gameLoop(time));
}
update(deltaTime) {
// 更新魔法锅
this.pot.update(deltaTime);
// 更新粒子系统
this.particleSystem.update(deltaTime);
// 更新动物系统
this.animalSystem.update(deltaTime);
// 如果正在溢出,生成食物粒子(控制生成频率)
if (this.gameState === 'overflowing') {
const currentTime = Date.now();
if (currentTime - this.lastFoodSpawnTime >= this.foodSpawnInterval) {
this.spawnFoodParticles();
this.lastFoodSpawnTime = currentTime;
// 在生成食物粒子后立即检查动物生成
this.checkAnimalSpawn();
this.checkEasterEggs();
}
}
}
spawnFoodParticles() {
// 检查是否达到最大粒子数量
if (this.particleSystem.getParticleCount() >= this.config.maxParticles) {
this.showDebug('已达到最大粒子数量,停止生成新粒子');
return;
}
// 检查是否达到最大食物计数
if (this.foodCount >= this.config.maxFoodCount) {
this.showDebug('已达到最大食物数量,自动停止烹饪');
this.stopCooking();
return;
}
// 根据配置生成食物粒子
const spawnCount = Math.min(
this.config.particleSpawnRate,
this.config.maxParticles - this.particleSystem.getParticleCount(),
this.config.maxFoodCount - this.foodCount
);
for (let i = 0; i < spawnCount; i++) {
const particle = this.particleSystem.createFoodParticle(
this.pot.x,
this.pot.y - 50,
this.currentFood
);
// 注意:粒子已经在 createFoodParticle 中添加到 particleSystem.particles 了
// 这里不需要再添加到 this.particles
this.foodCount++;
console.log(`食物粒子已创建: ${this.currentFood}, 当前计数: ${this.foodCount}`);
// 随机播放食物弹出音效
if (this.audio && Math.random() < 0.3) {
this.audio.playRandomFoodPop();
}
}
// 更新UI显示
this.updateUI();
}
checkAnimalSpawn() {
// 更宽松的动物出现条件
if (this.foodCount >= this.config.animalSpawnThreshold) {
// 每2个食物就有机会出现动物,并且增加随机性
if (this.foodCount % 2 === 0 && Math.random() < 0.3) {
this.showDebug(`食物数量达到${this.foodCount},尝试生成动物`);
this.spawnAnimal();
}
} else {
// 调试信息:显示距离动物出现还需要多少食物
if (this.foodCount % 2 === 0) {
const remaining = this.config.animalSpawnThreshold - this.foodCount;
this.showDebug(`还需要${remaining}个食物才能出现动物(当前:${this.foodCount})`);
}
}
}
checkEasterEggs() {
// 草莓和西瓜的特殊彩蛋
if ((this.currentFood === 'strawberry' || this.currentFood === 'watermelon') &&
this.foodCount >= this.config.easterEggThreshold) {
// 检查是否已经有小女孩了
const hasGirl = this.animalSystem.animals.some(animal => animal.type === 'girl');
if (!hasGirl && this.foodCount % 15 === 0) {
this.spawnLittleGirl();
this.showSpecialMessage('小女孩被美味的水果吸引过来了!');
}
}
// 其他隐藏彩蛋
this.checkSecretEasterEggs();
}
checkSecretEasterEggs() {
// 特殊组合彩蛋
if (this.foodCount === 100) {
this.createCelebrationEffect();
this.showSpecialMessage('恭喜!你制作了100个食物!');
}
// 特定食物的特殊效果
if (this.currentFood === 'cake' && this.foodCount >= 20) {
this.createBirthdayEffect();
}
}
spawnAnimal() {
// 检查是否达到最大动物数量
if (this.animalSystem.getAnimalCount() >= this.config.maxAnimals) {
this.showDebug('已达到最大动物数量,不再生成新动物');
return;
}
// 检查当前食物是否已设置
if (!this.currentFood) {
this.showDebug('当前食物未设置,无法生成动物');
return;
}
const side = Math.random() < 0.5 ? 'left' : 'right';
console.log(`尝试生成动物: 食物类型=${this.currentFood}, 边=${side}, 当前动物数=${this.animalSystem.getAnimalCount()}`);
const animal = this.animalSystem.createAnimal(this.currentFood, side);
if (animal) {
// 动物已经在 createAnimal 方法中添加到 animalSystem.animals 了
this.showDebug(`🐾 小动物出现了!类型: ${animal.type}, 食物: ${this.currentFood}, 边: ${side}, 总数: ${this.animalSystem.getAnimalCount()}`);
// 播放动物出现音效
if (this.audio) {
this.audio.playAnimalAppear();
}
} else {
this.showDebug('❌ 动物生成失败');
}
}
spawnLittleGirl() {
const girl = this.animalSystem.createLittleGirl('right');
if (girl) {
// 小女孩已经在 createLittleGirl 方法中添加到 animalSystem.animals 了
this.showDebug('小女孩出现了!');
// 播放小女孩出现音效
if (this.audio) {
this.audio.playGirlAppear();
}
// 创建特殊效果
this.particleSystem.createSparkleParticles(
this.canvas.width - 100,
this.canvas.height - 100,
10
);
}
}
createCelebrationEffect() {
// 播放庆祝音效
if (this.audio) {
this.audio.playCelebration();
}
// 创建庆祝烟花效果
for (let i = 0; i < 5; i++) {
setTimeout(() => {
const x = Math.random() * this.canvas.width;
const y = Math.random() * this.canvas.height * 0.5;
this.particleSystem.createExplosionParticles(x, y, 15, '#FFD700');
this.particleSystem.createExplosionParticles(x, y, 15, '#FF69B4');
}, i * 500);
}
}
createBirthdayEffect() {
// 生日蛋糕特效
const centerX = this.canvas.width / 2;
const centerY = this.canvas.height / 2;
this.particleSystem.createSparkleParticles(centerX, centerY, 20);
this.showSpecialMessage('生日快乐!🎂');
}
showSpecialMessage(message) {
// 显示特殊消息
const messageDiv = document.createElement('div');
messageDiv.textContent = message;
messageDiv.style.cssText = `
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: linear-gradient(45deg, #FF6B6B, #4ECDC4);
color: white;
padding: 20px 30px;
border-radius: 15px;
font-size: 18px;
font-weight: bold;
box-shadow: 0 8px 32px rgba(0,0,0,0.3);
z-index: 10000;
animation: specialMessage 3s ease-in-out forwards;
`;
document.body.appendChild(messageDiv);
setTimeout(() => {
if (messageDiv.parentNode) {
messageDiv.parentNode.removeChild(messageDiv);
}
}, 3000);
}
render() {
// 清空画布
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
// 绘制背景
this.renderBackground();
// 绘制魔法锅
this.pot.render(this.ctx);
// 绘制粒子
this.particleSystem.render(this.ctx);
// 绘制动物
this.animalSystem.render(this.ctx);
// 绘制特效
this.renderEffects();
}
renderBackground() {
// 绘制渐变背景
const gradient = this.ctx.createLinearGradient(0, 0, 0, this.canvas.height);
gradient.addColorStop(0, '#87CEEB');
gradient.addColorStop(1, '#98FB98');
this.ctx.fillStyle = gradient;
this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
// 绘制装饰性元素
this.renderDecorations();
}
renderDecorations() {
// 绘制云朵
this.ctx.fillStyle = 'rgba(255, 255, 255, 0.8)';
this.drawCloud(100, 80, 60);
this.drawCloud(300, 60, 40);
this.drawCloud(500, 90, 50);
}
drawCloud(x, y, size) {
this.ctx.beginPath();
this.ctx.arc(x, y, size * 0.5, 0, Math.PI * 2);
this.ctx.arc(x + size * 0.3, y, size * 0.6, 0, Math.PI * 2);
this.ctx.arc(x + size * 0.6, y, size * 0.4, 0, Math.PI * 2);
this.ctx.arc(x - size * 0.3, y, size * 0.4, 0, Math.PI * 2);
this.ctx.fill();
}
renderEffects() {
// 绘制魔法粒子效果
if (this.gameState === 'activated' || this.gameState === 'cooking') {
this.renderMagicSparkles();
}
}
renderMagicSparkles() {
const time = Date.now() * 0.005;
this.ctx.fillStyle = '#FFD700';
for (let i = 0; i < 5; i++) {
const angle = (i / 5) * Math.PI * 2 + time;
const radius = 80 + Math.sin(time + i) * 20;
const x = this.pot.x + Math.cos(angle) * radius;
const y = this.pot.y + Math.sin(angle) * radius;
this.ctx.beginPath();
this.ctx.arc(x, y, 3, 0, Math.PI * 2);
this.ctx.fill();
}
}
updateUI() {
// 更新锅状态
const potStatus = document.getElementById('potStatus');
const statusIndicator = potStatus.querySelector('.status-indicator');
statusIndicator.className = 'status-indicator';
switch (this.gameState) {
case 'idle':
statusIndicator.textContent = '魔法锅休眠中';
statusIndicator.classList.add('inactive');
break;
case 'activated':
statusIndicator.textContent = '魔法锅已激活';
statusIndicator.classList.add('active');
break;
case 'cooking':
statusIndicator.textContent = '正在烹饪中...';
statusIndicator.classList.add('cooking');
break;
case 'overflowing':
statusIndicator.textContent = '食物涌出中!';
statusIndicator.classList.add('overflowing');
break;
case 'stopped':
statusIndicator.textContent = '烹饪已停止';
statusIndicator.classList.add('inactive');
break;
}
// 更新烹饪信息
const cookingInfo = document.getElementById('cookingInfo');
const currentFoodSpan = document.getElementById('currentFood');
if (this.gameState === 'cooking' && this.currentFood) {
cookingInfo.style.display = 'block';
currentFoodSpan.textContent = this.currentFood;
} else {
cookingInfo.style.display = 'none';
}
// 更新食物计数器
const foodCounter = document.getElementById('foodCounter');
const foodCount = document.getElementById('foodCount');
const foodEmoji = document.getElementById('foodEmoji');
if (this.gameState === 'overflowing' || this.gameState === 'stopped') {
foodCounter.style.display = 'block';
foodCount.textContent = this.foodCount;
foodEmoji.textContent = this.getFoodEmoji(this.currentFood);
} else {
foodCounter.style.display = 'none';
}
// 更新语音提示
this.updateVoiceHints();
// 更新性能监控
this.updatePerformanceDisplay();
}
updateVoiceHints() {
const foodHint = document.getElementById('foodHint');
const stopHint = document.getElementById('stopHint');
foodHint.style.display = this.gameState === 'activated' ? 'block' : 'none';
stopHint.style.display = this.gameState === 'overflowing' ? 'block' : 'none';
}
updatePerformanceDisplay() {
const performanceMonitor = document.getElementById('performanceMonitor');
const particleCount = document.getElementById('particleCount');
const animalCount = document.getElementById('animalCount');
const fpsCount = document.getElementById('fpsCount');
if (performanceMonitor && particleCount && animalCount && fpsCount) {
const perfInfo = this.getPerformanceInfo();
particleCount.textContent = perfInfo.particles;
animalCount.textContent = perfInfo.animals;
fpsCount.textContent = perfInfo.fps;
// 根据游戏状态显示/隐藏性能监控
const shouldShow = this.gameState === 'overflowing' ||
this.gameState === 'cooking' ||
perfInfo.particles > 50;
performanceMonitor.style.display = shouldShow ? 'block' : 'none';
// 性能警告颜色
if (perfInfo.particles > this.config.maxParticles * 0.8) {
particleCount.style.color = '#ff6b6b';
} else {
particleCount.style.color = '#ffffff';
}
if (perfInfo.animals > this.config.maxAnimals * 0.8) {
animalCount.style.color = '#ff6b6b';
} else {
animalCount.style.color = '#ffffff';
}
if (perfInfo.fps < 30) {
fpsCount.style.color = '#ff6b6b';
} else if (perfInfo.fps < 45) {
fpsCount.style.color = '#f39c12';
} else {
fpsCount.style.color = '#27ae60';
}
}
}
updateCookingProgress(progress) {
const progressFill = document.getElementById('progressFill');
if (progressFill) {
progressFill.style.width = progress + '%';
}
}
updateVoiceStatus(status) {
const voiceStatus = document.getElementById('voiceStatus');
const statusText = voiceStatus.querySelector('.status-text');
switch (status) {
case 'listening':
statusText.textContent = '正在听取...';
voiceStatus.style.background = '#e74c3c';
break;
case 'processing':
statusText.textContent = '处理中...';
voiceStatus.style.background = '#f39c12';
break;
case 'ready':
statusText.textContent = '准备就绪';
voiceStatus.style.background = '#4ecdc4';
break;
case 'error':
statusText.textContent = '语音错误';
voiceStatus.style.background = '#95a5a6';
break;
}
// 更新语音激活按钮状态
this.updateVoiceActivateButton();
}
getFoodEmoji(foodName) {
const emojiMap = {
'apple': '🍎',
'banana': '🍌',
'orange': '🍊',
'strawberry': '🍓',
'watermelon': '🍉',
'grape': '🍇',
'pizza': '🍕',
'burger': '🍔',
'cake': '🍰',
'cookie': '🍪',
'bread': '🍞',
'cheese': '🧀'
};
return emojiMap[foodName] || '🍽️';
}
showDebug(message) {
const debugLog = document.getElementById('debugLog');
if (debugLog) {
const time = new Date().toLocaleTimeString();
const perfInfo = this.getPerformanceInfo();
const perfText = `FPS:${perfInfo.fps} P:${perfInfo.particles} A:${perfInfo.animals} M:${perfInfo.memoryUsage}KB`;
debugLog.innerHTML += `<div>[${time}] [${perfText}] ${message}</div>`;
debugLog.scrollTop = debugLog.scrollHeight;
// 限制调试日志行数
const lines = debugLog.children;
if (lines.length > 50) {
for (let i = 0; i < 10; i++) {
debugLog.removeChild(lines[0]);
}
}
}
console.log(`[MagicPot] ${message}`);
}
toggleDebug() {
const debugInfo = document.getElementById('debugInfo');
debugInfo.style.display = debugInfo.style.display === 'none' ? 'block' : 'none';
}
resizeCanvas() {
this.setupCanvas();
}
showVoiceHelp() {
const helpContent = `
<h3>🎤 语音命令帮助</h3>
<div class="help-section">
<h4>🪄 激活魔法锅</h4>
<p>说出:"<strong>cook cook cook pot</strong>"</p>
<p>其他可用命令:</p>
<ul>
<li>"magic pot cook"</li>
<li>"cook three times pot"</li>
</ul>
</div>
<div class="help-section">
<h4>🍎 开始烹饪</h4>
<p>魔法锅激活后,说出任何食物名称:</p>
<ul>
<li><strong>水果:</strong>apple, banana, orange, strawberry, watermelon, grape</li>
<li><strong>主食:</strong>pizza, burger, bread, rice, noodles</li>
<li><strong>甜点:</strong>cake, cookie</li>
<li><strong>其他:</strong>cheese, fish, chicken</li>
</ul>
</div>
<div class="help-section">
<h4>🛑 停止烹饪</h4>
<p>说出:"<strong>stop stop stop pot</strong>"</p>
<p>其他可用命令:</p>
<ul>
<li>"magic pot stop"</li>
<li>"stop three times pot"</li>
</ul>
</div>
<div class="help-section">
<h4>🎁 特殊彩蛋</h4>
<ul>
<li><strong>草莓/西瓜:</strong>数量达到30个时,小女孩会出现</li>
<li><strong>生日蛋糕:</strong>制作蛋糕时有特殊庆祝效果</li>
<li><strong>百食庆祝:</strong>制作100个食物时有烟花效果</li>
</ul>
</div>
<div class="help-section">
<h4>🔊 音效控制</h4>
<ul>
<li><strong>音效按钮:</strong>点击🔊按钮开关音效</li>
<li><strong>F10:</strong>快捷键切换音效开关</li>
<li><strong>F11:</strong>测试所有音效</li>
</ul>
</div>
<div class="help-section">
<h4>🎵 音效说明</h4>
<ul>
<li><strong>激活音效:</strong>魔法锅激活时的神奇音调</li>
<li><strong>烹饪音效:</strong>烹饪过程中的冒泡声</li>
<li><strong>食物弹出:</strong>食物涌出时的弹跳声</li>
<li><strong>动物出现:</strong>小动物出现时的鸟叫声</li>
<li><strong>小女孩:</strong>小女孩出现时的铃声</li>
<li><strong>庆祝音效:</strong>达成成就时的号角声</li>
</ul>
</div>
<div class="help-section">
<h4>💡 使用技巧</h4>
<ul>
<li>说话清晰,语速适中</li>
<li>确保浏览器已允许麦克风权限</li>
<li>在安静的环境中使用效果更佳</li>
<li>按F12可查看调试信息</li>
<li><strong>按F9激活/取消激活键盘面板</strong></li>
<li><strong>按F8显示/隐藏键盘面板</strong></li>
<li><strong>按F7清空命令历史</strong></li>
<li><strong>按F5重置面板位置</strong></li>
<li><strong>按F4调整食物生成速度</strong></li>
<li><strong>按Alt+A强制生成动物(调试)</strong></li>
<li>首次使用时点击任意按钮激活音频</li>
</ul>
</div>
<div class="help-section">
<h4>⌨️ 键盘输入调试</h4>
<ul>
<li><strong>F9:</strong> 激活/取消激活键盘面板 ⭐</li>
<li><strong>Ctrl+K:</strong> 聚焦到输入框(自动激活)</li>
<li><strong>Ctrl+反引号:</strong> 切换输入面板</li>
<li><strong>数字键1-6:</strong> 快速执行常用命令</li>
<li><strong>↑↓方向键:</strong> 浏览命令历史</li>
<li><strong>Enter:</strong> 执行当前输入的命令</li>
<li><strong>Esc:</strong> 清空输入或取消激活</li>
</ul>
<p style="color: #f39c12; font-style: italic;">
💡 面板默认隐藏,按F9激活并显示面板后才能输入命令<br>
🖱️ 激活后可拖拽标题栏移动面板,按F5重置到右下角<br>
👁️ 按F8可手动显示/隐藏面板,取消激活时自动隐藏
</p>
</div>
`;
this.showModal('语音命令帮助', helpContent);
}
showModal(title, content) {
// 创建模态框
const modal = document.createElement('div');
modal.className = 'help-modal';
modal.innerHTML = `
<div class="modal-overlay"></div>
<div class="modal-content">
<div class="modal-header">
<h2>${title}</h2>
<button class="modal-close">✕</button>
</div>
<div class="modal-body">
${content}
</div>
</div>
`;
document.body.appendChild(modal);
// 关闭按钮事件
const closeBtn = modal.querySelector('.modal-close');
const overlay = modal.querySelector('.modal-overlay');
const closeModal = () => {
modal.style.animation = 'modalFadeOut 0.3s ease-out forwards';
setTimeout(() => {
if (modal.parentNode) {
modal.parentNode.removeChild(modal);
}
}, 300);
};
closeBtn.addEventListener('click', closeModal);
overlay.addEventListener('click', closeModal);
// ESC键关闭
const handleKeydown = (e) => {
if (e.key === 'Escape') {
closeModal();
document.removeEventListener('keydown', handleKeydown);
}
};
document.addEventListener('keydown', handleKeydown);
}
// 性能监控和优化方法
startPerformanceMonitoring() {
this.showDebug('性能监控已启动');
}
updatePerformanceStats(currentTime) {
// 更新FPS
this.performanceStats.frameCount++;
if (currentTime - this.performanceStats.lastFpsTime >= 1000) {
this.performanceStats.fps = this.performanceStats.frameCount;
this.performanceStats.frameCount = 0;
this.performanceStats.lastFpsTime = currentTime;
}
// 更新粒子和动物统计
this.performanceStats.particles = this.particleSystem.getParticleCount();
this.performanceStats.animals = this.animalSystem.getAnimalCount();
}
performCleanupIfNeeded(currentTime) {
// 定期清理
if (currentTime - this.lastCleanupTime >= this.config.cleanupInterval) {
this.performMemoryCleanup();
this.lastCleanupTime = currentTime;
}
}
performMemoryCleanup() {
const beforeParticles = this.particleSystem.getParticleCount();
const beforeAnimals = this.animalSystem.getAnimalCount();
// 清理地面上过多的粒子
this.cleanupGroundedParticles();
// 清理长时间存在的动物
this.cleanupOldAnimals();
// 清理无效的粒子
this.particleSystem.cleanupInvalidParticles();
const afterParticles = this.particleSystem.getParticleCount();
const afterAnimals = this.animalSystem.getAnimalCount();
if (beforeParticles !== afterParticles || beforeAnimals !== afterAnimals) {
this.showDebug(`内存清理完成 - 粒子: ${beforeParticles}${afterParticles}, 动物: ${beforeAnimals}${afterAnimals}`);
}
}
cleanupGroundedParticles() {
const groundedParticles = this.particleSystem.particles.filter(p =>
p.type === 'food' && p.isGrounded
);
if (groundedParticles.length > this.config.maxGroundedParticles) {
// 移除最老的地面粒子
const toRemove = groundedParticles.length - this.config.maxGroundedParticles;
const oldestParticles = groundedParticles
.sort((a, b) => a.lifeTime - b.lifeTime)
.slice(0, toRemove);
oldestParticles.forEach(particle => {
const index = this.particleSystem.particles.indexOf(particle);
if (index > -1) {
this.particleSystem.particles.splice(index, 1);
}
});
}
}
cleanupOldAnimals() {
// 移除长时间未活动的动物
const oldAnimals = this.animalSystem.animals.filter(animal =>
animal.lifeTime > 45 && animal.state !== 'eating'
);
oldAnimals.forEach(animal => {
animal.shouldRemove = true;
});
}
// 获取性能统计信息
getPerformanceInfo() {
return {
fps: this.performanceStats.fps,
particles: this.performanceStats.particles,
animals: this.performanceStats.animals,
foodCount: this.foodCount,
gameState: this.gameState,
memoryUsage: this.estimateMemoryUsage()
};
}
estimateMemoryUsage() {
// 粗略估算内存使用量(KB)
const particleMemory = this.performanceStats.particles * 0.5; // 每个粒子约0.5KB
const animalMemory = this.performanceStats.animals * 1; // 每个动物约1KB
const baseMemory = 100; // 基础内存约100KB
return Math.round(baseMemory + particleMemory + animalMemory);
}
// 强制生成动物(调试用)
forceSpawnAnimal() {
console.log('=== 强制生成动物调试 ===');
console.log(`当前食物: ${this.currentFood}`);
console.log(`食物数量: ${this.foodCount}`);
console.log(`当前动物数: ${this.animalSystem.getAnimalCount()}`);
console.log(`最大动物数: ${this.config.maxAnimals}`);
console.log(`游戏状态: ${this.gameState}`);
console.log(`Canvas尺寸: ${this.canvas.width}x${this.canvas.height}`);
if (!this.currentFood) {
this.showDebug('❌ 当前没有食物类型,无法生成动物');
return;
}
if (this.animalSystem.getAnimalCount() >= this.config.maxAnimals) {
this.showDebug(`❌ 已达到最大动物数量 ${this.config.maxAnimals},清理一些动物后再试`);
// 清理一些老动物
this.animalSystem.makeAllAnimalsLeave();
return;
}
this.showDebug('🔧 强制尝试生成动物...');
this.spawnAnimal();
// 显示当前所有动物的状态
setTimeout(() => {
console.log('=== 当前动物状态 ===');
this.animalSystem.animals.forEach((animal, index) => {
console.log(`动物${index + 1}: ${animal.type}, 位置(${animal.x}, ${animal.y}), 状态: ${animal.state}, emoji: ${animal.emoji}`);
});
}, 100);
}
// 音效控制方法
toggleAudio() {
if (this.audio) {
const newState = !this.audio.isEnabled;
this.audio.setEnabled(newState);
this.updateAudioButton();
if (newState) {
this.showDebug('音效已开启');
// 播放测试音效
setTimeout(() => {
this.audio.playPotActivate();
}, 100);
} else {
this.showDebug('音效已关闭');
}
}
}
updateAudioButton() {
const audioButton = document.getElementById('audioButton');
if (audioButton && this.audio) {
if (this.audio.isEnabled) {
audioButton.textContent = '🔊';
audioButton.classList.remove('muted');
audioButton.title = '点击关闭音效';
} else {
audioButton.textContent = '🔇';
audioButton.classList.add('muted');
audioButton.title = '点击开启音效';
}
}
}
updateVoiceActivateButton() {
const voiceActivateButton = document.getElementById('voiceActivateButton');
if (voiceActivateButton && this.voice) {
const status = this.voice.getStatus();
// 移除所有状态类
voiceActivateButton.classList.remove('active', 'disabled');
if (!status.isSupported) {
voiceActivateButton.classList.add('disabled');
voiceActivateButton.title = '浏览器不支持语音识别';
voiceActivateButton.textContent = '❌';
} else if (status.isDisabled) {
voiceActivateButton.classList.add('disabled');
voiceActivateButton.title = '语音识别已禁用,点击重新激活';
voiceActivateButton.textContent = '🔇';
} else if (status.isListening) {
voiceActivateButton.classList.add('active');
voiceActivateButton.title = '语音识别运行中';
voiceActivateButton.textContent = '🎤';
} else {
voiceActivateButton.title = '点击激活语音识别';
voiceActivateButton.textContent = '🎤';
}
}
}
// 音效测试功能
testAudio() {
if (this.audio) {
this.showDebug('开始音效测试...');
this.audio.testAllSounds();
}
}
// 重启语音识别
restartVoice() {
if (this.voice) {
this.showDebug('手动重启语音识别系统');
this.voice.restart();
}
}
// 手动激活语音识别
manualActivateVoice() {
if (this.voice) {
this.showDebug('手动激活语音识别');
this.voice.manualActivate();
}
}
// 切换键盘输入面板
toggleKeyboardPanel() {
if (this.keyboard) {
this.keyboard.togglePanel();
const stats = this.keyboard.getStats();
this.showDebug(`键盘面板${stats.isCollapsed ? '已折叠' : '已展开'}`);
}
}
// 清空命令历史
clearCommandHistory() {
if (this.keyboard) {
this.keyboard.clearHistory();
this.showDebug('键盘命令历史已清空');
}
}
// 切换键盘面板激活状态
toggleKeyboardActivation() {
if (this.keyboard) {
console.log('F9 pressed - toggling keyboard activation');
this.keyboard.toggleActiveState();
const stats = this.keyboard.getStats();
// 激活时自动展开面板,取消激活时隐藏面板
if (stats.isActive) {
this.keyboard.setPanelCollapsed(false);
this.showDebug('键盘面板已激活并显示');
} else {
this.keyboard.setPanelCollapsed(true);
this.showDebug('键盘面板已取消激活并隐藏');
}
console.log('Keyboard activation state:', stats.isActive);
} else {
console.log('Keyboard handler not initialized');
}
}
// 重置键盘面板位置
resetKeyboardPanelPosition() {
if (this.keyboard) {
this.keyboard.resetPosition();
this.showDebug('键盘面板位置已重置');
}
}
// 调整食物生成速度
adjustFoodSpawnSpeed() {
// 循环切换生成速度:慢速(800ms) -> 中速(400ms) -> 快速(200ms) -> 超快(100ms)
const speeds = [
{ interval: 800, rate: 1, name: '慢速' },
{ interval: 400, rate: 1, name: '中速' },
{ interval: 200, rate: 2, name: '快速' },
{ interval: 100, rate: 3, name: '超快' }
];
// 找到当前速度的索引
let currentIndex = speeds.findIndex(speed => speed.interval === this.foodSpawnInterval);
if (currentIndex === -1) currentIndex = 0; // 默认慢速
// 切换到下一个速度
const nextIndex = (currentIndex + 1) % speeds.length;
const newSpeed = speeds[nextIndex];
this.foodSpawnInterval = newSpeed.interval;
this.config.particleSpawnRate = newSpeed.rate;
this.showDebug(`食物生成速度已调整为: ${newSpeed.name} (${newSpeed.interval}ms间隔)`);
this.showSpecialMessage(`🍽️ 食物生成速度: ${newSpeed.name}`);
}
// 获取完整的调试信息
getDebugInfo() {
const info = {
game: this.getPerformanceInfo(),
voice: this.voice ? this.voice.getStatus() : null,
keyboard: this.keyboard ? this.keyboard.getStats() : null
};
return info;
}
}
// 游戏初始化
document.addEventListener('DOMContentLoaded', () => {
window.game = new MagicPotGame();
});