fix(babylon_setup.js): fix exit not generating properly

Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
pobadoba
2026-05-06 01:47:27 +09:00
parent 4bb46115ca
commit 176ad34214

View File

@@ -126,6 +126,7 @@ let chestMap = new Map(); // key: "x,y" -> {mesh, opened}
let keyChestKey = null;
let exitBox = null;
let exitGridPos = null; // track exit grid position for collision checking
let spawnGridPos = null; // track spawn grid position for validation
let spawnMarker = null;
const cellSize = 2;
@@ -139,6 +140,7 @@ function clearLevelMeshes() {
if (exitBox) { try { exitBox.dispose(); } catch(e){}; exitBox = null; }
if (spawnMarker) { try { spawnMarker.dispose(); } catch(e){}; spawnMarker = null; }
exitGridPos = null;
spawnGridPos = null;
}
// Simple seeded RNG
@@ -209,6 +211,26 @@ function findDeadEnds(grid) {
return dead;
}
function gridCellToWorld(grid, x, y) {
const halfW = (grid[0].length * cellSize) / 2;
const halfH = (grid.length * cellSize) / 2;
return new BABYLON.Vector3(
x * cellSize - halfW + cellSize / 2,
0,
y * cellSize - halfH + cellSize / 2,
);
}
function isWalkableCell(grid, x, y) {
return y >= 0 && y < grid.length && x >= 0 && x < grid[0].length && grid[y][x] === 0;
}
function isReservedCell(x, y) {
if (chestMap.has(`${x},${y}`)) return true;
if (exitGridPos && exitGridPos.x === x && exitGridPos.y === y) return true;
return false;
}
function buildLevelFromGrid(grid) {
clearLevelMeshes();
const h = grid.length;
@@ -295,71 +317,96 @@ function placeExit(grid, seed) {
exitGridPos = { x, y };
}
const [x,y] = exitGridPos;
const halfW = (grid[0].length * cellSize) / 2;
const halfH = (grid.length * cellSize) / 2;
const ex = x*cellSize - halfW + cellSize/2;
const ez = y*cellSize - halfH + cellSize/2;
const [x,y] = [exitGridPos.x, exitGridPos.y];
if (!isWalkableCell(grid, x, y)) {
console.warn("Exit selected on non-walkable cell", { x, y });
return;
}
const exitWorld = gridCellToWorld(grid, x, y);
const ex = exitWorld.x;
const ez = exitWorld.z;
const worldSpan = Math.max(grid[0].length, grid.length) * cellSize;
// Ground plane indicator
const plane = BABYLON.MeshBuilder.CreateGround('exitZone', { width: cellSize*0.9, height: cellSize*0.9 }, scene);
plane.material = new BABYLON.StandardMaterial('exitMat', scene);
plane.material.emissiveColor = new BABYLON.Color3(0.9, 0.8, 0.2);
plane.position = new BABYLON.Vector3(ex, 0.01, ez);
// Ground plane indicator (larger + slightly raised to avoid z-fighting)
const plane = BABYLON.MeshBuilder.CreateGround('exitZone', { width: cellSize*1.4, height: cellSize*1.4 }, scene);
const exitMat = new BABYLON.StandardMaterial('exitMat', scene);
exitMat.diffuseColor = new BABYLON.Color3(0.25, 0.2, 0.03);
exitMat.emissiveColor = new BABYLON.Color3(1.0, 0.85, 0.15);
exitMat.disableLighting = true;
plane.material = exitMat;
plane.position = new BABYLON.Vector3(ex, 0.08, ez);
exitBox = plane;
levelMeshes.push(plane);
// Tall pillar for visibility
const pillar = BABYLON.MeshBuilder.CreateCylinder('exitPillar', { diameter: cellSize*0.3, height: cellSize*3.0 }, scene);
// Very tall beacon for visibility over maze walls
const beaconHeight = Math.max(18, worldSpan * 0.8);
const pillar = BABYLON.MeshBuilder.CreateCylinder('exitPillar', { diameter: cellSize*0.55, height: beaconHeight }, scene);
const pillarMat = new BABYLON.StandardMaterial('exitPillarMat', scene);
pillarMat.diffuseColor = new BABYLON.Color3(0.95, 0.8, 0.1);
pillarMat.emissiveColor = new BABYLON.Color3(0.3, 0.25, 0.05);
pillarMat.emissiveColor = new BABYLON.Color3(1.0, 0.7, 0.1);
pillarMat.disableLighting = true;
pillar.material = pillarMat;
pillar.position = new BABYLON.Vector3(ex, cellSize*0.75, ez);
pillar.position = new BABYLON.Vector3(ex, beaconHeight * 0.5, ez);
levelMeshes.push(pillar);
// Bright orb at the top of beacon
const orb = BABYLON.MeshBuilder.CreateSphere('exitOrb', { diameter: cellSize * 1.0 }, scene);
const orbMat = new BABYLON.StandardMaterial('exitOrbMat', scene);
orbMat.diffuseColor = new BABYLON.Color3(1.0, 0.9, 0.3);
orbMat.emissiveColor = new BABYLON.Color3(1.0, 0.9, 0.35);
orbMat.disableLighting = true;
orb.material = orbMat;
orb.position = new BABYLON.Vector3(ex, beaconHeight + cellSize * 0.7, ez);
levelMeshes.push(orb);
}
function spawnCameraAt(grid) {
const h = grid.length, w = grid[0].length;
for (let y = 1; y < h-1; y++) {
for (let x = 1; x < w-1; x++) {
// Skip walls
if (grid[y][x] !== 0) continue;
// Skip chests
if (chestMap.has(`${x},${y}`)) continue;
// Skip exit
if (exitGridPos && exitGridPos.x === x && exitGridPos.y === y) continue;
// Found valid spawn point
const halfW = (w * cellSize) / 2;
const halfH = (h * cellSize) / 2;
const px = x*cellSize - halfW + cellSize/2;
const pz = y*cellSize - halfH + cellSize/2;
try {
if (camera && camera.position) {
camera.position = new BABYLON.Vector3(px, 1.6, pz);
}
} catch (e) {}
// Add spawn marker sphere
if (spawnMarker) {
try { spawnMarker.dispose(); } catch(e) {}
const h = grid.length;
const w = grid[0].length;
let bestCell = null;
let bestDist = -1;
// Choose a valid spawn that is far from exit when possible.
for (let y = 1; y < h - 1; y++) {
for (let x = 1; x < w - 1; x++) {
if (!isWalkableCell(grid, x, y)) continue;
if (isReservedCell(x, y)) continue;
const d = exitGridPos ? Math.hypot(x - exitGridPos.x, y - exitGridPos.y) : 0;
if (d > bestDist) {
bestDist = d;
bestCell = { x, y };
}
const marker = BABYLON.MeshBuilder.CreateSphere('spawnMarker', { diameter: cellSize*0.4 }, scene);
const markerMat = new BABYLON.StandardMaterial('spawnMarkerMat', scene);
markerMat.diffuseColor = new BABYLON.Color3(0.2, 0.6, 0.95);
markerMat.emissiveColor = new BABYLON.Color3(0.1, 0.3, 0.5);
marker.material = markerMat;
marker.position = new BABYLON.Vector3(px, cellSize*0.2, pz);
spawnMarker = marker;
levelMeshes.push(marker);
return;
}
}
if (!bestCell) {
console.warn("No valid spawn cell found.");
return;
}
spawnGridPos = bestCell;
const spawnWorld = gridCellToWorld(grid, bestCell.x, bestCell.y);
const px = spawnWorld.x;
const pz = spawnWorld.z;
try {
if (camera && camera.position) {
camera.position = new BABYLON.Vector3(px, 1.6, pz);
}
} catch (e) {}
if (spawnMarker) {
try { spawnMarker.dispose(); } catch(e) {}
}
const marker = BABYLON.MeshBuilder.CreateSphere('spawnMarker', { diameter: cellSize*0.4 }, scene);
const markerMat = new BABYLON.StandardMaterial('spawnMarkerMat', scene);
markerMat.diffuseColor = new BABYLON.Color3(0.2, 0.6, 0.95);
markerMat.emissiveColor = new BABYLON.Color3(0.1, 0.3, 0.5);
marker.material = markerMat;
marker.position = new BABYLON.Vector3(px, cellSize*0.2, pz);
spawnMarker = marker;
levelMeshes.push(marker);
}
function generateLevel() {
@@ -374,7 +421,20 @@ function generateLevel() {
placeChestsOnDeadEnds(grid, dead, cfg.minChestDeadEnds, seed + cfg.level);
placeExit(grid, seed + cfg.level);
spawnCameraAt(grid);
sharedState.runtime.message = `Level ${cfg.level} generated.`;
const placementValid =
!!exitGridPos &&
!!spawnGridPos &&
isWalkableCell(grid, exitGridPos.x, exitGridPos.y) &&
isWalkableCell(grid, spawnGridPos.x, spawnGridPos.y) &&
!(exitGridPos.x === spawnGridPos.x && exitGridPos.y === spawnGridPos.y);
if (!placementValid) {
sharedState.runtime.message = `Placement warning: spawn/exit invalid on level ${cfg.level}.`;
console.warn("Invalid spawn/exit placement", { exitGridPos, spawnGridPos });
} else {
sharedState.runtime.message = `Level ${cfg.level} generated (spawn ${spawnGridPos.x},${spawnGridPos.y} / exit ${exitGridPos.x},${exitGridPos.y}).`;
}
window.requestAnimationFrame(()=>{ /* let scene update */ });
}