From 525a92a2ebab8699315b87c66d9727cd94a4fbb8 Mon Sep 17 00:00:00 2001 From: Matthew Mone Date: Tue, 23 Dec 2025 20:28:12 -0800 Subject: [PATCH] Refactor GameLoop and MovementSystem to improve unit positioning and enemy spawning logic. Adjust unit height calculations for better alignment with floor surfaces. Enhance movement highlights with multiple glow layers for improved visibility. Update reachable position checks to utilize accurate walkable heights. --- src/core/GameLoop.js | 134 +++++++++++++++++++++++++++++----- src/systems/MovementSystem.js | 5 +- 2 files changed, 117 insertions(+), 22 deletions(-) diff --git a/src/core/GameLoop.js b/src/core/GameLoop.js index eea26b2..0c86028 100644 --- a/src/core/GameLoop.js +++ b/src/core/GameLoop.js @@ -334,9 +334,10 @@ export class GameLoop { // Update unit mesh position const mesh = this.unitMeshes.get(activeUnit.id); if (mesh) { + // Floor surface is at pos.y - 0.5, unit should be 0.6 above: pos.y + 0.1 mesh.position.set( activeUnit.position.x, - activeUnit.position.y + 0.6, + activeUnit.position.y + 0.1, activeUnit.position.z ); } @@ -513,7 +514,8 @@ export class GameLoop { // Update Mesh const mesh = this.unitMeshes.get(existingUnit.id); if (mesh) { - mesh.position.set(targetTile.x, targetTile.y + 0.6, targetTile.z); + // Floor surface is at pos.y - 0.5, unit should be 0.6 above: pos.y + 0.1 + mesh.position.set(targetTile.x, targetTile.y + 0.1, targetTile.z); } console.log( `Moved ${existingUnit.name} to ${targetTile.x},${targetTile.y},${targetTile.z}` @@ -554,14 +556,36 @@ export class GameLoop { ) return; const enemyCount = 2; - for (let i = 0; i < enemyCount; i++) { + let attempts = 0; + const maxAttempts = this.enemySpawnZone.length * 2; // Try up to 2x the zone size + + for (let i = 0; i < enemyCount && attempts < maxAttempts; attempts++) { const spotIndex = Math.floor(Math.random() * this.enemySpawnZone.length); const spot = this.enemySpawnZone[spotIndex]; - if (spot && !this.grid.isOccupied(spot)) { + + if (!spot) continue; + + // Check if position is walkable (not just unoccupied) + // Find the correct walkable Y for this position + const walkableY = this.movementSystem?.findWalkableY( + spot.x, + spot.z, + spot.y + ); + if (walkableY === null) continue; + + const walkablePos = { x: spot.x, y: walkableY, z: spot.z }; + + // Check if position is not occupied and is walkable (not solid) + if ( + !this.grid.isOccupied(walkablePos) && + !this.grid.isSolid(walkablePos) + ) { const enemy = this.unitManager.createUnit("ENEMY_DEFAULT", "ENEMY"); - this.grid.placeUnit(enemy, spot); - this.createUnitMesh(enemy, spot); + this.grid.placeUnit(enemy, walkablePos); + this.createUnitMesh(enemy, walkablePos); this.enemySpawnZone.splice(spotIndex, 1); + i++; // Only increment if we successfully placed an enemy } } @@ -651,24 +675,92 @@ export class GameLoop { const reachablePositions = this.movementSystem.getReachableTiles(activeUnit); - // Create blue highlight material - const highlightMaterial = new THREE.MeshBasicMaterial({ - color: 0x0066ff, // Blue color + // Create glowing blue outline materials with multiple layers for enhanced glow + // Outer glow layers (fade outward, decreasing opacity) + const outerGlowMaterial = new THREE.LineBasicMaterial({ + color: 0x0066ff, transparent: true, - opacity: 0.4, + opacity: 0.3, }); - // Create geometry for highlights (plane on the ground) - const geometry = new THREE.PlaneGeometry(1, 1); - geometry.rotateX(-Math.PI / 2); + const midGlowMaterial = new THREE.LineBasicMaterial({ + color: 0x0088ff, + transparent: true, + opacity: 0.5, + }); - // Create highlight meshes for each reachable position + // Inner bright outline (main glow - brightest) + const highlightMaterial = new THREE.LineBasicMaterial({ + color: 0x00ccff, // Very bright cyan-blue for maximum visibility + transparent: true, + opacity: 1.0, + }); + + // Thick inner outline (for thickness simulation) + const thickMaterial = new THREE.LineBasicMaterial({ + color: 0x00aaff, + transparent: true, + opacity: 0.8, + }); + + // Create base plane geometry for the tile + const baseGeometry = new THREE.PlaneGeometry(1, 1); + baseGeometry.rotateX(-Math.PI / 2); + + // Create highlight outlines for each reachable position reachablePositions.forEach((pos) => { - const mesh = new THREE.Mesh(geometry, highlightMaterial); - // Position just above floor surface (pos.y is the air space, floor surface is at pos.y) - mesh.position.set(pos.x, pos.y + 0.01, pos.z); - this.scene.add(mesh); - this.movementHighlights.add(mesh); + // Get the correct floor surface height for this position + const walkableY = this.movementSystem.findWalkableY(pos.x, pos.z, pos.y); + if (walkableY === null) return; // Skip if no valid floor found + + // Floor surface is at the walkable Y coordinate (top of the floor block) + // Adjust by -0.5 to account for voxel centering + const floorSurfaceY = walkableY - 0.5; + + // Create multiple glow layers for enhanced visibility and fade effect + // Outer glow (largest, most transparent) + const outerGlowGeometry = new THREE.PlaneGeometry(1.15, 1.15); + outerGlowGeometry.rotateX(-Math.PI / 2); + const outerGlowEdges = new THREE.EdgesGeometry(outerGlowGeometry); + const outerGlowLines = new THREE.LineSegments( + outerGlowEdges, + outerGlowMaterial + ); + outerGlowLines.position.set(pos.x, floorSurfaceY + 0.003, pos.z); + this.scene.add(outerGlowLines); + this.movementHighlights.add(outerGlowLines); + + // Mid glow (medium size) + const midGlowGeometry = new THREE.PlaneGeometry(1.08, 1.08); + midGlowGeometry.rotateX(-Math.PI / 2); + const midGlowEdges = new THREE.EdgesGeometry(midGlowGeometry); + const midGlowLines = new THREE.LineSegments( + midGlowEdges, + midGlowMaterial + ); + midGlowLines.position.set(pos.x, floorSurfaceY + 0.002, pos.z); + this.scene.add(midGlowLines); + this.movementHighlights.add(midGlowLines); + + // Thick inner outline (slightly larger than base for thickness) + const thickGeometry = new THREE.PlaneGeometry(1.02, 1.02); + thickGeometry.rotateX(-Math.PI / 2); + const thickEdges = new THREE.EdgesGeometry(thickGeometry); + const thickLines = new THREE.LineSegments(thickEdges, thickMaterial); + thickLines.position.set(pos.x, floorSurfaceY + 0.001, pos.z); + this.scene.add(thickLines); + this.movementHighlights.add(thickLines); + + // Main bright outline (exact size, brightest) + const edgesGeometry = new THREE.EdgesGeometry(baseGeometry); + const lineSegments = new THREE.LineSegments( + edgesGeometry, + highlightMaterial + ); + // Position exactly on floor surface + lineSegments.position.set(pos.x, floorSurfaceY, pos.z); + this.scene.add(lineSegments); + this.movementHighlights.add(lineSegments); }); } @@ -684,7 +776,9 @@ export class GameLoop { else if (unit.team === "ENEMY") color = 0x550000; const material = new THREE.MeshStandardMaterial({ color: color }); const mesh = new THREE.Mesh(geometry, material); - mesh.position.set(pos.x, pos.y + 0.6, pos.z); + // Floor surface is at pos.y - 0.5 (floor block at pos.y-1, top at pos.y-0.5) + // Unit should be 0.6 units above floor surface: (pos.y - 0.5) + 0.6 = pos.y + 0.1 + mesh.position.set(pos.x, pos.y + 0.1, pos.z); this.scene.add(mesh); this.unitMeshes.set(unit.id, mesh); } diff --git a/src/systems/MovementSystem.js b/src/systems/MovementSystem.js index 6db60a3..e28897e 100644 --- a/src/systems/MovementSystem.js +++ b/src/systems/MovementSystem.js @@ -106,11 +106,12 @@ export class MovementSystem { if (walkableY === null) continue; const pos = { x, y: walkableY, z }; - const posKey = `${x},${y},${z}`; + // Use walkableY in the key, not the reference y + const posKey = `${x},${walkableY},${z}`; // Check if position is not occupied (or is the starting position) // Starting position is always reachable (unit is already there) - const isStartPos = x === start.x && z === start.z && y === start.y; + const isStartPos = x === start.x && z === start.z && walkableY === start.y; if (!this.grid.isOccupied(pos) || isStartPos) { reachable.add(posKey); }