feat: Implement door interaction system and UI improvements

- Add interactive door system with click detection on door meshes
- Create custom DoorModal component replacing browser confirm()
- Implement door opening with texture change to door1_open.png
- Add additive door rendering to preserve opened doors
- Remove exploration button and requestExploration method
- Implement camera orbit controls with smooth animations
- Add active view indicator (yellow highlight) on camera buttons
- Add vertical zoom slider with label
- Fix camera to maintain isometric perspective while rotating
- Integrate all systems into main game loop
This commit is contained in:
2026-01-01 17:16:58 +01:00
parent fd1708688a
commit 9234a2e3a0
19 changed files with 1220 additions and 100 deletions

View File

@@ -1,10 +1,13 @@
import { DungeonGenerator } from './engine/dungeon/DungeonGenerator.js';
import { GameEngine } from './engine/game/GameEngine.js';
import { GameRenderer } from './view/GameRenderer.js';
import { CameraManager } from './view/CameraManager.js';
import { UIManager } from './view/UIManager.js';
import { DoorModal } from './view/DoorModal.js';
import { MissionConfig, MISSION_TYPES } from './engine/dungeon/MissionConfig.js';
console.log("Initializing Warhammer Quest Engine... VERSION: TEXTURE_DEBUG_V1");
window.TEXTURE_DEBUG = true; // Global flag we can check
console.log("Initializing Warhammer Quest Engine... SYSTEM: GAME_LOOP_ARC_V1");
window.TEXTURE_DEBUG = true;
// 1. Setup Mission
const mission = new MissionConfig({
@@ -14,53 +17,147 @@ const mission = new MissionConfig({
minTiles: 6
});
// 2. Init Engine
import { GameRenderer } from './view/GameRenderer.js';
import { CameraManager } from './view/CameraManager.js';
import { UIManager } from './view/UIManager.js';
import { DIRECTIONS } from './engine/dungeon/Constants.js';
// 2. Initialize Core Systems
const renderer = new GameRenderer('app'); // Visuals
const cameraManager = new CameraManager(renderer); // Camera
const game = new GameEngine(); // Logic Brain
const renderer = new GameRenderer('app'); // Assuming <div id="app"> or body
const cameraManager = new CameraManager(renderer);
const generator = new DungeonGenerator();
const ui = new UIManager(cameraManager, generator);
// 3. Initialize UI
// UIManager currently reads directly from DungeonGenerator for minimap
const ui = new UIManager(cameraManager, game);
const doorModal = new DoorModal();
// Hook generator to renderer (Primitive Event system)
// We simply check placedTiles changes or adding methods
// Global Access for Debugging in Browser Console
window.GAME = game;
window.RENDERER = renderer;
// 4. Bridge Logic & View (Event Hook)
// When logic places a tile, we tell the renderer to spawn 3D meshes.
// Ideally, this should be an Event in GameEngine, but we keep this patch for now to verify.
const generator = game.dungeon;
const originalPlaceTile = generator.grid.placeTile.bind(generator.grid);
generator.grid.placeTile = (instance, def) => {
originalPlaceTile(instance, def);
// Visual Spawn
// We need to spawn the actual shape. For now `addTile` does a bug cube.
// Ideally we iterate the cells of the tile and spawn cubes.
// Quick Hack: Spawn a cube for every occupied cell of this tile
generator.grid.placeTile = (instance, def) => {
// 1. Execute Logic
originalPlaceTile(instance, def);
// 2. Execute Visuals
const cells = generator.grid.getGlobalCells(def, instance.x, instance.y, instance.rotation);
renderer.addTile(cells, def.type, def, instance);
// 3. Update Exits Visuals
setTimeout(() => {
renderer.renderExits(generator.pendingExits);
}, 50); // Small delay to ensure logic updated pendingExits
};
// 3. Start
console.log("Starting Dungeon Generation...");
// 5. Connect UI Buttons to Game Actions (Temporary)
// We will add a temporary button in pure JS here or modify UIManager later.
// For now, let's expose a global function for the UI to call if needed,
// or simply rely on UIManager updates.
generator.startDungeon(mission);
// 6. Start the Game
// 5a. Bridge Game Interactions
// 5a. Bridge Game Interactions
game.onEntityUpdate = (entity) => {
renderer.addEntity(entity);
renderer.updateEntityPosition(entity);
// 4. Render Loop
let lastStepTime = 0;
const STEP_DELAY = 1000; // 1 second delay
// Initial Center on Player Spawn
if (entity.id === 'p1' && !entity._centered) {
cameraManager.centerOn(entity.x, entity.y);
entity._centered = true;
}
};
game.onEntityMove = (entity, path) => {
renderer.moveEntityAlongPath(entity, path);
};
game.onEntitySelect = (entityId, isSelected) => {
renderer.toggleEntitySelection(entityId, isSelected);
};
renderer.onHeroFinishedMove = (x, y) => {
// x, y are World Coordinates (x, -z grid)
// Actually, renderer returns Mesh Position.
// Mesh X = Grid X. Mesh Z = -Grid Y.
// Camera centerOn takes (Grid X, Grid Y).
// So we need to convert back?
// centerOn implementation: this.target.set(x, 0, -y);
// If onHeroFinishedMove passes (mesh.x, -mesh.z), that is (Grid X, Grid Y).
// Let's verify what we passed in renderer:
// this.onHeroFinishedMove(mesh.position.x, -mesh.position.z);
// So if mesh is at (5, 1.5, -5), we pass (5, 5).
// centerOn(5, 5) -> target(5, 0, -5). Correct.
cameraManager.centerOn(x, y);
};
game.onPathChange = (path) => {
renderer.highlightCells(path);
};
// Custom click handler that checks for doors first
const handleCellClick = async (x, y, doorMesh) => {
// If doorMesh is provided, user clicked directly on a door texture
if (doorMesh && doorMesh.userData.isDoor && !doorMesh.userData.isOpen) {
// Get player position
const player = game.player;
if (!player) {
console.log('[Main] Player not found');
return;
}
// Check if player is adjacent to the door
if (renderer.isPlayerAdjacentToDoor(player.x, player.y, doorMesh)) {
// Show modal
const confirmed = await doorModal.show('¿Quieres abrir la puerta?');
if (confirmed) {
// Open the door
renderer.openDoor(doorMesh);
// Trigger exploration of the next tile
const exitCell = doorMesh.userData.cells[0];
console.log('[Main] Opening door at exit:', exitCell);
// Call game logic to explore through this exit
game.exploreExit(exitCell);
}
} else {
console.log('[Main] Player is not adjacent to the door. Move closer first.');
}
} else if (x !== null && y !== null) {
// Normal cell click (no door involved)
game.onCellClick(x, y);
}
};
renderer.setupInteraction(
() => cameraManager.getCamera(),
handleCellClick,
(x, y) => game.onCellRightClick(x, y)
);
console.log("--- Starting Game Session ---");
game.startMission(mission);
// 7. Render Loop
const animate = (time) => {
requestAnimationFrame(animate);
// Logic Step with Delay
if (!generator.isComplete) {
if (time - lastStepTime > STEP_DELAY) {
console.log("--- Executing Generation Step ---");
generator.step();
lastStepTime = time;
}
}
// Update Game Logic (State Machine, Timers, etc)
game.update(time);
// Render
// Update Camera Animations
cameraManager.update(time);
// Update Visual Animations
renderer.updateAnimations(time);
// Render Frame
renderer.render(cameraManager.getCamera());
};
animate(0);