Files
monkeygg2.github.io/games/five-nights-at-epsteins/js/CameraSystem.js
T
2026-02-25 22:49:56 -05:00

1046 lines
41 KiB
JavaScript

// Camera system management
class CameraSystem {
constructor(game) {
this.game = game;
this.cameraPanel = document.getElementById('camera-panel');
this.currentCamLabel = document.getElementById('current-cam-label');
this.cameraErrorLabel = document.getElementById('camera-error-label');
this.playSoundBtn = document.getElementById('play-sound-btn');
this.shockHawkingBtn = document.getElementById('shock-hawking-btn');
this.currentSoundToggle = false;
this.staticVideo = document.getElementById('camera-static-video');
// 播放声音按钮状态
this.soundButtonCooldown = false;
this.soundButtonUseCount = 0;
this.maxSoundUses = 5; // 连续使用5次后摄像头故障
this.cooldownTime = 8000; // 8秒冷却
this.cooldownInterval = null; // 冷却动画定时器
// 每个位置的连续吸引计数
this.locationAttractCount = {}; // { 'cam11': 2, 'cam8': 1, ... }
this.maxLocationAttractCount = 2; // 同一位置最多连续吸引2次
this.lastEpLocation = null; // 记录EP的上一个位置,用于检测移动
// EP 角色配置 - 直接引用 EnemyAI 的配置(游戏初始化后会设置)
this.characterImages = null;
this.characterPositions = null;
this.characterBrightness = null;
this.characterRotation = null;
this.bindEvents();
}
// Initialize EP config (from EnemyAI)
initEPConfig() {
if (this.game.enemyAI) {
this.characterImages = this.game.enemyAI.characterImages;
this.characterPositions = this.game.enemyAI.characterPositions;
this.characterBrightness = this.game.enemyAI.characterBrightness;
this.characterRotation = this.game.enemyAI.characterRotation;
console.log('EP config initialized from EnemyAI');
}
}
bindEvents() {
if (this.playSoundBtn) {
this.playSoundBtn.addEventListener('click', () => this.playAmbientSound());
}
if (this.shockHawkingBtn) {
this.shockHawkingBtn.addEventListener('click', () => this.shockHawking());
}
}
toggle() {
// console.log('📷 Camera toggle called, current state:', this.game.state.cameraOpen);
if (this.game.state.cameraOpen) {
this.close();
} else {
this.open();
}
}
open() {
// console.log('📷 Opening camera...');
// console.log('📷 Camera panel element:', this.cameraPanel);
// console.log('📷 Camera panel classes before:', this.cameraPanel.className);
this.game.state.cameraOpen = true;
this.cameraPanel.classList.remove('hidden');
this.cameraPanel.classList.add('show');
// console.log('📷 Camera panel classes after:', this.cameraPanel.className);
// console.log('📷 Camera panel display:', window.getComputedStyle(this.cameraPanel).display);
// console.log('📷 Camera panel opacity:', window.getComputedStyle(this.cameraPanel).opacity);
// console.log('📷 Camera panel transform:', window.getComputedStyle(this.cameraPanel).transform);
this.game.assets.playSound('crank1');
// Start looping low volume static sound
this.game.assets.playSound('staticLoop', true, 0.3);
this.createCameraGrid();
// 更新电击按钮显示
this.updateShockButtonVisibility();
// 更新霍金警告位置(从风扇左边移到地图上)
if (this.game.enemyAI && this.game.enemyAI.hawking.active) {
this.game.enemyAI.updateHawkingWarningDisplay();
}
// If camera failed, show failure effect
if (this.game.state.cameraFailed) {
console.log('📷 Camera is failed, showing failure effect');
this.showCameraFailure();
} else {
console.log('📷 Camera is normal, showing normal view');
// Normal state, ensure all failure effects removed
this.cameraPanel.classList.remove('transitioning');
// Hide ERR label
if (this.cameraErrorLabel) {
this.cameraErrorLabel.classList.remove('active');
}
// Stop static
this.stopStatic();
// Show map
const cameraGrid = document.getElementById('camera-grid');
if (cameraGrid) {
cameraGrid.style.display = 'block';
}
// Update view
this.updateView();
}
// Stop view rotation
this.game.isRotatingLeft = false;
this.game.isRotatingRight = false;
}
// Show camera failure effect
showCameraFailure() {
console.log('Showing camera failure effect...');
// Night 5: 30% 概率触发 Golden 霍金彩蛋
if (this.game.state.currentNight === 5 && Math.random() < 0.3) {
this.game.showGoldenStephen();
}
// Hide background image and characters
this.cameraPanel.classList.add('transitioning');
// Hide map
const cameraGrid = document.getElementById('camera-grid');
if (cameraGrid) {
cameraGrid.style.display = 'none';
console.log('Camera grid hidden');
}
// Show ERR label
if (this.cameraErrorLabel) {
this.cameraErrorLabel.classList.add('active');
console.log('ERR label shown');
}
// Show and play static video
if (this.staticVideo) {
console.log('Starting static video...');
this.staticVideo.classList.add('active');
this.staticVideo.currentTime = 0; // Play from beginning
this.staticVideo.play().catch(e => console.log('Video playback failed:', e));
} else {
console.error('Static video element not found!');
}
}
// Stop static effect
stopStatic() {
if (this.staticVideo) {
this.staticVideo.classList.remove('active');
this.staticVideo.pause();
this.staticVideo.currentTime = 0;
}
}
// Start static effect (for switching cameras)
startStatic() {
if (this.staticVideo) {
this.staticVideo.classList.add('active');
this.staticVideo.play().catch(e => console.log('Video playback failed:', e));
}
}
// Restore camera normal display
restoreCameraView() {
console.log('Restoring camera view...');
// Stop static
this.stopStatic();
console.log('Static video stopped');
// Remove failure state
this.cameraPanel.classList.remove('transitioning');
console.log('Removed transitioning class');
// Hide ERR label
if (this.cameraErrorLabel) {
this.cameraErrorLabel.classList.remove('active');
console.log('ERR label hidden');
}
// Show map
const cameraGrid = document.getElementById('camera-grid');
if (cameraGrid) {
cameraGrid.style.display = 'block';
console.log('Camera grid shown');
}
// Update view
this.updateView();
console.log('View updated');
}
// Fix camera
restartCamera() {
// 如果控制面板正忙,不允许操作
if (this.game.state.controlPanelBusy) {
console.log('Control panel is busy, cannot restart camera');
return;
}
console.log('Restarting camera system...');
this.game.state.cameraRestarting = true;
this.game.state.controlPanelBusy = true; // 锁定控制面板
// 播放心电图音效
this.game.assets.playSound('ekg', false, 0.8);
// Restore after 4 seconds
setTimeout(() => {
// 无论之前是否故障,重启后都恢复正常
this.game.state.cameraFailed = false;
this.game.state.cameraRestarting = false;
this.game.state.controlPanelBusy = false; // 解锁控制面板
// Stop static noise (如果有的话)
this.game.assets.stopSound('static');
// Reset sound button count (恢复5次使用次数)
this.resetSoundButtonCount();
console.log('Camera system restored!');
// If camera is open, immediately restore display
if (this.game.state.cameraOpen) {
console.log('Camera is open, restoring view...');
this.restoreCameraView();
}
}, 4000);
}
close() {
this.game.state.cameraOpen = false;
this.cameraPanel.classList.add('closing');
this.cameraPanel.classList.remove('show');
// Stop looping static sound
this.game.assets.stopSound('staticLoop');
// Clear character display
const characterOverlay = document.getElementById('character-overlay');
if (characterOverlay) {
characterOverlay.innerHTML = '';
console.log('Character overlay cleared');
}
// 更新霍金警告位置(从地图移到风扇左边)
if (this.game.enemyAI && this.game.enemyAI.hawking.active) {
this.game.enemyAI.updateHawkingWarningDisplay();
}
setTimeout(() => {
this.cameraPanel.classList.add('hidden');
this.cameraPanel.classList.remove('closing');
}, 400);
this.game.assets.playSound('crank2');
}
switchCamera(camNum) {
// If camera failed, cannot switch
if (this.game.state.cameraFailed) {
console.log('Camera system is offline! Cannot switch cameras.');
return;
}
// Add transition state, hide background image
this.cameraPanel.classList.add('transitioning');
// Hide map
const cameraGrid = document.getElementById('camera-grid');
if (cameraGrid) {
cameraGrid.style.display = 'none';
}
// 隐藏角色
const characterOverlay = document.getElementById('character-overlay');
if (characterOverlay) {
characterOverlay.style.display = 'none';
}
// 暂时降低循环静态音的音量
this.game.assets.setSoundVolume('staticLoop', 0.1);
// 播放正常音量的静态音效
this.game.assets.playSound('static', false, 1.0);
// 1000ms 后停止静态音效
setTimeout(() => {
this.game.assets.stopSound('static');
}, 1000);
// Show static effect
this.startStatic();
// Switch camera after 500ms
setTimeout(() => {
// If camera already failed, stop switch animation, show failure effect
if (this.game.state.cameraFailed) {
console.log('Camera failed during switch, showing failure effect');
this.showCameraFailure();
return;
}
this.game.state.currentCam = `cam${camNum}`;
this.updateView();
this.createCameraGrid();
// After another 500ms fade out static, restore background
setTimeout(() => {
// Check again if failed
if (this.game.state.cameraFailed) {
console.log('Camera failed during switch, showing failure effect');
this.showCameraFailure();
return;
}
this.stopStatic();
this.cameraPanel.classList.remove('transitioning');
// 显示地图
if (cameraGrid) {
cameraGrid.style.display = 'block';
}
// 显示角色
if (characterOverlay) {
characterOverlay.style.display = 'block';
}
// 更新电击按钮显示(根据当前摄像头)
this.updateShockButtonVisibility();
// 恢复循环静态音的音量
this.game.assets.setSoundVolume('staticLoop', 0.3);
}, 500);
}, 500);
}
updateView() {
// If camera failed, don't update view
if (this.game.state.cameraFailed) {
return;
}
// Update camera panel background image
if (this.game.assets.images[this.game.state.currentCam]) {
this.cameraPanel.style.backgroundImage = `url('${this.game.assets.images[this.game.state.currentCam].src}')`;
}
// 更新摄像头标签
const camNum = this.game.state.currentCam.replace('cam', '');
this.currentCamLabel.textContent = `CAM ${camNum}`;
// 更新角色显示
this.updateCharacterDisplay();
// 更新电击按钮显示
this.updateShockButtonVisibility();
}
// 更新角色显示(支持多个敌人)
updateCharacterDisplay() {
const currentCam = this.game.state.currentCam;
const epLocation = this.game.enemyAI.getCurrentLocation();
const trumpLocation = this.game.enemyAI.getTrumpCurrentLocation();
const hawkingActive = this.game.enemyAI.hawking.active;
console.log(`updateCharacterDisplay - Current Cam: ${currentCam}, EP: ${epLocation}, Trump: ${trumpLocation}, Hawking: ${hawkingActive}, Night: ${this.game.state.currentNight}`);
// 打印所有相关元素的z-index
console.log('🔍 Z-Index Debug:');
console.log(' - cameraPanel:', window.getComputedStyle(this.cameraPanel).zIndex);
const staticVideo = document.getElementById('camera-static-video');
if (staticVideo) {
console.log(' - staticVideo:', window.getComputedStyle(staticVideo).zIndex);
}
const existingOverlay = document.getElementById('character-overlay');
if (existingOverlay) {
console.log(' - characterOverlay:', window.getComputedStyle(existingOverlay).zIndex);
console.log(' - characterOverlay display:', window.getComputedStyle(existingOverlay).display);
console.log(' - characterOverlay children count:', existingOverlay.children.length);
}
// 获取或创建角色容器
let characterOverlay = document.getElementById('character-overlay');
if (!characterOverlay) {
characterOverlay = document.createElement('div');
characterOverlay.id = 'character-overlay';
characterOverlay.style.position = 'absolute';
characterOverlay.style.top = '0';
characterOverlay.style.left = '0';
characterOverlay.style.width = '100%';
characterOverlay.style.height = '100%';
characterOverlay.style.pointerEvents = 'none';
characterOverlay.style.zIndex = '5';
characterOverlay.style.overflow = 'hidden';
this.cameraPanel.appendChild(characterOverlay);
}
// 清空之前的角色
characterOverlay.innerHTML = '';
console.log('🔍 Character overlay cleared, checking EP display conditions...');
console.log('🔍 EP hasSpawned:', this.game.enemyAI.epstein.hasSpawned);
console.log('🔍 EP location matches current cam:', epLocation === currentCam);
console.log('🔍 Has characterImages:', !!this.characterImages);
console.log('🔍 Has image for current cam:', this.characterImages ? !!this.characterImages[currentCam] : 'N/A');
// 显示霍金(如果激活且在cam6)
if (hawkingActive && currentCam === 'cam6') {
const hawkingImg = document.createElement('img');
hawkingImg.src = 'assets/images/mrstephen.png';
hawkingImg.style.position = 'absolute';
hawkingImg.className = 'visible hawking-character';
hawkingImg.style.zIndex = '3'; // Hawking 在最上层
hawkingImg.style.left = '59.6%';
hawkingImg.style.bottom = '0.9%';
hawkingImg.style.width = '37%';
hawkingImg.style.transform = 'translateX(-50%) rotate(-5deg)';
hawkingImg.style.filter = 'brightness(0.33) contrast(1) saturate(1)';
characterOverlay.appendChild(hawkingImg);
console.log(`✓ Displaying Hawking at cam6`);
}
// 显示 EP(如果已出场且在当前摄像头)
// console.log('🔍 EP Display Check:', {
// hasSpawned: this.game.enemyAI.epstein.hasSpawned,
// epLocation: epLocation,
// currentCam: currentCam,
// match: epLocation === currentCam,
// hasImage: !!this.characterImages,
// imageForCam: this.characterImages ? !!this.characterImages[currentCam] : 'N/A'
// });
if (this.game.enemyAI.epstein.hasSpawned && epLocation === currentCam && this.characterImages && this.characterImages[currentCam]) {
// 创建EP容器(用于包含EP图片和电眼)
const epContainer = document.createElement('div');
epContainer.className = 'ep-container';
epContainer.style.position = 'absolute';
epContainer.style.zIndex = '1';
const pos = this.characterPositions[currentCam];
if (pos) {
if (pos.left) {
epContainer.style.left = pos.left;
epContainer.style.right = 'auto';
} else if (pos.right) {
epContainer.style.right = pos.right;
epContainer.style.left = 'auto';
}
epContainer.style.bottom = pos.bottom;
epContainer.style.width = pos.width;
epContainer.style.transform = pos.transform || 'none';
}
// EP图片
const epImg = document.createElement('img');
epImg.src = this.characterImages[currentCam];
epImg.style.position = 'relative';
epImg.style.width = '100%';
epImg.style.height = 'auto';
epImg.style.display = 'block';
epImg.className = 'visible ep-character';
// 应用明暗度
const brightness = this.characterBrightness[currentCam] || 100;
epImg.style.filter = `brightness(${brightness}%)`;
epContainer.appendChild(epImg);
characterOverlay.appendChild(epContainer);
console.log(`✓ Displaying EP at ${currentCam}`);
// Night 6: 渲染电眼特效(作为EP容器的子元素)
if (this.game.state.currentNight === 6) {
this.renderLightningEyes(epContainer, currentCam);
}
}
// 显示 Trump(如果已出场且在当前摄像头,且不在爬行状态,且当前夜晚有Trump配置)
if (this.game.enemyAI.trump.hasSpawned && !this.game.enemyAI.trump.isCrawling && trumpLocation === currentCam && this.game.enemyAI.currentTrumpConfig) {
const trumpImages = this.game.enemyAI.trumpImages;
const trumpPositions = this.game.enemyAI.trumpPositions;
const trumpBrightness = this.game.enemyAI.trumpBrightness;
if (trumpImages[currentCam]) {
const trumpImg = document.createElement('img');
trumpImg.src = trumpImages[currentCam];
trumpImg.style.position = 'absolute';
trumpImg.className = 'visible trump-character';
trumpImg.style.zIndex = '2'; // Trump 在上层
const pos = trumpPositions[currentCam];
if (pos) {
if (pos.left) {
trumpImg.style.left = pos.left;
trumpImg.style.right = 'auto';
} else if (pos.right) {
trumpImg.style.right = pos.right;
trumpImg.style.left = 'auto';
}
trumpImg.style.bottom = pos.bottom;
trumpImg.style.width = pos.width;
trumpImg.style.transform = pos.transform || 'none';
}
const brightness = trumpBrightness[currentCam] || 100;
trumpImg.style.filter = `brightness(${brightness}%)`;
characterOverlay.appendChild(trumpImg);
console.log(`✓ Displaying Trump at ${currentCam}`);
}
}
if (characterOverlay.children.length === 0) {
console.log(`✗ No characters at current camera (viewing ${currentCam})`);
}
}
createCameraGrid() {
const grid = document.getElementById('camera-grid');
grid.innerHTML = '';
// 创建地图容器
const mapContainer = document.createElement('div');
mapContainer.style.position = 'relative';
mapContainer.style.width = '100%';
mapContainer.style.height = '100%';
// 添加地图图片
const mapImg = document.createElement('img');
mapImg.src = 'assets/images/FNAE-Map-layout.png';
mapImg.style.width = '100%';
mapImg.style.height = 'auto';
mapImg.style.display = 'block';
mapContainer.appendChild(mapImg);
// 添加 YOU 标记(玩家位置)
const youMarker = document.createElement('div');
youMarker.style.position = 'absolute';
youMarker.style.left = '7.0%';
youMarker.style.top = '82.6%';
youMarker.style.width = '13.0%';
youMarker.style.height = '8.0%';
youMarker.style.display = 'flex';
youMarker.style.alignItems = 'center';
youMarker.style.justifyContent = 'center';
youMarker.style.fontSize = '0.7vw';
youMarker.style.fontWeight = 'bold';
youMarker.style.color = '#fff';
youMarker.style.textShadow = '1px 1px 2px #000';
youMarker.style.fontFamily = 'Arial, sans-serif';
youMarker.style.background = 'rgba(0, 0, 0, 0.5)';
youMarker.style.borderRadius = '4px';
youMarker.textContent = 'YOU';
mapContainer.appendChild(youMarker);
// 定义每个摄像头在地图上的位置(百分比)
const cameraPositions = [
{ cam: 1, x: 25.7, y: 84.3, width: 13.0, height: 8.0 },
{ cam: 2, x: 35.0, y: 56.6, width: 13.0, height: 8.0 },
{ cam: 3, x: 51.5, y: 77.6, width: 13.0, height: 8.0 },
{ cam: 4, x: 57.7, y: 44.9, width: 12.9, height: 8.0 },
{ cam: 5, x: 75.4, y: 60.3, width: 12.9, height: 8.0 },
{ cam: 6, x: 77.2, y: 82.2, width: 13.0, height: 8.0 },
{ cam: 7, x: 52.0, y: 27.9, width: 12.9, height: 8.0 },
{ cam: 8, x: 80.2, y: 21.9, width: 12.8, height: 8.0 },
{ cam: 9, x: 24.4, y: 20.6, width: 12.9, height: 8.0 },
{ cam: 10, x: 7.9, y: 39.1, width: 12.8, height: 8.0 },
{ cam: 11, x: 72.9, y: 4.6, width: 13.0, height: 8.0 },
];
// 为每个摄像头创建可点击热区
cameraPositions.forEach(pos => {
const hotspot = document.createElement('div');
hotspot.className = 'camera-hotspot';
hotspot.style.position = 'absolute';
hotspot.style.left = pos.x + '%';
hotspot.style.top = pos.y + '%';
hotspot.style.width = pos.width + '%';
hotspot.style.height = pos.height + '%';
hotspot.style.cursor = 'pointer';
hotspot.style.transition = 'all 0.2s';
hotspot.style.display = 'flex';
hotspot.style.alignItems = 'center';
hotspot.style.justifyContent = 'center';
hotspot.style.fontSize = '0.7vw';
hotspot.style.fontWeight = 'bold';
hotspot.style.color = '#fff';
hotspot.style.textShadow = '1px 1px 2px #000';
hotspot.style.fontFamily = 'Arial, sans-serif';
hotspot.style.whiteSpace = 'nowrap';
hotspot.style.borderRadius = '4px';
hotspot.style.letterSpacing = '0.5px';
// 添加CAM文本
hotspot.textContent = `CAM ${pos.cam}`;
// 当前选中的摄像头绿色闪烁
if (this.game.state.currentCam === `cam${pos.cam}`) {
hotspot.classList.add('camera-selected');
hotspot.style.border = 'none';
} else {
hotspot.style.border = 'none';
hotspot.style.background = 'transparent';
}
// 悬浮效果
hotspot.addEventListener('mouseenter', () => {
if (this.game.state.currentCam !== `cam${pos.cam}`) {
hotspot.style.background = 'rgba(255, 255, 255, 0.2)';
}
});
hotspot.addEventListener('mouseleave', () => {
if (this.game.state.currentCam !== `cam${pos.cam}`) {
hotspot.style.background = 'transparent';
}
});
// 点击切换摄像头
hotspot.addEventListener('click', () => this.switchCamera(pos.cam));
mapContainer.appendChild(hotspot);
});
grid.appendChild(mapContainer);
}
playAmbientSound() {
// 如果在冷却中,不能使用
if (this.soundButtonCooldown) {
console.log('Sound button on cooldown');
return;
}
const currentCam = this.game.state.currentCam;
// 检查EP是否移动了,如果移动了则重置所有位置的计数
const currentEpLocation = this.game.enemyAI.getCurrentLocation();
if (this.lastEpLocation !== currentEpLocation) {
console.log(`EP moved from ${this.lastEpLocation} to ${currentEpLocation}, resetting all location counts`);
this.locationAttractCount = {}; // 重置所有位置计数
this.lastEpLocation = currentEpLocation;
}
// 交替播放 1.ogg 和 2.ogg
const soundFile = this.currentSoundToggle ? '2.ogg' : '1.ogg';
this.currentSoundToggle = !this.currentSoundToggle;
// 创建并播放音频
const audio = new Audio(`assets/sounds/${soundFile}`);
audio.play().catch(e => console.log('音频播放失败:', e));
// 检查当前位置是否已经用完2次
let canAttract = true;
if (this.locationAttractCount[currentCam] >= this.maxLocationAttractCount) {
console.log(`Location ${currentCam} already used ${this.maxLocationAttractCount} times - wasting player's attempt`);
canAttract = false;
}
// 尝试吸引EP到当前摄像头位置(如果位置可用)
let attracted = false;
if (canAttract) {
attracted = this.game.enemyAI.attractToSound(currentCam);
if (attracted) {
// 吸引成功,播放过场动画
this.playAttractionTransition();
// 增加该位置的计数
this.locationAttractCount[currentCam] = (this.locationAttractCount[currentCam] || 0) + 1;
console.log(`Epstein attracted to ${currentCam}! Count: ${this.locationAttractCount[currentCam]}/${this.maxLocationAttractCount}`);
// 更新EP位置记录
this.lastEpLocation = currentCam;
} else {
// 吸引失败(不邻近或其他原因),不给用户提示
console.log('Attraction failed');
}
} else {
// 位置已用完2次,浪费玩家的尝试
console.log('Location maxed out - player wasted an attempt');
}
// 增加使用次数(无论是否成功)
this.soundButtonUseCount++;
console.log(`Sound button used: ${this.soundButtonUseCount}/${this.maxSoundUses}`);
// 检查是否达到最大使用次数
if (this.soundButtonUseCount >= this.maxSoundUses) {
console.log('Sound button overused! Camera failure!');
this.soundButtonUseCount = 0; // 重置计数
// 如果正在播放吸引动画,立即停止
if (this.cameraPanel.classList.contains('transitioning')) {
this.stopStatic();
this.cameraPanel.classList.remove('transitioning');
}
// 触发摄像头故障
this.game.enemyAI.triggerCameraFailure();
}
// 开始冷却
this.soundButtonCooldown = true;
this.playSoundBtn.style.opacity = '0.5';
this.playSoundBtn.style.cursor = 'not-allowed';
// 添加加载动画
this.startCooldownAnimation();
// 8秒后解除冷却
setTimeout(() => {
this.soundButtonCooldown = false;
this.playSoundBtn.style.opacity = '1';
this.playSoundBtn.style.cursor = 'pointer';
this.stopCooldownAnimation();
}, this.cooldownTime);
}
// 开始冷却动画
startCooldownAnimation() {
let dotCount = 0;
this.cooldownInterval = setInterval(() => {
dotCount = (dotCount + 1) % 4;
const dots = '.'.repeat(dotCount);
this.playSoundBtn.textContent = `PLAY SOUND${dots}`;
}, 500);
}
// 停止冷却动画
stopCooldownAnimation() {
if (this.cooldownInterval) {
clearInterval(this.cooldownInterval);
this.cooldownInterval = null;
}
this.playSoundBtn.textContent = 'PLAY SOUND';
}
// 吸引成功的过场动画
playAttractionTransition() {
console.log('Playing attraction transition...');
// 添加过场状态,隐藏背景图片和地图
this.cameraPanel.classList.add('transitioning');
// 隐藏地图
const cameraGrid = document.getElementById('camera-grid');
if (cameraGrid) {
cameraGrid.style.display = 'none';
}
// 隐藏角色
const characterOverlay = document.getElementById('character-overlay');
if (characterOverlay) {
characterOverlay.style.display = 'none';
}
// 暂时降低循环静态音的音量
this.game.assets.setSoundVolume('staticLoop', 0.1);
// 播放正常音量的静态音效
this.game.assets.playSound('static', false, 1.0);
// 1000ms 后停止静态音效
setTimeout(() => {
this.game.assets.stopSound('static');
}, 1000);
// 显示雪花效果
this.startStatic();
// 500ms 后更新显示
setTimeout(() => {
// 如果摄像头已经故障,停止动画并显示故障效果
if (this.game.state.cameraFailed) {
console.log('Camera failed during attraction transition, showing failure effect');
this.showCameraFailure();
return;
}
this.updateCharacterDisplay();
// 再过 500ms 淡出雪花,恢复背景
setTimeout(() => {
// 如果摄像头已经故障,停止动画并显示故障效果
if (this.game.state.cameraFailed) {
console.log('Camera failed during attraction transition, showing failure effect');
this.showCameraFailure();
return;
}
this.stopStatic();
this.cameraPanel.classList.remove('transitioning');
// 显示地图
if (cameraGrid) {
cameraGrid.style.display = 'block';
}
// 显示角色
if (characterOverlay) {
characterOverlay.style.display = 'block';
}
// 恢复循环静态音的音量
this.game.assets.setSoundVolume('staticLoop', 0.3);
}, 500);
}, 500);
}
// 重置声音按钮计数(摄像头重启后调用)
resetSoundButtonCount() {
this.soundButtonUseCount = 0;
}
// EP移动时的过场动画
playMovementTransition() {
console.log('Playing movement transition...');
// 如果摄像头已经故障,不播放动画
if (this.game.state.cameraFailed) {
console.log('Camera already failed, skipping movement transition');
return;
}
// 添加过场状态
this.cameraPanel.classList.add('transitioning');
// 隐藏地图
const cameraGrid = document.getElementById('camera-grid');
if (cameraGrid) {
cameraGrid.style.display = 'none';
}
// 隐藏角色
const characterOverlay = document.getElementById('character-overlay');
if (characterOverlay) {
characterOverlay.style.display = 'none';
}
// 暂时降低循环静态音的音量
this.game.assets.setSoundVolume('staticLoop', 0.1);
// 播放正常音量的静态音效
this.game.assets.playSound('static', false, 1.0);
// 1000ms 后停止静态音效
setTimeout(() => {
this.game.assets.stopSound('static');
}, 1000);
// 显示雪花效果
this.startStatic();
// 500ms 后更新显示
setTimeout(() => {
// 如果摄像头已经故障,停止动画并显示故障效果
if (this.game.state.cameraFailed) {
console.log('Camera failed during movement transition, showing failure effect');
this.showCameraFailure();
return;
}
this.updateCharacterDisplay();
// 再过 500ms 淡出雪花,恢复背景
setTimeout(() => {
// 如果摄像头已经故障,停止动画并显示故障效果
if (this.game.state.cameraFailed) {
console.log('Camera failed during movement transition, showing failure effect');
this.showCameraFailure();
return;
}
this.stopStatic();
this.cameraPanel.classList.remove('transitioning');
// 显示地图
if (cameraGrid) {
cameraGrid.style.display = 'block';
}
// 显示角色
if (characterOverlay) {
characterOverlay.style.display = 'block';
}
// 恢复循环静态音的音量
this.game.assets.setSoundVolume('staticLoop', 0.3);
}, 500);
}, 500);
}
// 电击霍金
shockHawking() {
// 立即播放音效
this.game.assets.playSound('hawking_shock', false, 1.0);
// 显示雪花过场动画
this.cameraPanel.classList.add('transitioning');
// 播放雪花视频
if (this.staticVideo) {
this.staticVideo.classList.add('active');
this.staticVideo.currentTime = 0;
this.staticVideo.play().catch(e => console.log('Video playback failed:', e));
}
// 1秒后执行电击并恢复画面
setTimeout(() => {
if (this.game.enemyAI && this.game.enemyAI.shockHawking()) {
console.log('Hawking shocked successfully!');
}
// 停止雪花视频
if (this.staticVideo) {
this.staticVideo.classList.remove('active');
this.staticVideo.pause();
}
// 恢复摄像头画面
this.cameraPanel.classList.remove('transitioning');
this.updateView();
}, 1000);
}
// 更新电击按钮显示(Night 3-5 和 Custom Night 中 Hawking 激活时显示)
updateShockButtonVisibility() {
if (this.shockHawkingBtn) {
const currentCam = this.game.state.currentCam;
const night = this.game.state.currentNight;
// Night 3-5 显示
const isNormalNight = night >= 3 && night <= 5;
// Custom Night 且 Hawking AI > 0 时显示
const isCustomNightWithHawking = this.game.state.customNight &&
night === 7 &&
this.game.state.customAILevels.hawking > 0;
if ((isNormalNight || isCustomNightWithHawking) && this.game.state.cameraOpen && currentCam === 'cam6') {
this.shockHawkingBtn.style.display = 'block';
} else {
this.shockHawkingBtn.style.display = 'none';
}
}
}
// 渲染电眼特效(Night 6)- 作为EP容器的子元素
renderLightningEyes(epContainer, currentCam) {
const eyesConfig = this.game.enemyAI.lightningEyesConfig[currentCam];
if (!eyesConfig) return;
// 创建两只眼睛(相对于EP图片定位)
[eyesConfig.eye1, eyesConfig.eye2].forEach((eyeConfig, index) => {
// 眼睛容器
const eyeContainer = document.createElement('div');
eyeContainer.className = 'lightning-eye-container';
eyeContainer.style.position = 'absolute';
eyeContainer.style.left = eyeConfig.left;
eyeContainer.style.top = eyeConfig.top;
eyeContainer.style.width = eyeConfig.width;
eyeContainer.style.height = eyeConfig.height;
eyeContainer.style.transform = 'translate(-50%, -50%)';
eyeContainer.style.transformOrigin = 'center center';
eyeContainer.style.zIndex = '10';
eyeContainer.style.pointerEvents = 'none';
// 核心发光点
const core = document.createElement('div');
core.className = 'lightning-eye-core';
core.style.position = 'absolute';
core.style.top = '50%';
core.style.left = '50%';
core.style.width = '60%';
core.style.height = '60%';
core.style.transform = 'translate(-50%, -50%)';
core.style.background = 'radial-gradient(circle, rgba(255, 255, 255, 1) 0%, rgba(0, 255, 255, 1) 40%, rgba(0, 200, 255, 0.6) 70%, transparent 100%)';
core.style.borderRadius = '50%';
core.style.filter = 'brightness(2)';
core.style.animation = 'lightning-pulse 0.15s infinite';
// 外层光晕
const glow = document.createElement('div');
glow.className = 'lightning-eye-glow';
glow.style.position = 'absolute';
glow.style.top = '50%';
glow.style.left = '50%';
glow.style.width = '100%';
glow.style.height = '100%';
glow.style.transform = 'translate(-50%, -50%)';
glow.style.background = 'radial-gradient(ellipse at center, rgba(0, 255, 255, 0.8) 0%, rgba(0, 255, 255, 0.4) 30%, rgba(0, 200, 255, 0.2) 60%, transparent 100%)';
glow.style.borderRadius = '50%';
glow.style.boxShadow = `
0 0 20px rgba(0, 255, 255, 1),
0 0 40px rgba(0, 255, 255, 0.8),
0 0 60px rgba(0, 255, 255, 0.6)
`;
glow.style.animation = 'lightning-flicker 0.1s infinite';
// 雷电效果(多条随机闪电)
for (let i = 0; i < 3; i++) {
const lightning = document.createElement('div');
lightning.className = 'lightning-bolt';
lightning.style.position = 'absolute';
lightning.style.top = '50%';
lightning.style.left = '50%';
lightning.style.width = '2px';
lightning.style.height = `${30 + Math.random() * 40}%`;
lightning.style.background = 'linear-gradient(to bottom, rgba(255, 255, 255, 1), rgba(0, 255, 255, 0.8), transparent)';
lightning.style.transformOrigin = 'top center';
lightning.style.transform = `translate(-50%, -50%) rotate(${Math.random() * 360}deg)`;
lightning.style.boxShadow = '0 0 5px rgba(0, 255, 255, 1), 0 0 10px rgba(0, 255, 255, 0.8)';
lightning.style.animation = `lightning-bolt ${0.1 + Math.random() * 0.1}s infinite`;
lightning.style.animationDelay = `${Math.random() * 0.1}s`;
lightning.style.opacity = '0.8';
eyeContainer.appendChild(lightning);
}
eyeContainer.appendChild(glow);
eyeContainer.appendChild(core);
epContainer.appendChild(eyeContainer);
});
console.log(`⚡ Rendered lightning eyes with electric effects at ${currentCam}`);
}
}