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

@@ -6,12 +6,11 @@ export class CameraManager {
this.renderer = renderer; // Reference to GameRenderer to access scenes/resize if needed
// Configuration
this.zoomLevel = 20; // Orthographic zoom factor
// Configuration
this.zoomLevel = 2.5; // Orthographic zoom factor (Lower = Closer)
this.aspect = window.innerWidth / window.innerHeight;
// Isometric Setup: Orthographic Camera
// Left, Right, Top, Bottom, Near, Far
// Dimensions determined by zoomLevel and aspect
this.camera = new THREE.OrthographicCamera(
-this.zoomLevel * this.aspect,
this.zoomLevel * this.aspect,
@@ -22,9 +21,11 @@ export class CameraManager {
);
// Initial Position: Isometric View
// Looking from "High Corner"
this.camera.position.set(20, 20, 20);
this.camera.lookAt(0, 0, 0);
this.target = new THREE.Vector3(0, 0, 0); // Focus point
this.isoOffset = new THREE.Vector3(20, 20, 20); // Relative offset
this.camera.position.copy(this.target).add(this.isoOffset);
this.camera.lookAt(this.target);
// --- Controls State ---
this.isDragging = false;
@@ -33,8 +34,15 @@ export class CameraManager {
this.panSpeed = 0.5;
// Current Snap View (North, East, South, West)
// We'll define View Angles relative to "Target"
this.currentViewAngle = 0; // 0 = North? We'll refine mapping.
this.currentViewAngle = 0;
// Animation state for smooth transitions
this.isAnimating = false;
this.animationStartPos = new THREE.Vector3();
this.animationTargetPos = new THREE.Vector3();
this.animationProgress = 0;
this.animationDuration = 0.5; // seconds
this.animationStartTime = 0;
this.setupInputListeners();
}
@@ -43,13 +51,20 @@ export class CameraManager {
return this.camera;
}
centerOn(x, y) {
// Grid (x, y) -> World (x, 0, -y)
this.target.set(x, 0, -y);
this.camera.position.copy(this.target).add(this.isoOffset);
this.camera.lookAt(this.target);
}
setupInputListeners() {
// Zoom (Mouse Wheel)
window.addEventListener('wheel', (e) => {
e.preventDefault();
// Adjust Zoom Level property
if (e.deltaY < 0) this.zoomLevel = Math.max(5, this.zoomLevel - 1);
else this.zoomLevel = Math.min(50, this.zoomLevel + 1);
if (e.deltaY < 0) this.zoomLevel = Math.max(3, this.zoomLevel - 1);
else this.zoomLevel = Math.min(30, this.zoomLevel + 1);
this.updateProjection();
}, { passive: false });
@@ -74,7 +89,7 @@ export class CameraManager {
this.lastMouseX = e.clientX;
this.lastMouseY = e.clientY;
this.pan(-dx, dy); // Invert X usually feels natural (drag ground)
this.pan(-dx, dy);
}
});
@@ -94,57 +109,92 @@ export class CameraManager {
}
pan(dx, dy) {
// Panning moves the camera position relative to its local axes
// X movement moves Right/Left
// Y movement moves Up/Down (in screen space)
// Move Target and Camera together
// We pan on the logical "Ground Plane" relative to screen movement
// Since we are isometric, "Up/Down" on screen means moving along the projected Z axis basically.
const moveSpeed = this.panSpeed * 0.05 * (this.zoomLevel / 10);
// Simple implementation: Translate on X and Z (Ground Plane)
// We need to convert screen delta to world delta based on current rotation?
// For 'Fixed' views, it's easier.
// Transform screen delta to world delta
// In Iso view, Right on screen = (1, 0, 1) in world?
// Or using camera right/up vectors
const moveSpeed = this.panSpeed * 0.1 * (this.zoomLevel / 10);
const right = new THREE.Vector3(1, 0, 1).normalize(); // Approx logic for standard Iso
const forward = new THREE.Vector3(-1, 0, 1).normalize();
// Let's use camera vectors for generic support
// Project camera right/up onto XZ plane
// Or just direct translation:
// Basic Pan relative to world for now:
// We really want to move camera.translateX/Y?
this.camera.translateX(dx * moveSpeed);
this.camera.translateY(dy * moveSpeed);
// This moves camera. We need to update target reference too if we want to snap back correctly
// But for now, simple pan is "offsetting everything".
// centerOn resets this.
}
update(deltaTime) {
// Update camera animation if active
if (this.isAnimating) {
const elapsed = (performance.now() - this.animationStartTime) / 1000;
this.animationProgress = Math.min(elapsed / this.animationDuration, 1);
// Easing function (ease-in-out)
const t = this.animationProgress;
const eased = t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t;
// Interpolate position
this.camera.position.lerpVectors(this.animationStartPos, this.animationTargetPos, eased);
this.camera.lookAt(this.target);
// End animation
if (this.animationProgress >= 1) {
this.isAnimating = false;
this.camera.position.copy(this.animationTargetPos);
}
}
}
// --- Fixed Orbit Logic ---
// N, S, E, W
setIsoView(direction) {
// Standard Isometric look from corner
// Distance
const dist = 40;
const height = 30; // 35 degrees up approx?
// Rotate camera around target while maintaining isometric angle
// Isometric view: 45 degree angle from horizontal
const distance = 28; // Distance from target
const isoAngle = Math.PI / 4; // 45 degrees for isometric view
let x, z;
// Horizontal rotation angle based on direction
let horizontalAngle = 0;
switch (direction) {
case DIRECTIONS.NORTH: // Looking North means camera is at South?
// Or Looking FROM North?
// Usually "North View" means "Top of map is North".
// In 3D Iso, standard is X=Right, Z=Down(South).
// "Normal" view: Camera at +X, +Z looking at origin?
x = dist; z = dist;
case DIRECTIONS.NORTH: // 'N'
horizontalAngle = Math.PI / 4; // 45 degrees (NE in isometric)
break;
case DIRECTIONS.SOUTH:
x = -dist; z = -dist;
case DIRECTIONS.EAST: // 'E'
horizontalAngle = -Math.PI / 4; // -45 degrees (SE in isometric)
break;
case DIRECTIONS.EAST:
x = dist; z = -dist;
case DIRECTIONS.SOUTH: // 'S'
horizontalAngle = -3 * Math.PI / 4; // -135 degrees (SW in isometric)
break;
case DIRECTIONS.WEST:
x = -dist; z = dist;
case DIRECTIONS.WEST: // 'W'
horizontalAngle = 3 * Math.PI / 4; // 135 degrees (NW in isometric)
break;
default:
x = dist; z = dist;
}
this.camera.position.set(x, height, z);
this.camera.lookAt(0, 0, 0); // Need to orbit around a pivot actually if we want to pan...
// If we pan, camera.lookAt overrides position logic unless we move the visual target.
// TODO: Implement OrbitControls-like logic with a target.
// Calculate camera position maintaining isometric angle
// x and z form a circle on the horizontal plane
// y is elevated to maintain the isometric angle
const horizontalDistance = distance * Math.cos(isoAngle);
const height = distance * Math.sin(isoAngle);
const x = this.target.x + horizontalDistance * Math.cos(horizontalAngle);
const z = this.target.z + horizontalDistance * Math.sin(horizontalAngle);
// Start animation instead of instant change
this.animationStartPos.copy(this.camera.position);
this.animationTargetPos.set(x, height, z);
this.animationProgress = 0;
this.animationStartTime = performance.now();
this.isAnimating = true;
this.currentViewAngle = horizontalAngle;
}
}