2025-12-19 23:07:36 +00:00
|
|
|
import * as THREE from "three";
|
2025-12-19 23:08:54 +00:00
|
|
|
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
|
2025-12-19 23:07:36 +00:00
|
|
|
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;
|
2025-12-19 23:08:54 +00:00
|
|
|
this.controls = null;
|
2025-12-19 23:07:36 +00:00
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
2025-12-19 23:08:54 +00:00
|
|
|
// 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
|
|
|
|
|
|
2025-12-19 23:07:36 +00:00
|
|
|
// 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();
|
2025-12-19 23:08:54 +00:00
|
|
|
|
|
|
|
|
// Center camera using the focus target
|
|
|
|
|
if (this.controls) {
|
|
|
|
|
this.voxelManager.focusCamera(this.controls);
|
|
|
|
|
}
|
2025-12-19 23:07:36 +00:00
|
|
|
|
|
|
|
|
// 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();
|
|
|
|
|
|
2025-12-19 23:08:54 +00:00
|
|
|
if (this.controls) {
|
|
|
|
|
this.controls.update();
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-19 23:07:36 +00:00
|
|
|
// Render
|
|
|
|
|
this.renderer.render(this.scene, this.camera);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
stop() {
|
|
|
|
|
this.isRunning = false;
|
|
|
|
|
// Cleanup Three.js resources if needed
|
2025-12-19 23:08:54 +00:00
|
|
|
if (this.controls) this.controls.dispose();
|
2025-12-19 23:07:36 +00:00
|
|
|
}
|
|
|
|
|
}
|