aether-shards/src/core/GameLoop.js

181 lines
5.2 KiB
JavaScript
Raw Normal View History

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
// Mock Registry for Prototype so UnitManager doesn't crash on createUnit
const mockRegistry = {
get: (id) => {
return {
type: "EXPLORER",
name: id, // Fallback name
stats: { hp: 100, attack: 10, speed: 10 },
};
},
};
this.unitManager = new UnitManager(mockRegistry);
this.spawnSquad(runData.squad);
// Start Loop
this.animate();
}
spawnSquad(squadManifest) {
if (!squadManifest || !this.unitManager) return;
// Simple spawn logic: line them up starting at (2, 1, 2)
// In a full implementation, the Generator would provide 'StartPoints'
let spawnX = 2;
let spawnZ = 2;
const spawnY = 1; // Assuming flat floor at y=1 for Ruins
squadManifest.forEach((member) => {
if (!member) return;
// Create Unit (this uses the registry to look up stats)
const unit = this.unitManager.createUnit(member.classId, "PLAYER");
// Override name if provided in manifest
if (member.name) unit.name = member.name;
// Find a valid spot (basic collision check)
let placed = false;
// Try a few spots if the first is taken
for (let i = 0; i < 5; i++) {
const pos = { x: spawnX + i, y: spawnY, z: spawnZ };
// Ensure we don't spawn inside a wall or off the map
if (this.grid.isValidBounds(pos) && !this.grid.isSolid(pos)) {
this.grid.placeUnit(unit, pos);
console.log(
`Spawned ${unit.name} (${unit.id}) at ${pos.x},${pos.y},${pos.z}`
);
placed = true;
// Update X for next unit to be next to this one
spawnX = pos.x + 1;
break;
}
}
if (!placed) {
console.warn(`Could not find spawn point for ${unit.name}`);
}
});
}
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();
}
}