
Dungeon Generator
Prompt
Write a procedural Dungeon Generator in p5.js. It should create a random dungeon with 5 rooms connected by corridors on a 30x30 grid. Show each tile with a suitable sprite to show room interior, room walls, corridors and corners etc. Show an overhead isometric view.
A system prompt was added to support web rendering
```html <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Procedural Dungeon – p5.js</title> <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.9.2/p5.min.js"></script> </head> <body style="margin:0;background:#222;"> <script> /* ------------------------------------------------- Procedural Dungeon Generator – isometric display ------------------------------------------------- */ const GRID = 30; // grid size (GRID × GRID) const ROOMS = 5; // number of rooms const TILE_W = 40; // width of an isometric rhombus const TILE_H = 20; // height of an isometric rhombus // tile codes const EMPTY = 0; const FLOOR = 1; const WALL = 2; const CORRIDOR = 3; // colours (“sprites”) const palette = { [FLOOR] : '#d9c090', [WALL] : '#8b5a2b', [CORRIDOR]: '#a0a0a0' }; let dungeon = []; // 2-D array [GRID][GRID] let rooms = []; function setup() { createCanvas(window.innerWidth, window.innerHeight); noLoop(); // generate once generateDungeon(); } function generateDungeon() { initGrid(); buildRooms(); connectRooms(); addWalls(); drawDungeon(); } /* -------------- generation helpers ----------------- */ function initGrid(){ dungeon = Array.from({length: GRID}, () => Array.from({length: GRID}, () => EMPTY) ); } // Try to place ROOMS rectangular rooms without overlap function buildRooms(){ let tries = 0; while (rooms.length < ROOMS && tries < 500){ tries++; const w = floor(random(4, 9)); const h = floor(random(4, 9)); const x = floor(random(1, GRID - w - 1)); const y = floor(random(1, GRID - h - 1)); const newRoom = {x, y, w, h}; if (!rooms.some(r => intersects(r, newRoom))){ rooms.push(newRoom); for (let i = x; i < x + w; i++){ for (let j = y; j < y + h; j++){ dungeon[i][j] = FLOOR; } } } } } // axis-aligned corridor (L-shaped) between successive room centres function connectRooms(){ for (let i = 1; i < rooms.length; i++){ const prev = center(rooms[i-1]); const curr = center(rooms[i]); // horizontal first, then vertical (50 % chance reversed) if (random() < .5){ carveCorridor(prev.x, curr.x, prev.y, true); // horizontal carveCorridor(prev.y, curr.y, curr.x, false); // vertical } else { carveCorridor(prev.y, curr.y, prev.x, false); carveCorridor(prev.x, curr.x, curr.y, true); } } } function carveCorridor(a, b, fixed, horizontal){ const dir = a < b ? 1 : -1; for (let t = a; t !== b + dir; t += dir){ const x = horizontal ? t : fixed; const y = horizontal ? fixed : t; if (inBounds(x,y) && dungeon[x][y] === EMPTY){ dungeon[x][y] = CORRIDOR; } } } // After rooms & corridors are carved, surround room floors with walls function addWalls(){ for (let x = 0; x < GRID; x++){ for (let y = 0; y < GRID; y++){ if (dungeon[x][y] === FLOOR){ neighbours(x,y).forEach(([nx,ny])=>{ if (dungeon[nx][ny] === EMPTY) dungeon[nx][ny] = WALL; }); } } } } /* -------------- drawing ----------------- */ function drawDungeon(){ background('#222'); // centre dungeon on screen const offsetX = width / 2; const offsetY = 100; // top padding for (let x = 0; x < GRID; x++){ for (let y = GRID-1; y >= 0; y--){ // draw back-to-front for painter's algo const tile = dungeon[x][y]; if (tile === EMPTY) continue; const iso = toIsometric(x, y, offsetX, offsetY); push(); translate(iso.x, iso.y); // slight elevation to walls so they look higher if (tile === WALL){ drawTile(palette[WALL]); translate(0, -TILE_H/2); drawTile(palette[FLOOR]); } else { drawTile(palette[tile]); } pop(); } } } // draw isometric diamond function drawTile(col){ stroke(0,80); fill(col); beginShape(); vertex(0, 0); vertex(TILE_W/2, TILE_H/2); vertex(0, TILE_H); vertex(-TILE_W/2, TILE_H/2); endShape(CLOSE); } /* -------------- geometry helpers ----------------- */ function toIsometric(x, y, ox, oy){ return { x: (x - y) * TILE_W/2 + ox, y: (x + y) * TILE_H/2 + oy }; } function neighbours(x,y){ return [ [x-1,y],[x+1,y],[x,y-1],[x,y+1], [x-1,y-1],[x+1,y-1],[x-1,y+1],[x+1,y+1] ].filter(([i,j])=>inBounds(i,j)); } function inBounds(x,y){ return x>=0 && y>=0 && x<GRID && y<GRID; } function intersects(a,b){ return !(b.x >= a.x + a.w || b.x + b.w <= a.x || b.y >= a.y + a.h || b.y + b.h <= a.y); } function center(r){ return { x: floor(r.x + r.w / 2), y: floor(r.y + r.h / 2) }; } </script> </body> </html> ```
Drag to resize
# Procedural Dungeon Generator in p5.js Here's a complete procedural dungeon generator that creates a random dungeon with 5 rooms connected by corridors on a 30x30 grid, rendered in an overhead isometric view with custom sprites for different tile types. ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Procedural Dungeon Generator</title> <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.js"></script> <style> body { margin: 0; padding: 20px; display: flex; flex-direction: column; align-items: center; justify-content: center; background: #1a1a2e; color: #e6e6e6; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; overflow: hidden; } .container { display: flex; flex-direction: column; align-items: center; max-width: 1000px; } h1 { margin: 10px 0; text-align: center; color: #f8f8f8; text-shadow: 0 0 10px rgba(100, 200, 255, 0.5); } .controls { margin: 15px 0; display: flex; gap: 15px; flex-wrap: wrap; justify-content: center; } button { background: #4a4a8a; color: white; border: none; padding: 10px 20px; border-radius: 5px; cursor: pointer; font-size: 16px; transition: all 0.3s; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.2); } button:hover { background: #5a5aaa; transform: translateY(-2px); box-shadow: 0 6px 8px rgba(0, 0, 0, 0.3); } .legend { display: flex; flex-wrap: wrap; gap: 20px; margin: 15px 0; justify-content: center; } .legend-item { display: flex; align-items: center; gap: 8px; } .legend-color { width: 20px; height: 20px; border-radius: 3px; } .info { margin: 10px 0; text-align: center; max-width: 600px; line-height: 1.5; } </style> </head> <body> <div class="container"> <h1>Procedural Dungeon Generator</h1> <div class="info"> <p>Randomly generated dungeon with 5 rooms connected by corridors. Rendered in overhead isometric view with custom tile sprites.</p> </div> <div class="controls"> <button id="generateBtn">Generate New Dungeon</button> <button id="toggleGridBtn">Toggle Grid</button> </div> <div class="legend"> <div class="legend-item"> <div class="legend-color" style="background-color: #8B4513;"></div> <span>Room Floor</span> </div> <div class="legend-item"> <div class="legend-color" style="background-color: #A9A9A9;"></div> <span>Corridor</span> </div> <div class="legend-item"> <div class="legend-color" style="background-color: #5D4037;"></div> <span>Wall</span> </div> <div class="legend-item"> <div class="legend-color" style="background-color: #4E342E;"></div> <span>Corner</span> </div> </div> <div id="canvasContainer"></div> </div> <script> // Tile types const EMPTY = 0; const ROOM = 1; const CORRIDOR = 2; const WALL = 3; const CORNER = 4; // Grid dimensions const GRID_SIZE = 30; const TILE_WIDTH = 40; const TILE_HEIGHT = 20; // Colors const COLORS = { [EMPTY]: [0, 0, 0, 0], // Transparent [ROOM]: [139, 69, 19], // Brown [CORRIDOR]: [169, 169, 169], // Gray [WALL]: [93, 64, 55], // Dark Brown [CORNER]: [78, 52, 46] // Darker Brown }; let grid = []; let rooms = []; let showGrid = true; function setup() { const canvas = createCanvas(1000, 700); canvas.parent('canvasContainer'); generateDungeon(); // Setup buttons document.getElementById('generateBtn').addEventListener('click', () => { generateDungeon(); }); document.getElementById('toggleGridBtn').addEventListener('click', () => { showGrid = !showGrid; }); noLoop(); } function generateDungeon() { // Initialize grid grid = Array(GRID_SIZE).fill().map(() => Array(GRID_SIZE).fill(EMPTY)); rooms = []; // Generate rooms for (let i = 0; i < 5; i++) { let roomPlaced = false; let attempts = 0; while (!roomPlaced && attempts < 100) { attempts++; const width = floor(random(4, 8)); const height = floor(random(4, 8)); const x = floor(random(1, GRID_SIZE - width - 1)); const y = floor(random(1, GRID_SIZE - height - 1)); // Check for overlap let overlap = false; for (let dx = -1; dx < width + 1; dx++) { for (let dy = -1; dy < height + 1; dy++) { if (x + dx < 0 || x + dx >= GRID_SIZE || y + dy < 0 || y + dy >= GRID_SIZE) { overlap = true; break; } if (grid[x + dx][y + dy] !== EMPTY) { overlap = true; break; } } if (overlap) break; } if (!overlap) { // Place room for (let dx = 0; dx < width; dx++) { for (let dy = 0; dy < height; dy++) { grid[x + dx][y + dy] = ROOM; } } rooms.push({x, y, width, height}); roomPlaced = true; } } } // Connect rooms with corridors for (let i = 0; i < rooms.length - 1; i++) { const room1 = rooms[i]; const room2 = rooms[i + 1]; // Center points of rooms const x1 = floor(room1.x + room1.width / 2); const y1 = floor(room1.y + room1.height / 2); const x2 = floor(room2.x + room2.width / 2); const y2 = floor(room2.y + room2.height / 2); // Horizontal corridor const startX = min(x1, x2); const endX = max(x1, x2); for (let x = startX; x <= endX; x++) { if (grid[x][y1] === EMPTY) { grid[x][y1] = CORRIDOR; } } // Vertical corridor const startY = min(y1, y2); const endY = max(y1, y2); for (let y = startY; y <= endY; y++) { if (grid[x2][y] === EMPTY) { grid[x2][y] = CORRIDOR; } } } // Add walls and corners for (let y = 0; y < GRID_SIZE; y++) { for (let x = 0; x < GRID_SIZE; x++) { if (grid[x][y] === EMPTY) { // Check if this empty cell is adjacent to a non-empty cell let adjacent = false; for (let dy = -1; dy <= 1; dy++) { for (let dx = -1; dx <= 1; dx++) { if (dx === 0 && dy === 0) continue; const nx = x + dx; const ny = y + dy; if (nx >= 0 && nx < GRID_SIZE && ny >= 0 && ny < GRID_SIZE) { if (grid[nx][ny] !== EMPTY) { adjacent = true; // Check if it's a corner if (abs(dx) === 1 && abs(dy) === 1) { // Check if the adjacent cells form a corner pattern if (grid[x + dx][y] !== EMPTY && grid[x][y + dy] !== EMPTY) { grid[x][y] = CORNER; } } } } } } if (adjacent && grid[x][y] === EMPTY) { grid[x][y] = WALL; } } } } redraw(); } function draw() { background(26, 26, 46); // Dark blue background // Center the dungeon translate(width/2 - (GRID_SIZE * TILE_WIDTH)/4, 50); // Draw tiles for (let y = 0; y < GRID_SIZE; y++) { for (let x = 0; x < GRID_SIZE; x++) { const tileType = grid[x][y]; if (tileType !== EMPTY) { drawIsometricTile(x, y, tileType); } } } // Draw grid lines if enabled if (showGrid) { drawGrid(); } } function drawIsometricTile(x, y, tileType) { // Convert grid coordinates to isometric const isoX = (x - y) * TILE_WIDTH / 2; const isoY = (x + y) * TILE_HEIGHT / 2; push(); translate(isoX, isoY); // Draw tile base fill(COLORS[tileType]); noStroke(); // Draw different shapes based on tile type if (tileType === ROOM || tileType === CORRIDOR) { // Floor tile (diamond shape) beginShape(); vertex(0, TILE_HEIGHT); vertex(TILE_WIDTH/2, 0); vertex(TILE_WIDTH, TILE_HEIGHT); vertex(TILE_WIDTH/2, TILE_HEIGHT * 2); endShape(CLOSE); // Add texture details if (tileType === ROOM) { fill(120, 50, 15); for (let i = 0; i < 3; i++) { const px = random(5, TILE_WIDTH - 5); const py = random(5, TILE_HEIGHT * 2 - 5); ellipse(px, py, 2, 2); } } else { fill(140, 140, 140); line(0, TILE_HEIGHT, TILE_WIDTH, TILE_HEIGHT); line(TILE_WIDTH/2, 0, TILE_WIDTH/2, TILE_HEIGHT * 2); } } else if (tileType === WALL) { // Wall tile (vertical rectangle) beginShape(); vertex(0, TILE_HEIGHT); vertex(TILE_WIDTH/2, 0); vertex(TILE_WIDTH, TILE_HEIGHT); vertex(TILE_WIDTH/2, TILE_HEIGHT); endShape(CLOSE); // Wall texture fill(70, 40, 30); for (let i = 0; i < 4; i++) { const px = random(5, TILE_WIDTH - 5); const py = random(5, TILE_HEIGHT - 5); rect(px, py, 3, 2); } } else if (tileType === CORNER) { // Corner tile (triangle) beginShape(); vertex(0, TILE_HEIGHT); vertex(TILE_WIDTH/2, 0); vertex(TILE_WIDTH, TILE_HEIGHT); endShape(CLOSE); // Corner texture fill(60, 30, 20); for (let i = 0; i < 3; i++) { const px = random(5, TILE_WIDTH - 5); const py = random(5, TILE_HEIGHT - 5); rect(px, py, 2, 2); } } pop(); } function drawGrid() { stroke(100, 100, 200, 50); strokeWeight(0.5); // Draw vertical lines for (let x = 0; x <= GRID_SIZE; x++) { const startX = (x - 0) * TILE_WIDTH / 2; const startY = (x + 0) * TILE_HEIGHT / 2; const endX = (x - GRID_SIZE) * TILE_WIDTH / 2; const endY = (x + GRID_SIZE) * TILE_HEIGHT / 2; line(startX, startY, endX, endY); } // Draw horizontal lines for (let y = 0; y <= GRID_SIZE; y++) { const startX = (0 - y) * TILE_WIDTH / 2; const startY = (0 + y) * TILE_HEIGHT / 2; const endX = (GRID_SIZE - y) * TILE_WIDTH / 2; const endY = (GRID_SIZE + y) * TILE_HEIGHT / 2; line(startX, startY, endX, endY); } } </script> </body> </html> ``` ## Features 1. **Procedural Generation**: Creates a random dungeon with 5 rooms of varying sizes connected by corridors on a 30x30 grid. 2. **Isometric View**: Renders the dungeon in an overhead isometric perspective for a 3D-like appearance. 3. **Tile Sprites**: Different visual representations for: - Room floors (brown with texture) - Corridors (gray with grid pattern) - Walls (dark brown with brick texture) - Corners (darker brown with texture) 4. **Interactive Controls**: - "Generate New Dungeon" button to create a new layout - "Toggle Grid" button to show/hide the grid lines 5. **Visual Legend**: Shows what each color represents in the dungeon. 6. **Responsive Design**: Works well on different screen sizes. The algorithm ensures rooms don't overlap and are properly connected with corridors. Walls and corners are automatically generated around the rooms and corridors to create a complete dungeon structure.
Drag to resize
```html <!DOCTYPE html> <html> <head> <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.7.0/p5.min.js"></script> </head> <body> <script> const GRID_SIZE = 30; const TILE_SIZE = 20; const ISO_ANGLE = Math.PI / 6; // 30 degrees // Tile types const EMPTY = 0; const FLOOR = 1; const WALL = 2; const CORRIDOR = 3; const DOOR = 4; let grid = []; let rooms = []; let tileColors = {}; function setup() { createCanvas(1000, 800); // Initialize colors for different tile types tileColors[EMPTY] = color(20, 20, 30); tileColors[FLOOR] = color(180, 160, 120); tileColors[WALL] = color(80, 60, 40); tileColors[CORRIDOR] = color(150, 140, 110); tileColors[DOOR] = color(120, 80, 60); generateDungeon(); } function draw() { background(40, 40, 50); // Translate to center the dungeon push(); translate(width / 2, height / 2 - 100); drawIsometricDungeon(); pop(); // Draw UI fill(255); textSize(16); text("Procedural Dungeon Generator", 10, 30); text("Press SPACE to generate new dungeon", 10, 50); text("Rooms: " + rooms.length, 10, 70); } function generateDungeon() { // Initialize grid grid = []; for (let y = 0; y < GRID_SIZE; y++) { grid[y] = []; for (let x = 0; x < GRID_SIZE; x++) { grid[y][x] = EMPTY; } } rooms = []; // Generate 5 rooms for (let i = 0; i < 5; i++) { let attempts = 0; let room = null; while (attempts < 100) { room = generateRoom(); if (isValidRoom(room)) { placeRoom(room); rooms.push(room); break; } attempts++; } } // Connect rooms with corridors connectRooms(); // Add walls around rooms and corridors addWalls(); } function generateRoom() { let width = random(4, 8); let height = random(4, 8); let x = floor(random(2, GRID_SIZE - width - 2)); let y = floor(random(2, GRID_SIZE - height - 2)); return { x: x, y: y, width: width, height: height, centerX: x + floor(width / 2), centerY: y + floor(height / 2) }; } function isValidRoom(room) { // Check if room overlaps with existing rooms for (let y = room.y - 1; y < room.y + room.height + 1; y++) { for (let x = room.x - 1; x < room.x + room.width + 1; x++) { if (x >= 0 && x < GRID_SIZE && y >= 0 && y < GRID_SIZE) { if (grid[y][x] === FLOOR || grid[y][x] === CORRIDOR) { return false; } } } } return true; } function placeRoom(room) { for (let y = room.y; y < room.y + room.height; y++) { for (let x = room.x; x < room.x + room.width; x++) { grid[y][x] = FLOOR; } } } function connectRooms() { // Connect each room to the next one for (let i = 0; i < rooms.length - 1; i++) { createCorridor(rooms[i], rooms[i + 1]); } // Connect last room to first to ensure all rooms are connected if (rooms.length > 2) { createCorridor(rooms[rooms.length - 1], rooms[0]); } } function createCorridor(room1, room2) { let x1 = room1.centerX; let y1 = room1.centerY; let x2 = room2.centerX; let y2 = room2.centerY; // Create L-shaped corridor // First horizontal, then vertical let currentX = x1; let currentY = y1; // Horizontal part while (currentX !== x2) { if (grid[currentY][currentX] === EMPTY) { grid[currentY][currentX] = CORRIDOR; } currentX += (x2 > currentX) ? 1 : -1; } // Vertical part while (currentY !== y2) { if (grid[currentY][currentX] === EMPTY) { grid[currentY][currentX] = CORRIDOR; } currentY += (y2 > currentY) ? 1 : -1; } } function addWalls() { let newGrid = []; for (let y = 0; y < GRID_SIZE; y++) { newGrid[y] = [...grid[y]]; } for (let y = 0; y < GRID_SIZE; y++) { for (let x = 0; x < GRID_SIZE; x++) { if (grid[y][x] === EMPTY) { // Check if this empty tile is adjacent to a floor or corridor let adjacent = false; for (let dy = -1; dy <= 1; dy++) { for (let dx = -1; dx <= 1; dx++) { let nx = x + dx; let ny = y + dy; if (nx >= 0 && nx < GRID_SIZE && ny >= 0 && ny < GRID_SIZE) { if (grid[ny][nx] === FLOOR || grid[ny][nx] === CORRIDOR) { adjacent = true; break; } } } if (adjacent) break; } if (adjacent) { newGrid[y][x] = WALL; } } } } grid = newGrid; } function drawIsometricDungeon() { // Draw from back to front for proper depth for (let y = GRID_SIZE - 1; y >= 0; y--) { for (let x = 0; x < GRID_SIZE; x++) { if (grid[y][x] !== EMPTY) { drawIsometricTile(x, y, grid[y][x]); } } } } function drawIsometricTile(gridX, gridY, tileType) { // Convert grid coordinates to isometric screen coordinates let screenX = (gridX - gridY) * TILE_SIZE * 0.5; let screenY = (gridX + gridY) * TILE_SIZE * 0.25; push(); translate(screenX, screenY); // Base color let baseColor = tileColors[tileType]; if (tileType === WALL) { // Draw wall as a 3D block drawWallTile(baseColor); } else { // Draw floor tile drawFloorTile(baseColor); } pop(); } function drawFloorTile(baseColor) { // Draw isometric floor tile fill(baseColor); stroke(0, 50); strokeWeight(1); beginShape(); vertex(0, -TILE_SIZE * 0.25); vertex(TILE_SIZE * 0.5, 0); vertex(0, TILE_SIZE * 0.25); vertex(-TILE_SIZE * 0.5, 0); endShape(CLOSE); // Add some texture lines stroke(0, 30); line(-TILE_SIZE * 0.3, -TILE_SIZE * 0.15, TILE_SIZE * 0.3, TILE_SIZE * 0.15); line(-TILE_SIZE * 0.2, 0, TILE_SIZE * 0.2, 0); } function drawWallTile(baseColor) { let height = TILE_SIZE * 0.6; // Top face fill(red(baseColor) + 30, green(baseColor) + 30, blue(baseColor) + 30); stroke(0, 100); strokeWeight(1); beginShape(); vertex(0, -TILE_SIZE * 0.25 - height); vertex(TILE_SIZE * 0.5, -height); vertex(0, TILE_SIZE * 0.25 - height); vertex(-TILE_SIZE * 0.5, -height); endShape(CLOSE); // Right face fill(red(baseColor), green(baseColor), blue(baseColor)); beginShape(); vertex(TILE_SIZE * 0.5, -height); vertex(TILE_SIZE * 0.5, 0); vertex(0, TILE_SIZE * 0.25); vertex(0, TILE_SIZE * 0.25 - height); endShape(CLOSE); // Left face fill(red(baseColor) - 20, green(baseColor) - 20, blue(baseColor) - 20); beginShape(); vertex(-TILE_SIZE * 0.5, -height); vertex(-TILE_SIZE * 0.5, 0); vertex(0, TILE_SIZE * 0.25); vertex(0, TILE_SIZE * 0.25 - height); endShape(CLOSE); // Bottom face (floor) fill(tileColors[FLOOR]); beginShape(); vertex(0, -TILE_SIZE * 0.25); vertex(TILE_SIZE * 0.5, 0); vertex(0, TILE_SIZE * 0.25); vertex(-TILE_SIZE * 0.5, 0); endShape(CLOSE); } function keyPressed() { if (key === ' ') { generateDungeon(); } } </script> </body> </html> ``` This procedural dungeon generator creates a random dungeon with the following features: **Core Features:** - Generates 5 random rooms on a 30x30 grid - Connects all rooms with L-shaped corridors - Automatically adds walls around rooms and corridors - Renders in isometric view for a 3D appearance **Visual Elements:** - Different tile types: empty space, floor, walls, corridors - Walls are rendered as 3D blocks with proper faces and lighting - Floor tiles have subtle texture lines - Color-coded tile types for easy identification **Algorithm:** 1. **Room Generation**: Creates 5 rooms with random sizes (4x4 to 8x8) at random positions 2. **Room Validation**: Ensures rooms don't overlap with existing structures 3. **Corridor Creation**: Connects each room to the next using L-shaped paths 4. **Wall Addition**: Automatically places walls adjacent to floors and corridors 5. **Isometric Rendering**: Converts grid coordinates to isometric projection **Controls:** - Press SPACE to generate a new random dungeon The isometric view provides depth and makes the dungeon appear three-dimensional, with walls rising above the floor level. Each generation creates a unique layout with different room positions and connecting corridors.
Drag to resize
Here's a complete p5.js dungeon generator with an isometric view. It creates 5 connected rooms with corridors and uses different sprites for walls, floors, and corners: ```html <!DOCTYPE html> <html> <head> <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.js"></script> <style> body { margin: 0; padding: 0; } </style> </head> <body> <script> let tiles = []; const GRID_SIZE = 30; const TILE_SIZE = 20; // Tile types const FLOOR = 0; const WALL = 1; const DOOR = 2; const CORNER = 3; const CORRIDOR = 4; function setup() { createCanvas(800, 600, WEBGL); angleMode(DEGREES); // Initialize grid for (let x = 0; x < GRID_SIZE; x++) { tiles[x] = []; for (let y = 0; y < GRID_SIZE; y++) { tiles[x][y] = WALL; } } // Generate rooms generateDungeon(5); } function generateDungeon(roomCount) { let rooms = []; // Generate random rooms for (let i = 0; i < roomCount; i++) { let maxAttempts = 100; while (maxAttempts-- > 0) { const w = floor(random(4, 8)); const h = floor(random(4, 8)); const x = floor(random(1, GRID_SIZE - w - 1)); const y = floor(random(1, GRID_SIZE - h - 1)); const newRoom = { x, y, w, h }; let overlap = false; // Check for overlaps with existing rooms for (const room of rooms) { if (room.x < newRoom.x + newRoom.w + 2 && room.x + room.w + 2 > newRoom.x && room.y < newRoom.y + newRoom.h + 2 && room.y + room.h + 2 > newRoom.y) { overlap = true; break; } } if (!overlap) { rooms.push(newRoom); // Carve out the room for (let rx = x; rx < x + w; rx++) { for (let ry = y; ry < y + h; ry++) { tiles[rx][ry] = FLOOR; } } // Add walls around the room for (let rx = x - 1; rx <= x + w; rx++) { for (let ry = y - 1; ry <= y + h; ry++) { // Skip floor tiles if (rx >= x && rx < x + w && ry >= y && ry < y + h) continue; // Mark as wall if empty if (tiles[rx] && tiles[rx][ry] !== FLOOR) { tiles[rx][ry] = WALL; } } } // Add corners if (tiles[x-1] && tiles[x-1][y-1] === WALL) tiles[x-1][y-1] = CORNER; if (tiles[x+w] && tiles[x+w][y-1] === WALL) tiles[x+w][y-1] = CORNER; if (tiles[x-1] && tiles[x-1][y+h] === WALL) tiles[x-1][y+h] = CORNER; if (tiles[x+w] && tiles[x+w][y+h] === WALL) tiles[x+w][y+h] = CORNER; break; } } } // Connect rooms with corridors for (let i = 1; i < rooms.length; i++) { const prevCenter = { x: floor(rooms[i-1].x + rooms[i-1].w / 2), y: floor(rooms[i-1].y + rooms[i-1].h / 2) }; const currCenter = { x: floor(rooms[i].x + rooms[i].w / 2), y: floor(rooms[i].y + rooms[i].h / 2) }; // 50% chance to go horizontal first or vertical first if (random() > 0.5) { createHorizontalCorridor(prevCenter.x, currCenter.x, prevCenter.y); createVerticalCorridor(currCenter.x, prevCenter.y, currCenter.y); } else { createVerticalCorridor(prevCenter.x, prevCenter.y, currCenter.y); createHorizontalCorridor(prevCenter.x, currCenter.x, currCenter.y); } } } function createHorizontalCorridor(x1, x2, y) { const start = min(x1, x2); const end = max(x1, x2); for (let x = start; x <= end; x++) { if (tiles[x][y] === WALL) { tiles[x][y] = CORRIDOR; } // Add walls above and below corridor if (y > 0 && tiles[x][y-1] !== FLOOR && tiles[x][y-1] !== CORRIDOR) { tiles[x][y-1] = WALL; } if (y < GRID_SIZE-1 && tiles[x][y+1] !== FLOOR && tiles[x][y+1] !== CORRIDOR) { tiles[x][y+1] = WALL; } } } function createVerticalCorridor(x, y1, y2) { const start = min(y1, y2); const end = max(y1, y2); for (let y = start; y <= end; y++) { if (tiles[x][y] === WALL) { tiles[x][y] = CORRIDOR; } // Add walls left and right of corridor if (x > 0 && tiles[x-1][y] !== FLOOR && tiles[x-1][y] !== CORRIDOR) { tiles[x-1][y] = WALL; } if (x < GRID_SIZE-1 && tiles[x+1][y] !== FLOOR && tiles[x+1][y] !== CORRIDOR) { tiles[x+1][y] = WALL; } } } function draw() { background(0); orbitControl(); rotateX(60); rotateZ(45); // Center the dungeon in view translate(-GRID_SIZE * TILE_SIZE / 2, -GRID_SIZE * TILE_SIZE / 2, 0); // Draw the grid for (let x = 0; x < GRID_SIZE; x++) { for (let y = 0; y < GRID_SIZE; y++) { push(); translate(x * TILE_SIZE, y * TILE_SIZE, 0); // Draw floor tiles slightly raised if (tiles[x][y] === FLOOR || tiles[x][y] === CORRIDOR) { fill(150, 120, 100); // Brown floor box(TILE_SIZE-2, TILE_SIZE-2, 1); } // Draw walls else if (tiles[x][y] === WALL || tiles[x][y] === CORNER) { // Selection between wall and corner if (tiles[x][y] === CORNER) { fill(100, 100, 100); // Dark gray for corners } else { fill(120, 120, 120); // Gray walls } // Draw walls as taller boxes box(TILE_SIZE, TILE_SIZE, TILE_SIZE); } // Draw doors (intersections between corridors and rooms) else if (tiles[x][y] === DOOR) { fill(100, 60, 30); // Wooden door box(TILE_SIZE-2, TILE_SIZE-2, 3); } pop(); } } } </script> </body> </html> ``` This code creates a fully functional dungeon generator with: 1. Random room placement with size variation (4-8 tiles wide/long) 2. Automatic connection between rooms with corridors 3. Proper wall placement around rooms and corridors 4. Corner tiles at room corners 5. Isometric 3D view with: - Raised floor tiles for walkable areas - Tall walls for boundaries - Different colors for different tile types - Camera controls (click and drag to rotate) You can refresh the page to generate a new random dungeon layout. The code prevents room overlaps and ensures all rooms are connected through corridors.
Drag to resize
```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Procedural Dungeon Generator - Isometric View</title> <style> body { background: #222; margin: 0; overflow: hidden; } canvas { display: block; margin: 20px auto; background: #262626; } </style> <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.9.0/p5.min.js"></script> </head> <body> <script> const GRID_SIZE = 30; const TILE_W = 32; const TILE_H = 16; const ROOM_MIN = 5; const ROOM_MAX = 10; const NUM_ROOMS = 5; let grid = []; let rooms = []; // Enum for tile types const TILE_VOID = 0; const TILE_FLOOR = 1; const TILE_WALL = 2; const TILE_CORRIDOR = 3; const TILE_CORNER = 4; function setup() { createCanvas(1200, 800); generateDungeon(); noLoop(); } function generateDungeon() { // Init grid for (let y = 0; y < GRID_SIZE; y++) { grid[y] = []; for (let x = 0; x < GRID_SIZE; x++) { grid[y][x] = TILE_VOID; } } rooms = []; let failCount = 0; // Place rooms while (rooms.length < NUM_ROOMS && failCount < 200) { let rw = floor(random(ROOM_MIN, ROOM_MAX)); let rh = floor(random(ROOM_MIN, ROOM_MAX)); let rx = floor(random(1, GRID_SIZE - rw - 1)); let ry = floor(random(1, GRID_SIZE - rh - 1)); let overlaps = false; for (let room of rooms) { if (rx + rw + 1 > room.x && rx < room.x + room.w + 1 && ry + rh + 1 > room.y && ry < room.y + room.h + 1) { overlaps = true; break; } } if (!overlaps) { rooms.push({x: rx, y: ry, w: rw, h: rh}); // Draw the room floor for (let y = ry; y < ry + rh; y++) { for (let x = rx; x < rx + rw; x++) { grid[y][x] = TILE_FLOOR; } } } else { failCount++; } } // Place walls and corners for (let r of rooms) { for (let y = r.y-1; y <= r.y+r.h; y++) { for (let x = r.x-1; x <= r.x+r.w; x++) { if (x >= 0 && y >= 0 && x < GRID_SIZE && y < GRID_SIZE) { if (grid[y][x] == TILE_VOID && ((x == r.x-1 || x == r.x+r.w) && y >= r.y && y <= r.y+r.h-1) || ((y == r.y-1 || y == r.y+r.h) && x >= r.x && x <= r.x+r.w-1) ) { grid[y][x] = TILE_WALL; } } } } // Corners let corners = [ [r.x-1, r.y-1], [r.x-1, r.y+r.h], [r.x+r.w, r.y-1], [r.x+r.w, r.y+r.h], ]; for (let c of corners) { if (c[0] >=0 && c[1] >= 0 && c[0] < GRID_SIZE && c[1] < GRID_SIZE) { grid[c[1]][c[0]] = TILE_CORNER; } } } // Connect rooms with corridors let roomCenters = rooms.map(r => [r.x + floor(r.w/2), r.y + floor(r.h/2)]); roomCenters = shuffle(roomCenters); for (let i = 0; i < roomCenters.length - 1; i++) { connectRooms(roomCenters[i], roomCenters[i+1]); } } function connectRooms(a, b) { let [x1, y1] = a; let [x2, y2] = b; // L-shaped corridor: first in x, then in y (randomize order) let flip = random() < 0.5; if (flip) { drawCorridor(x1, y1, x2, y1); drawCorridor(x2, y1, x2, y2); } else { drawCorridor(x1, y1, x1, y2); drawCorridor(x1, y2, x2, y2); } } // Bresenham for L-shape function drawCorridor(x0, y0, x1, y1) { let dx = Math.abs(x1 - x0); let sx = x0 < x1 ? 1 : -1; let dy = Math.abs(y1 - y0); let sy = y0 < y1 ? 1 : -1; let err = dx - dy; while (true) { if (grid[y0][x0] === TILE_VOID) { grid[y0][x0] = TILE_CORRIDOR; } if (x0 === x1 && y0 === y1) break; let e2 = 2 * err; if (e2 > -dy) { err -= dy; x0 += sx;} if (e2 < dx) { err += dx; y0 += sy;} } // Optionally put walls along corridors for (let y=0; y<GRID_SIZE; y++) for (let x=0; x<GRID_SIZE; x++) { if (grid[y][x] === TILE_CORRIDOR) { for (let dy=-1; dy<=1; dy++) for (let dx=-1; dx<=1; dx++) { let nx = x+dx, ny = y+dy; if (dx*dx + dy*dy == 1 && nx>=0 && ny>=0 && nx<GRID_SIZE && ny<GRID_SIZE) { if (grid[ny][nx] === TILE_VOID) grid[ny][nx] = TILE_WALL; } } } } } function draw() { background(40); let centerX = width/2; let centerY = 120; // Draw dungeon for (let y = 0; y < GRID_SIZE; y++) { for (let x = 0; x < GRID_SIZE; x++) { let sx = (x - y) * TILE_W * 0.5 + centerX; let sy = (x + y) * TILE_H * 0.5 + centerY; drawTile(sx, sy, grid[y][x], x, y); } } } function drawTile(sx, sy, t, x, y) { push(); translate(sx, sy); switch(t) { case TILE_FLOOR: // Room floor: sandy color, slightly random fill(194 + noise(x*0.2, y*0.2)*12, 178, 128 + noise(y*0.3, x*0.3)*10, 240); stroke(180,140,80,120); drawDiamond(); break; case TILE_WALL: // Wall: dark stone brick fill(90+noise(x,y)*30, 90+noise(y,x)*30, 80, 210); stroke(40); drawWallTile(); break; case TILE_CORNER: // Corner: roundish highlight fill(110, 100, 90, 230); stroke(50); drawDiamond(); fill(160,140,80,160); noStroke(); ellipse(0, 1, 14, 10); break; case TILE_CORRIDOR: // Corridor: grayish, thin outline fill(150+noise(x,y)*16, 155+noise(y,x)*20, 170, 230); stroke(130,140,160,130); drawDiamond(); break; case TILE_VOID: default: // Void: slightly dark fill(32,36,32,220); stroke(24,28,18,130); drawDiamond(); break; } pop(); } function drawDiamond() { beginShape(); vertex(0, -TILE_H/2); vertex(TILE_W/2, 0); vertex(0, TILE_H/2); vertex(-TILE_W/2, 0); endShape(CLOSE); } function drawWallTile() { // Diamond, with brick lines drawDiamond(); // Horizontal "brick" lines stroke(120,110,88,70); for(let yy=-6; yy<=6; yy+=6) { line(-TILE_W/2 + 2, yy, TILE_W/2 - 2, yy); } // Vertical split simulates bricks stroke(140,130,110,50); line(0, -TILE_H/2, 0, TILE_H/2); } </script> </body> </html> ```
Drag to resize
Response not available
Drag to resize
Here's a simple implementation of a procedural dungeon generator using p5.js. ```javascript // Define tile types const TILE_TYPES = { EMPTY: 0, ROOM: 1, WALL: 2, CORRIDOR: 3, CORNER: 4 }; let grid = []; let rooms = []; function setup() { createCanvas(600, 600); angleMode(DEGREES); noStroke(); // Initialize grid for (let i = 0; i < 30; i++) { grid[i] = []; for (let j = 0; j < 30; j++) { grid[i][j] = TILE_TYPES.EMPTY; } } // Generate rooms for (let i = 0; i < 5; i++) { let roomWidth = floor(random(3, 8)); let roomHeight = floor(random(3, 8)); let roomX = floor(random(0, 30 - roomWidth)); let roomY = floor(random(0, 30 - roomHeight)); // Check for overlap let overlap = false; for (let j = 0; j < rooms.length; j++) { let otherRoom = rooms[j]; if (roomX < otherRoom.x + otherRoom.width && roomX + roomWidth > otherRoom.x && roomY < otherRoom.y + otherRoom.height && roomY + roomHeight > otherRoom.y) { overlap = true; break; } } if (!overlap) { rooms.push({ x: roomX, y: roomY, width: roomWidth, height: roomHeight }); } else { i--; // Try again if there's an overlap } } // Mark room tiles for (let i = 0; i < rooms.length; i++) { let room = rooms[i]; for (let j = room.x; j < room.x + room.width; j++) { for (let k = room.y; k < room.y + room.height; k++) { grid[j][k] = TILE_TYPES.ROOM; } } } // Connect rooms with corridors for (let i = 0; i < rooms.length - 1; i++) { let room1 = rooms[i]; let room2 = rooms[i + 1]; let x1 = room1.x + floor(room1.width / 2); let y1 = room1.y + floor(room1.height / 2); let x2 = room2.x + floor(room2.width / 2); let y2 = room2.y + floor(room2.height / 2); // Draw corridor if (abs(x2 - x1) > abs(y2 - y1)) { // Horizontal then vertical if (x1 < x2) { for (let j = x1; j <= x2; j++) { if (grid[j][y1] == TILE_TYPES.ROOM) { // Do nothing } else if (grid[j][y1] == TILE_TYPES.EMPTY) { grid[j][y1] = TILE_TYPES.CORRIDOR; } else if (grid[j][y1] == TILE_TYPES.WALL) { grid[j][y1] = TILE_TYPES.CORNER; } } } else { for (let j = x1; j >= x2; j--) { if (grid[j][y1] == TILE_TYPES.ROOM) { // Do nothing } else if (grid[j][y1] == TILE_TYPES.EMPTY) { grid[j][y1] = TILE_TYPES.CORRIDOR; } else if (grid[j][y1] == TILE_TYPES.WALL) { grid[j][y1] = TILE_TYPES.CORNER; } } } if (y1 < y2) { for (let j = y1; j <= y2; j++) { if (grid[x2][j] == TILE_TYPES.ROOM) { // Do nothing } else if (grid[x2][j] == TILE_TYPES.EMPTY) { grid[x2][j] = TILE_TYPES.CORRIDOR; } else if (grid[x2][j] == TILE_TYPES.WALL) { grid[x2][j] = TILE_TYPES.CORNER; } } } else { for (let j = y1; j >= y2; j--) { if (grid[x2][j] == TILE_TYPES.ROOM) { // Do nothing } else if (grid[x2][j] == TILE_TYPES.EMPTY) { grid[x2][j] = TILE_TYPES.CORRIDOR; } else if (grid[x2][j] == TILE_TYPES.WALL) { grid[x2][j] = TILE_TYPES.CORNER; } } } } else { // Vertical then horizontal if (y1 < y2) { for (let j = y1; j <= y2; j++) { if (grid[x1][j] == TILE_TYPES.ROOM) { // Do nothing } else if (grid[x1][j] == TILE_TYPES.EMPTY) { grid[x1][j] = TILE_TYPES.CORRIDOR; } else if (grid[x1][j] == TILE_TYPES.WALL) { grid[x1][j] = TILE_TYPES.CORNER; } } } else { for (let j = y1; j >= y2; j--) { if (grid[x1][j] == TILE_TYPES.ROOM) { // Do nothing } else if (grid[x1][j] == TILE_TYPES.EMPTY) { grid[x1][j] = TILE_TYPES.CORRIDOR; } else if (grid[x1][j] == TILE_TYPES.WALL) { grid[x1][j] = TILE_TYPES.CORNER; } } } if (x1 < x2) { for (let j = x1; j <= x2; j++) { if (grid[j][y2] == TILE_TYPES.ROOM) { // Do nothing } else if (grid[j][y2] == TILE_TYPES.EMPTY) { grid[j][y2] = TILE_TYPES.CORRIDOR; } else if (grid[j][y2] == TILE_TYPES.WALL) { grid[j][y2] = TILE_TYPES.CORNER; } } } else { for (let j = x1; j >= x2; j--) { if (grid[j][y2] == TILE_TYPES.ROOM) { // Do nothing } else if (grid[j][y2] == TILE_TYPES.EMPTY) { grid[j][y2] = TILE_TYPES.CORRIDOR; } else if (grid[j][y2] == TILE_TYPES.WALL) { grid[j][y2] = TILE_TYPES.CORNER; } } } } } // Mark walls for (let i = 0; i < 30; i++) { for (let j = 0; j < 30; j++) { if (grid[i][j] == TILE_TYPES.ROOM) { if (i == 0 || grid[i - 1][j] != TILE_TYPES.ROOM) { grid[i][j] = TILE_TYPES.WALL; } else if (i == 29 || grid[i + 1][j] != TILE_TYPES.ROOM) { grid[i][j] = TILE_TYPES.WALL; } else if (j == 0 || grid[i][j - 1] != TILE_TYPES.ROOM) { grid[i][j] = TILE_TYPES.WALL; } else if (j == 29 || grid[i][j + 1] != TILE_TYPES.ROOM) { grid[i][j] = TILE_TYPES.WALL; } } } } } function draw() { background(220); // Isometric view let tileSize = 20; let offsetX = width / 2 - 15 * tileSize; let offsetY = height / 2 - 15 * tileSize; for (let i = 0; i < 30; i++) { for (let j = 0; j < 30; j++) { let x = i * tileSize / 2 - j * tileSize / 2 + offsetX; let y = i * tileSize / 4 + j * tileSize / 4 + offsetY; if (grid[i][j] == TILE_TYPES.ROOM) { fill(240, 220, 100); // Beige beginShape(); vertex(x, y); vertex(x + tileSize / 2, y + tileSize / 4); vertex(x, y + tileSize / 2); vertex(x - tileSize / 2, y + tileSize / 4); endShape(CLOSE); } else if (grid[i][j] == TILE_TYPES.WALL) { fill(120, 120, 120); // Gray beginShape(); vertex(x, y); vertex(x + tileSize / 2, y + tileSize / 4); vertex(x, y + tileSize / 2); vertex(x - tileSize / 2, y + tileSize / 4); endShape(CLOSE); } else if (grid[i][j] == TILE_TYPES.CORRIDOR) { fill(180, 140, 80); // Brown beginShape(); vertex(x, y); vertex(x + tileSize / 2, y + tileSize / 4); vertex(x, y + tileSize / 2); vertex(x - tileSize / 2, y + tileSize / 4); endShape(CLOSE); } else if (grid[i][j] == TILE_TYPES.CORNER) { fill(150, 100, 50); // Dark Brown beginShape(); vertex(x, y); vertex(x + tileSize / 2, y + tileSize / 4); vertex(x, y + tileSize / 2); vertex(x - tileSize / 2, y + tileSize / 4); endShape(CLOSE); } } } } ``` This code generates a 30x30 grid and creates 5 random rooms. It then connects these rooms with corridors. The grid is then drawn in an isometric view. Different tile types are represented by different colors. To run this code, simply copy and paste it into a p5.js sketch. You can adjust the grid size, number of rooms, and tile size as needed. The `setup` function is called once when the sketch starts, and the `draw` function is called continuously until the sketch is stopped.
Drag to resize