Current directory: /home/klas4s23/domains/585455.klas4s23.mid-ica.nl/public_html/Gastenboek/uploads
// ========================================
// ============= GAME CLASS ===============
// ========================================
import * as THREE from 'three';
import { CONFIG } from '../config.js';
import { Car } from './Car.js';
import { CameraController } from './CameraController.js';
import { Environment } from './Environment.js';
import { InteractionManager } from './InteractionManager.js';
import { Collectible } from './Collectible.js';
import { HUD } from './HUD.js';
import { IntroManager } from './IntroManager.js';
import { PerformanceManager } from './PerformanceManager.js';
import { PostProcessingManager } from './PostProcessingManager.js';
import { SettingsManager } from './SettingsManager.js';
import { AudioManager } from './AudioManager.js';
export class Game {
constructor() {
// Three.js core
this.scene = null;
this.camera = null;
this.renderer = null;
// Loading management
this.loadingManager = null;
this.loadingProgress = 0;
// Game systems
this.car = null;
this.cameraController = null;
this.environment = null;
this.interactionManager = null;
this.collectibles = [];
this.hud = null;
this.introManager = null;
this.performanceManager = null;
this.postProcessing = null;
this.settingsManager = null;
// Game state
this.gameState = 'loading'; // 'loading' | 'intro' | 'playing'
// Time
this.clock = new THREE.Clock();
this.time = 0;
// Collectibles
this.collectedCount = 0; // Track collected count
this._lastAchievementCheck = 0; // Throttle achievement checks
// Achievements
this.achievements = [];
this.unlockedAchievements = new Set();
// Performance
this.stats = null;
this.init();
}
// ========== INITIALIZATION ==========
async init() {
this.setupLoadingManager();
await this.updateLoadingProgressAsync(0, 'Initializing...', 100);
this.audioManager = new AudioManager();
this.setupRenderer();
await this.updateLoadingProgressAsync(10, 'Setting up scene...', 150);
this.setupScene();
await this.updateLoadingProgressAsync(20, 'Creating camera...', 100);
this.setupCamera();
await this.updateLoadingProgressAsync(30, 'Building car...', 200);
this.createCar();
await this.updateLoadingProgressAsync(40, 'Creating environment...', 300);
this.createEnvironment();
await this.updateLoadingProgressAsync(60, 'Spawning collectibles...', 150);
this.createCollectibles();
await this.updateLoadingProgressAsync(70, 'Setting up camera...', 100);
this.createCameraController();
await this.updateLoadingProgressAsync(80, 'Loading interactions...', 150);
this.createInteractionManager();
await this.updateLoadingProgressAsync(85, 'Preparing HUD...', 100);
this.createHUD();
await this.updateLoadingProgressAsync(90, 'Setting up intro...', 150);
this.createIntroManager();
await this.updateLoadingProgressAsync(95, 'Initializing performance systems...', 100);
this.createPerformanceManager();
this.createPostProcessing();
this.createSettingsManager();
await this.updateLoadingProgressAsync(98, 'Finalizing...', 100);
this.setupAchievements();
this.setupEventListeners();
await this.updateLoadingProgressAsync(100, 'Ready!', 200);
// Start animation loop
this.animate();
// Hide loading screen and show intro after a brief delay
setTimeout(() => {
this.hideLoadingScreen();
this.gameState = 'intro';
}, 300);
// Transition from loading to intro music (guarded)
setTimeout(() => {
if (this.audioManager) {
this.audioManager.stopMusic(true); // Fade out loading
setTimeout(() => {
if (this.audioManager) this.audioManager.playMusic('intro', true); // Fade in intro
}, 1000);
}
this.hideLoadingScreen();
}, 500);
}
setupLoadingManager() {
this.loadingManager = new THREE.LoadingManager();
this.loadingManager.onStart = (url, itemsLoaded, itemsTotal) => {
console.log(`Loading: ${url}`);
};
this.loadingManager.onProgress = (url, itemsLoaded, itemsTotal) => {
const progress = (itemsLoaded / itemsTotal) * 100;
console.log(`Loading progress: ${progress.toFixed(2)}%`);
};
this.loadingManager.onLoad = () => {
console.log('All assets loaded');
};
this.loadingManager.onError = (url) => {
console.error(`Error loading: ${url}`);
};
}
updateLoadingProgress(percentage, message) {
this.loadingProgress = percentage;
const progressBar = document.querySelector('.loading-progress');
const loadingText = document.querySelector('#loading-text');
const loadingPercentage = document.querySelector('.loading-percentage');
if (progressBar) {
progressBar.style.width = `${percentage}%`;
}
if (loadingText) {
loadingText.textContent = message;
}
if (loadingPercentage) {
loadingPercentage.textContent = `${Math.round(percentage)}%`;
}
}
// Async version with delay for visual feedback
updateLoadingProgressAsync(percentage, message, delay = 100) {
return new Promise((resolve) => {
this.updateLoadingProgress(percentage, message);
setTimeout(resolve, delay);
});
}
hideLoadingScreen() {
const loadingScreen = document.getElementById('loading-screen');
if (loadingScreen) {
// Fade out animation
loadingScreen.style.opacity = '0';
setTimeout(() => {
loadingScreen.style.display = 'none';
}, 500);
}
}
setupRenderer() {
this.renderer = new THREE.WebGLRenderer({
antialias: CONFIG.performance.antialias
});
this.renderer.setSize(window.innerWidth, window.innerHeight);
this.renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
this.renderer.shadowMap.enabled = CONFIG.performance.shadows;
this.renderer.shadowMap.type = THREE.PCFSoftShadowMap;
// Append to game container instead of body
const gameContainer = document.getElementById('game-container');
if (gameContainer) {
gameContainer.appendChild(this.renderer.domElement);
} else {
document.body.appendChild(this.renderer.domElement);
}
}
setupScene() {
this.scene = new THREE.Scene();
this.scene.fog = new THREE.Fog(
CONFIG.colors.body,
CONFIG.world.fogNear,
CONFIG.world.fogFar
);
}
setupCamera() {
this.camera = new THREE.PerspectiveCamera(
CONFIG.camera.fov,
window.innerWidth / window.innerHeight,
0.1,
1000
);
}
// ========== GAME OBJECTS ==========
createCar() {
this.car = new Car(this.scene, this.audioManager);
return this.car;
}
createEnvironment() {
this.environment = new Environment(this.scene);
}
createCollectibles() {
const positions = [
new THREE.Vector3(-60, 2, 40),
new THREE.Vector3(60, 2, 40),
new THREE.Vector3(-60, 2, -40),
new THREE.Vector3(60, 2, -40),
new THREE.Vector3(-30, 2, 30),
new THREE.Vector3(30, 2, -30),
new THREE.Vector3(0, 2, 60),
new THREE.Vector3(0, 2, -60)
];
positions.forEach((pos, index) => {
// Add this.audioManager as the 4th parameter
const collectible = new Collectible(this.scene, pos, index, this.audioManager);
this.collectibles.push(collectible);
});
}
createCameraController() {
this.cameraController = new CameraController(
this.camera,
this.car,
this.renderer
);
}
createInteractionManager() {
this.interactionManager = new InteractionManager(
this.scene,
this.car,
this.environment
);
}
createHUD() {
this.hud = new HUD(this.car, this.collectibles);
// Always hide HUD initially - only show when game starts
this.hud.hide();
}
createIntroManager() {
// Disable car controls during intro
this.car.disableControls();
// Create intro manager
this.introManager = new IntroManager(this.car, this.camera);
// Setup callback for when intro transition completes
this.introManager.onTransitionComplete = () => {
this.onIntroComplete();
};
}
createPerformanceManager() {
this.performanceManager = new PerformanceManager(this.renderer);
console.log('⚡ Performance Manager initialized');
}
createPostProcessing() {
this.postProcessing = new PostProcessingManager(
this.renderer,
this.scene,
this.camera
);
console.log('🎨 Post-Processing initialized');
}
createSettingsManager() {
this.settingsManager = new SettingsManager(this);
// Apply saved settings
this.settingsManager.applySettings();
console.log('⚙️ Settings Manager initialized');
}
async onIntroComplete() {
console.log('🎮 Starting gameplay...');
this.gameState = 'playing';
if (this.audioManager) {
// Stop intro music with fade
await this.audioManager.stopMusic(true);
// Start gameplay music
await this.audioManager.playMusic('gameplay', true);
// Start sound effects
this.audioManager.startEngine();
this.audioManager.playSoundLoop('nightAmbience', 0.2);
}
if (this.car) {
this.car.enableControls();
}
if (this.hud) {
this.hud.show();
}
}
// ==================================
// ========== ACHIEVEMENTS ==========
// ==================================
setupAchievements() {
this.achievements = CONFIG.achievements.list.map(achievement => ({
...achievement,
unlocked: false
}));
}
checkAchievements() {
// Only check achievements once per second, not every frame!
if (this.time - this._lastAchievementCheck < 1.0) {
return;
}
this._lastAchievementCheck = this.time;
const stats = this.car.getStats();
// Use cached count instead of filtering every frame
const collectedCount = this.collectedCount;
this.achievements.forEach((achievement) => {
if (achievement.unlocked || this.unlockedAchievements.has(achievement.id)) {
return;
}
let unlock = false;
switch (achievement.id) {
case 'first_drive':
unlock = stats.distanceTraveled > 10;
break;
case 'speed_demon':
unlock = stats.maxSpeedReached >= CONFIG.car.maxSpeed * 0.95;
break;
case 'explorer':
unlock = stats.distanceTraveled > 500;
break;
case 'collector':
unlock = collectedCount >= 1;
break;
case 'master_collector':
unlock = collectedCount >= this.collectibles.length;
break;
case 'night_owl':
unlock = this.environment && this.environment.isNight();
break;
}
if (unlock) {
this.unlockAchievement(achievement);
}
});
}
collect(currentTime) {
if (this.collected) return false;
this.collected = true;
this.collectStartTime = currentTime;
// DEBUG: Check if audioManager exists
console.log('Collectible collected!', {
hasAudioManager: !!this.audioManager,
audioManager: this.audioManager
});
// Play collection sound
if (this.audioManager) {
console.log('Attempting to play collectible sound...');
this.audioManager.playSound('collectible', 0.8);
} else {
console.warn('No audioManager available for collectible sound!');
}
// Create particle burst effect
this.createCollectionParticles();
// Brighten the light temporarily
if (this.pointLight) {
this.pointLight.intensity = this.config.lightIntensity * 3;
}
return true;
}
unlockAchievement(achievement) {
achievement.unlocked = true;
this.unlockedAchievements.add(achievement.id);
if (this.hud) {
this.hud.showAchievement(achievement);
}
console.log(`🏆 Achievement Unlocked: ${achievement.name}`);
}
// ========== COLLECTIBLES ==========
checkCollectibles() {
const carPosition = this.car.getPosition();
this.collectibles.forEach(collectible => {
if (!collectible.isCollected() && collectible.checkCollision(carPosition)) {
console.time('collect');
collectible.collect(this.time);
console.timeEnd('collect');
this.collectedCount++;
}
});
}
cleanupCollectedItems() {
this.collectibles.forEach(collectible => {
if (collectible.isCollected() && collectible.mesh && !collectible.mesh.visible) {
collectible.dispose();
}
});
// Remove disposed collectibles from array
this.collectibles = this.collectibles.filter(c => c.mesh !== null);
}
// ========== EVENT LISTENERS ==========
setupEventListeners() {
// Window resize
window.addEventListener('resize', () => this.onWindowResize());
// Keyboard shortcuts
document.addEventListener('keydown', (e) => {
// H key - toggle HUD
if (e.key === 'h' || e.key === 'H') {
if (this.hud) {
const hudContainer = document.getElementById('hud-container');
if (hudContainer) {
hudContainer.style.display =
hudContainer.style.display === 'none' ? 'block' : 'none';
}
}
}
});
}
onWindowResize() {
this.camera.aspect = window.innerWidth / window.innerHeight;
this.camera.updateProjectionMatrix();
this.renderer.setSize(window.innerWidth, window.innerHeight);
// Update post-processing
if (this.postProcessing) {
this.postProcessing.resize();
}
}
// ========== GAME LOOP ==========
animate() {
requestAnimationFrame(() => this.animate());
let deltaTime = this.clock.getDelta();
// Cap deltaTime to prevent huge jumps (jitter prevention)
// Max 60ms = ~16 FPS minimum
deltaTime = Math.min(deltaTime, 0.06);
this.time = this.clock.getElapsedTime();
// Start performance tracking
if (this.performanceManager) {
this.performanceManager.startFrame();
}
// Don't update during loading
if (this.gameState === 'loading') {
return;
}
// Check if paused
const isPaused = this.settingsManager && this.settingsManager.isPausedState();
// Handle intro state
if (this.gameState === 'intro') {
if (this.introManager) {
this.introManager.update(deltaTime);
}
// Always update environment during intro for visual effects
if (this.environment) {
this.environment.update(deltaTime);
}
this.render();
return;
}
// Don't update car if modal is open OR if paused
const isModalOpen = this.interactionManager &&
this.interactionManager.getIsModalOpen();
if (!isModalOpen && !isPaused) {
this.update(deltaTime);
} else {
// Still update camera and render even when modal is open or paused
if (this.cameraController) {
this.cameraController.update(deltaTime);
}
}
this.render();
// End performance tracking and update FPS counter
if (this.performanceManager) {
this.performanceManager.endFrame();
if (this.settingsManager) {
const fps = this.performanceManager.getFPS();
const frameTime = this.performanceManager.getFrameTime();
const metrics = this.performanceManager.getMetrics();
this.settingsManager.updateFPS(fps, frameTime, metrics);
}
}
}
update(deltaTime) {
// Check if we should throttle expensive updates (NOT car physics!)
const shouldUpdateAI = !this.performanceManager ||
this.performanceManager.shouldUpdate('ai');
// Update car (ALWAYS - no throttling for smooth movement)
if (this.car) {
this.car.update(deltaTime);
}
// Update camera (ALWAYS - no throttling for smooth movement)
if (this.cameraController) {
this.cameraController.update(deltaTime);
}
// Update environment (throttled for AI/animations only)
if (this.environment && shouldUpdateAI) {
this.environment.update(deltaTime);
}
// Update collectibles
this.collectibles.forEach(collectible => {
collectible.update(deltaTime, this.time);
});
// Check collectible collisions
this.checkCollectibles();
// Update interaction manager
if (this.interactionManager) {
this.interactionManager.update();
}
// Cleanup collected items periodically
if (!this._lastCleanup) this._lastCleanup = 0;
if (this.time - this._lastCleanup > 5) { // Every 5 seconds
this.cleanupCollectedItems();
this._lastCleanup = this.time;
}
// Update HUD
if (this.hud) {
this.hud.update();
}
// Check achievements
this.checkAchievements();
}
render() {
// Use post-processing if enabled
if (this.postProcessing && this.postProcessing.enabled) {
this.postProcessing.render(this.clock.getDelta());
} else {
this.renderer.render(this.scene, this.camera);
}
// Update performance metrics
if (this.performanceManager) {
this.performanceManager.updateMetrics(this.renderer);
}
}
// ========== PUBLIC API ==========
getScene() {
return this.scene;
}
getCar() {
return this.car;
}
getCamera() {
return this.camera;
}
getEnvironment() {
return this.environment;
}
getCollectibles() {
return this.collectibles;
}
getAchievements() {
return this.achievements;
}
// ========== CLEANUP ==========
dispose() {
// Cleanup Three.js resources
this.scene.traverse(object => {
if (object.geometry) {
object.geometry.dispose();
}
if (object.material) {
if (Array.isArray(object.material)) {
object.material.forEach(material => material.dispose());
} else {
object.material.dispose();
}
}
});
this.renderer.dispose();
// Remove DOM elements
if (this.renderer.domElement.parentNode) {
this.renderer.domElement.parentNode.removeChild(this.renderer.domElement);
}
console.log('Game disposed');
}
}
export default Game;