import * as THREE from "three"; import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js"; import { VoxelGrid } from "../grid/VoxelGrid.js"; import { VoxelManager } from "../grid/VoxelManager.js"; // import { UnitManager } from "../managers/UnitManager.js"; import { CaveGenerator } from "../generation/CaveGenerator.js"; import { RuinGenerator } from "../generation/RuinGenerator.js"; // import { TurnSystem } from '../systems/TurnSystem.js'; export class GameLoop { constructor() { this.isRunning = false; // 1. Core Systems this.scene = new THREE.Scene(); this.camera = null; this.renderer = null; this.controls = null; this.grid = null; this.voxelManager = null; this.unitManager = null; // 2. State this.runData = null; } init(container) { // Setup Three.js this.camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 0.1, 1000 ); this.camera.position.set(20, 20, 20); this.camera.lookAt(0, 0, 0); this.renderer = new THREE.WebGLRenderer({ antialias: true }); this.renderer.setSize(window.innerWidth, window.innerHeight); this.renderer.setClearColor(0x111111); // Dark background container.appendChild(this.renderer.domElement); // Setup OrbitControls this.controls = new OrbitControls(this.camera, this.renderer.domElement); this.controls.enableDamping = true; // Smooth camera movement this.controls.dampingFactor = 0.05; this.controls.screenSpacePanning = false; this.controls.minDistance = 5; this.controls.maxDistance = 100; this.controls.maxPolarAngle = Math.PI / 2; // Prevent going below ground // Lighting const ambient = new THREE.AmbientLight(0xffffff, 0.6); const dirLight = new THREE.DirectionalLight(0xffffff, 0.8); dirLight.position.set(10, 20, 10); this.scene.add(ambient); this.scene.add(dirLight); // Handle Resize window.addEventListener("resize", () => { this.camera.aspect = window.innerWidth / window.innerHeight; this.camera.updateProjectionMatrix(); this.renderer.setSize(window.innerWidth, window.innerHeight); }); this.animate = this.animate.bind(this); } /** * Starts a Level based on Run Data (New or Loaded). */ async startLevel(runData) { console.log("GameLoop: Starting Level..."); this.runData = runData; this.isRunning = true; // 1. Initialize Grid (20x10x20 for prototype) this.grid = new VoxelGrid(20, 10, 20); // 2. Generate World (Using saved seed) // TODO: Switch generator based on runData.biome_id const generator = new RuinGenerator(this.grid, runData.seed); generator.generate(); // 3. Initialize Visuals this.voxelManager = new VoxelManager(this.grid, this.scene); // Apply textures generated by the biome logic this.voxelManager.updateMaterials(generator.generatedAssets); this.voxelManager.update(); // Center camera using the focus target if (this.controls) { this.voxelManager.focusCamera(this.controls); } // 4. Initialize Units // this.unitManager = new UnitManager(); // this.spawnSquad(runData.squad); // Start Loop this.animate(); } spawnSquad(squadManifest) { // TODO: Loop manifest and unitManager.createUnit() // Place them at spawn points defined by Generator } animate() { if (!this.isRunning) return; requestAnimationFrame(this.animate); // Update Logic // TWEEN.update(); if (this.controls) { this.controls.update(); } // Render this.renderer.render(this.scene, this.camera); } stop() { this.isRunning = false; // Cleanup Three.js resources if needed if (this.controls) this.controls.dispose(); } }