User prompt
Has que la generación de los pasillos sea menos frecuente y si se generan sean más cortos
User prompt
Please fix the bug: 'self.createNarrowCorridorAreas is not a function. (In 'self.createNarrowCorridorAreas(offsetX, offsetY, rooms)', 'self.createNarrowCorridorAreas' is undefined)' in or related to this line: 'self.createNarrowCorridorAreas(offsetX, offsetY, rooms);' Line Number: 802
User prompt
Aparte de las habitaciones has que el sistema procedural pueda crear áreas de pasillos estrechos lo suficientemente grandes para que el personaje pueda pasar
User prompt
Please fix the bug: 'self.createDeadEndCorridors is not a function. (In 'self.createDeadEndCorridors(offsetX, offsetY, rooms)', 'self.createDeadEndCorridors' is undefined)' in or related to this line: 'self.createDeadEndCorridors(offsetX, offsetY, rooms);' Line Number: 550
User prompt
Please fix the bug: 'self.createDeadEndCorridors is not a function. (In 'self.createDeadEndCorridors(offsetX, offsetY, rooms)', 'self.createDeadEndCorridors' is undefined)' in or related to this line: 'self.createDeadEndCorridors(offsetX, offsetY, rooms);' Line Number: 550
User prompt
Please fix the bug: 'self.createDeadEndCorridors is not a function. (In 'self.createDeadEndCorridors(offsetX, offsetY, rooms)', 'self.createDeadEndCorridors' is undefined)' in or related to this line: 'self.createDeadEndCorridors(offsetX, offsetY, rooms);' Line Number: 550
User prompt
Has que el sistema procedural cree las habitaciones simpre con múltiples salidas y con múltiples callejones sin salida
User prompt
Has un sistema de reconocimiento de pasajes si una habitación no tiene salida crea una que se haga antes que el jugador pase por ese lugar
User prompt
Corrige el sistema procedural para que siempre esté conectada por lo menos con 1 habitación y que la siguiente tenga lo mismo
User prompt
Crea un sistema de reconocimiento de áreas sin salida si una habitación no tiene salida se considera pared
User prompt
Has que el jugador aparezca en un lugar donde se pueda mover si esta colisionando con una pared redirigelo a un lugar donde se pueda mover
User prompt
Cada habitación tiene que tener por lo menos 1 salida a otra habitación
User prompt
Has que los pasillos tengan la probabilidad del 30% de generarse
User prompt
Has que los pasillos sean más cortos
User prompt
Haz que las habitaciones sean variadas y que no sean tan rectas o pasillos rectos
User prompt
Has que las habitaciones tengan probabilidades las pequeñas de 50% mediana 35% y las grandes de 15%
User prompt
Las habitaciones mucho más más pequeña
User prompt
Has que las habitaciones sean más pequeñas
User prompt
Please fix the bug: 'TypeError: undefined is not an object (evaluating 'worldGrid.walls[midX + i - Math.floor(self.hallwayWidth / 2)][offsetY + self.chunkSize] = false')' in or related to this line: 'worldGrid.walls[midX + i - Math.floor(self.hallwayWidth / 2)][offsetY + self.chunkSize] = false;' Line Number: 631
User prompt
Corrige la generación procedural o has uno nuevo en este sistema por lo menos tiene que a ver 1 salida o entrada a una habitación nueva
User prompt
Crea el escenario proseduralmente
User prompt
El piso está mal posicionado
User prompt
Has que el piso tenga sombreado si el personaje está serca se ilumina pero si esta lejos se oscurece pero hazlo en el área de piso no lo pongas en el área de pared ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Ahora ponle sombreado como lo tienen los demás objetos ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Error el piso lo esas considerando pared y estas dejando sin textura el área de piso
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1"); /**** * Classes ****/ var CeilingTileRenderer = Container.expand(function () { var self = Container.call(this); self.tiles = []; // Generate ceiling tiles in safe positions (away from corners and walls) self.generateTiles = function () { for (var x = 2; x < worldGrid.width - 2; x++) { for (var y = 2; y < worldGrid.height - 2; y++) { // Only place tiles in open areas (not near walls or corners) if (!worldGrid.hasWallAt(x * worldGrid.cellSize, y * worldGrid.cellSize) && !self.isNearCorner(x, y) && self.isSafePosition(x, y)) { var tile = { worldX: x * worldGrid.cellSize + worldGrid.cellSize / 2, worldY: y * worldGrid.cellSize + worldGrid.cellSize / 2, sprite: null }; self.tiles.push(tile); } } } }; // Check if position is near a corner self.isNearCorner = function (gridX, gridY) { // Check 3x3 area around position for wall density var wallCount = 0; for (var dx = -1; dx <= 1; dx++) { for (var dy = -1; dy <= 1; dy++) { var checkX = gridX + dx; var checkY = gridY + dy; if (checkX >= 0 && checkX < worldGrid.width && checkY >= 0 && checkY < worldGrid.height) { if (worldGrid.walls && worldGrid.walls[checkX] && worldGrid.walls[checkX][checkY]) { wallCount++; } } } } return wallCount >= 3; // Near corner if 3+ walls nearby }; // Check if position is safe (center of open areas) self.isSafePosition = function (gridX, gridY) { // Ensure there's open space in all 4 cardinal directions var directions = [{ x: 0, y: -1 }, { x: 1, y: 0 }, { x: 0, y: 1 }, { x: -1, y: 0 }]; for (var i = 0; i < directions.length; i++) { var checkX = gridX + directions[i].x; var checkY = gridY + directions[i].y; if (checkX >= 0 && checkX < worldGrid.width && checkY >= 0 && checkY < worldGrid.height) { if (worldGrid.walls && worldGrid.walls[checkX] && worldGrid.walls[checkX][checkY]) { return false; } } } return true; }; self.render = function (player) { // Clear existing sprites for (var i = 0; i < self.tiles.length; i++) { if (self.tiles[i].sprite) { self.tiles[i].sprite.visible = false; } } var visibleTiles = []; // Calculate which tiles are visible and their screen positions for (var i = 0; i < self.tiles.length; i++) { var tile = self.tiles[i]; var dx = tile.worldX - player.x; var dy = tile.worldY - player.y; var distance = Math.sqrt(dx * dx + dy * dy); // Only render tiles within reasonable distance if (distance < 800) { // Calculate angle relative to player's view direction var tileAngle = Math.atan2(dy, dx); var angleDiff = tileAngle - player.angle; // Normalize angle difference while (angleDiff > Math.PI) angleDiff -= 2 * Math.PI; while (angleDiff < -Math.PI) angleDiff += 2 * Math.PI; // Check if tile is within field of view var fov = Math.PI / 3; if (Math.abs(angleDiff) < fov / 2) { // Calculate screen X position var screenX = 1366 + angleDiff / (fov / 2) * 1366; // Only add tiles that are within horizontal screen bounds with margin if (screenX >= -50 && screenX <= 2782) { // Apply pitch offset to ceiling tiles var pitchOffset = player.pitch * 400; visibleTiles.push({ tile: tile, distance: distance, screenX: screenX, screenY: 400 - 200 * (1000 / (distance + 100)) + pitchOffset // Project to ceiling with pitch }); } } } } // Sort by distance (farthest first) visibleTiles.sort(function (a, b) { return b.distance - a.distance; }); // Render visible tiles for (var i = 0; i < visibleTiles.length; i++) { var visibleTile = visibleTiles[i]; var tile = visibleTile.tile; if (!tile.sprite) { tile.sprite = self.addChild(LK.getAsset('ceilingTile', { anchorX: 0.5, anchorY: 0.5 })); } tile.sprite.x = visibleTile.screenX; tile.sprite.y = visibleTile.screenY; tile.sprite.visible = true; // Scale based on distance var scale = Math.max(0.1, 20 / (visibleTile.distance + 20)); tile.sprite.scaleX = scale; tile.sprite.scaleY = scale; } }; return self; }); var GeometricWallRenderer = Container.expand(function () { var self = Container.call(this); // Simplified wall rendering with geometric shapes self.wallStrips = []; self.numStrips = 64; // Further reduced for better performance self.maxWalls = 32; // Maximum number of wall strips to render // Initialize wall strips pool self.initWallStrips = function () { for (var i = 0; i < self.maxWalls; i++) { var wallStrip = self.addChild(LK.getAsset('wallSegment', { anchorX: 0.5, anchorY: 0.5 })); wallStrip.visible = false; self.wallStrips.push(wallStrip); } }; // Get available wall strip from pool self.getWallStrip = function () { for (var i = 0; i < self.wallStrips.length; i++) { if (!self.wallStrips[i].visible) { return self.wallStrips[i]; } } return null; }; // Render walls using simplified column-based approach self.render = function (player) { // Initialize strips if not done if (self.wallStrips.length === 0) { self.initWallStrips(); } // Hide all wall strips for (var j = 0; j < self.wallStrips.length; j++) { self.wallStrips[j].visible = false; } var fov = Math.PI / 3; // 60 degrees field of view var halfFov = fov / 2; var screenCenter = 1024; // Y center of screen var pitchOffset = player.pitch * 300; // Apply pitch for vertical look var stripWidth = 2732 / self.numStrips; var wallsRendered = 0; // Cast rays and render wall strips for (var i = 0; i < self.numStrips && wallsRendered < self.maxWalls; i++) { var rayAngle = player.angle - halfFov + i / self.numStrips * fov; var rayData = self.castSimpleRay(player.x, player.y, rayAngle); if (rayData.hit) { var distance = rayData.distance; // Apply fish-eye correction var correctedDistance = distance * Math.cos(rayAngle - player.angle); // Calculate screen X position for horizontal bounds checking var screenX = i * stripWidth + stripWidth / 2; // Only render if within horizontal screen bounds with stricter checking if (screenX >= -stripWidth && screenX <= 2732 + stripWidth) { // Get wall strip from pool var wallStrip = self.getWallStrip(); if (wallStrip) { // Calculate wall height based on distance var baseWallSize = worldGrid.cellSize; var wallHeight = Math.max(60, baseWallSize * (500 / (correctedDistance + 50))); // Position wall strip wallStrip.width = stripWidth + 2; // Add small overlap wallStrip.height = wallHeight; wallStrip.x = Math.max(-stripWidth, Math.min(2732 + stripWidth, screenX)); // Clamp position wallStrip.y = screenCenter + pitchOffset; wallStrip.visible = true; // Apply distance-based shading var shadingFactor = Math.max(0.2, 1.0 - correctedDistance / 600); var tintValue = Math.floor(shadingFactor * 255); wallStrip.tint = tintValue << 16 | tintValue << 8 | tintValue; // Add slight variation based on position for texture effect var positionVariation = (rayData.hitX + rayData.hitY) % 40; if (positionVariation < 20) { tintValue = Math.floor(tintValue * 0.9); // Slightly darker wallStrip.tint = tintValue << 16 | tintValue << 8 | tintValue; } wallsRendered++; } } } } }; // Simplified raycasting for geometric walls self.castSimpleRay = function (startX, startY, angle) { var rayX = startX; var rayY = startY; var deltaX = Math.cos(angle) * 8; // Larger steps for performance var deltaY = Math.sin(angle) * 8; var distance = 0; var maxDistance = 600; var stepSize = 8; // Raycast until wall hit or max distance while (distance < maxDistance) { rayX += deltaX; rayY += deltaY; distance += stepSize; // Check for wall collision if (worldGrid.hasWallAt(rayX, rayY)) { return { hit: true, distance: distance, hitX: rayX, hitY: rayY }; } } return { hit: false, distance: maxDistance, hitX: rayX, hitY: rayY }; }; return self; }); var MovementCrosshair = Container.expand(function () { var self = Container.call(this); self.isActive = false; self.activeButton = null; // Create base circle var base = self.attachAsset('crosshairBase', { anchorX: 0.5, anchorY: 0.5 }); base.alpha = 0.6; // Create directional buttons var upButton = self.attachAsset('crosshairUp', { anchorX: 0.5, anchorY: 0.5 }); upButton.x = 0; upButton.y = -70; upButton.alpha = 0.7; var downButton = self.attachAsset('crosshairDown', { anchorX: 0.5, anchorY: 0.5 }); downButton.x = 0; downButton.y = 70; downButton.alpha = 0.7; var leftButton = self.attachAsset('crosshairLeft', { anchorX: 0.5, anchorY: 0.5 }); leftButton.x = -70; leftButton.y = 0; leftButton.alpha = 0.7; var rightButton = self.attachAsset('crosshairRight', { anchorX: 0.5, anchorY: 0.5 }); rightButton.x = 70; rightButton.y = 0; rightButton.alpha = 0.7; var centerButton = self.attachAsset('crosshairCenter', { anchorX: 0.5, anchorY: 0.5 }); centerButton.alpha = 0.8; // Movement state tracking self.movementState = { forward: false, backward: false, left: false, right: false }; // Button press handlers upButton.down = function (x, y, obj) { upButton.alpha = 1.0; self.movementState.forward = true; self.activeButton = 'up'; }; upButton.up = function (x, y, obj) { upButton.alpha = 0.7; self.movementState.forward = false; if (self.activeButton === 'up') { self.activeButton = null; } }; downButton.down = function (x, y, obj) { downButton.alpha = 1.0; self.movementState.backward = true; self.activeButton = 'down'; }; downButton.up = function (x, y, obj) { downButton.alpha = 0.7; self.movementState.backward = false; if (self.activeButton === 'down') { self.activeButton = null; } }; leftButton.down = function (x, y, obj) { leftButton.alpha = 1.0; self.movementState.left = true; self.activeButton = 'left'; }; leftButton.up = function (x, y, obj) { leftButton.alpha = 0.7; self.movementState.left = false; if (self.activeButton === 'left') { self.activeButton = null; } }; rightButton.down = function (x, y, obj) { rightButton.alpha = 1.0; self.movementState.right = true; self.activeButton = 'right'; }; rightButton.up = function (x, y, obj) { rightButton.alpha = 0.7; self.movementState.right = false; if (self.activeButton === 'right') { self.activeButton = null; } }; // Get current movement state self.getMovementState = function () { return self.movementState; }; // Reset all movement states self.resetMovement = function () { self.movementState.forward = false; self.movementState.backward = false; self.movementState.left = false; self.movementState.right = false; self.activeButton = null; upButton.alpha = 0.7; downButton.alpha = 0.7; leftButton.alpha = 0.7; rightButton.alpha = 0.7; }; return self; }); var Player = Container.expand(function () { var self = Container.call(this); self.x = 1366; self.y = 1024; self.angle = 0; self.pitch = 0; // Vertical look angle (up/down) self.speed = 3; self.rotSpeed = 0.1; // Smooth interpolation properties self.targetX = 1366; self.targetY = 1024; self.targetAngle = 0; self.targetPitch = 0; self.smoothingFactor = 0.15; // Player visual for debugging (will be hidden in first person) var playerGraphics = self.attachAsset('player', { anchorX: 0.5, anchorY: 0.5 }); playerGraphics.visible = false; // Hide for first person view self.moveForward = function () { var newX = self.targetX + Math.cos(self.targetAngle) * self.speed; var newY = self.targetY + Math.sin(self.targetAngle) * self.speed; // Constrain Y coordinate to not go below 0 if (newY < 0) { newY = 0; } // Check collision with world grid using improved collision detection if (!worldGrid.checkCollision(newX, newY)) { self.targetX = newX; self.targetY = newY; } }; self.moveBackward = function () { var newX = self.targetX - Math.cos(self.targetAngle) * self.speed; var newY = self.targetY - Math.sin(self.targetAngle) * self.speed; // Constrain Y coordinate to not go below 0 if (newY < 0) { newY = 0; } // Check collision with world grid using improved collision detection if (!worldGrid.checkCollision(newX, newY)) { self.targetX = newX; self.targetY = newY; } }; self.turnLeft = function () { self.targetAngle -= self.rotSpeed; }; self.turnRight = function () { self.targetAngle += self.rotSpeed; }; self.lookUp = function () { self.targetPitch = Math.max(-Math.PI / 3, self.targetPitch - self.rotSpeed); // Limit to -60 degrees }; self.lookDown = function () { self.targetPitch = Math.min(Math.PI / 3, self.targetPitch + self.rotSpeed); // Limit to +60 degrees }; self.updateSmooth = function () { // Smooth interpolation for position self.x += (self.targetX - self.x) * self.smoothingFactor; self.y += (self.targetY - self.y) * self.smoothingFactor; // Smooth interpolation for rotation with angle wrapping var angleDiff = self.targetAngle - self.angle; // Handle angle wrapping (ensure shortest rotation path) if (angleDiff > Math.PI) angleDiff -= 2 * Math.PI; if (angleDiff < -Math.PI) angleDiff += 2 * Math.PI; self.angle += angleDiff * self.smoothingFactor; // Smooth interpolation for pitch var pitchDiff = self.targetPitch - self.pitch; self.pitch += pitchDiff * self.smoothingFactor; }; return self; }); var ProcGen = Container.expand(function () { var self = Container.call(this); self.generatedChunks = {}; self.chunkSize = 10; // Smaller chunks for more compact generation self.roomMinSize = 1; self.roomMaxSize = 2; self.hallwayWidth = 1; // Much narrower connecting hallways - reduced for shorter corridors // Generate a procedural chunk at given chunk coordinates self.generateChunk = function (chunkX, chunkY) { var chunkKey = chunkX + ',' + chunkY; if (self.generatedChunks[chunkKey]) { return; // Already generated } self.generatedChunks[chunkKey] = true; // Calculate world grid offset for this chunk var offsetX = chunkX * self.chunkSize; var offsetY = chunkY * self.chunkSize; // Generate Backrooms-style layout for this chunk self.generateBackroomsChunk(offsetX, offsetY); }; // Generate Backrooms-style layout with guaranteed connectivity self.generateBackroomsChunk = function (offsetX, offsetY) { // First, fill entire chunk with walls for (var x = offsetX; x < offsetX + self.chunkSize; x++) { for (var y = offsetY; y < offsetY + self.chunkSize; y++) { if (x >= 0 && x < worldGrid.width && y >= 0 && y < worldGrid.height) { worldGrid.walls[x][y] = true; } } } // Create main room in center of chunk with irregular shape var mainRoomSize = 2; var mainRoomX = offsetX + Math.floor((self.chunkSize - mainRoomSize) / 2); var mainRoomY = offsetY + Math.floor((self.chunkSize - mainRoomSize) / 2); self.carveIrregularRoom(mainRoomX, mainRoomY, mainRoomSize, mainRoomSize); // Create 1-2 additional rooms with size probabilities var numRooms = Math.floor(Math.random() * 2) + 1; var rooms = [{ x: mainRoomX, y: mainRoomY, width: mainRoomSize, height: mainRoomSize, centerX: mainRoomX + Math.floor(mainRoomSize / 2), centerY: mainRoomY + Math.floor(mainRoomSize / 2) }]; for (var i = 0; i < numRooms; i++) { var attempts = 0; while (attempts < 30) { // Determine room size based on probabilities var roomSizes = self.getRoomSizeByProbability(); var roomW = roomSizes.width; var roomH = roomSizes.height; var roomX = offsetX + Math.floor(Math.random() * (self.chunkSize - roomW - 2)) + 1; var roomY = offsetY + Math.floor(Math.random() * (self.chunkSize - roomH - 2)) + 1; var newRoom = { x: roomX, y: roomY, width: roomW, height: roomH, centerX: roomX + Math.floor(roomW / 2), centerY: roomY + Math.floor(roomH / 2) }; if (!self.roomOverlaps(newRoom, rooms)) { self.carveIrregularRoom(roomX, roomY, roomW, roomH); rooms.push(newRoom); // Connect this room to the nearest existing room with curved corridor self.connectToNearestRoomCurved(newRoom, rooms); break; } attempts++; } } // Create guaranteed exits to adjacent chunks self.createChunkExits(offsetX, offsetY, rooms[0]); // Add some random pillars for atmosphere self.addRandomPillars(offsetX, offsetY); }; // Carve out a room (remove walls) self.carveRoom = function (x, y, width, height) { for (var roomX = x; roomX < x + width; roomX++) { for (var roomY = y; roomY < y + height; roomY++) { if (roomX >= 0 && roomX < worldGrid.width && roomY >= 0 && roomY < worldGrid.height) { worldGrid.walls[roomX][roomY] = false; } } } }; // Carve out an irregular room shape with random variations self.carveIrregularRoom = function (x, y, width, height) { var roomType = Math.random(); var centerX = x + Math.floor(width / 2); var centerY = y + Math.floor(height / 2); if (roomType < 0.4) { // Circular/oval room var radiusX = Math.floor(width / 2); var radiusY = Math.floor(height / 2); for (var roomX = x; roomX < x + width; roomX++) { for (var roomY = y; roomY < y + height; roomY++) { if (roomX >= 0 && roomX < worldGrid.width && roomY >= 0 && roomY < worldGrid.height) { var dx = roomX - centerX; var dy = roomY - centerY; // Create elliptical shape with some randomness var distanceSquared = dx * dx / (radiusX * radiusX) + dy * dy / (radiusY * radiusY); if (distanceSquared <= 1.0 + Math.random() * 0.3 - 0.15) { worldGrid.walls[roomX][roomY] = false; } } } } } else if (roomType < 0.7) { // L-shaped room self.carveRoom(x, y, width, Math.floor(height / 2) + 1); self.carveRoom(x + Math.floor(width / 2), y + Math.floor(height / 2), Math.floor(width / 2) + 1, Math.floor(height / 2) + 1); } else if (roomType < 0.9) { // Cross-shaped room var halfW = Math.floor(width / 2); var halfH = Math.floor(height / 2); // Vertical bar self.carveRoom(centerX - 1, y, 2, height); // Horizontal bar self.carveRoom(x, centerY - 1, width, 2); } else { // Regular rectangular room with random indentations self.carveRoom(x, y, width, height); // Add random indentations var indentations = Math.floor(Math.random() * 3) + 1; for (var i = 0; i < indentations; i++) { var indentX = x + Math.floor(Math.random() * width); var indentY = y + Math.floor(Math.random() * height); var indentSize = Math.floor(Math.random() * 2) + 1; for (var ix = 0; ix < indentSize; ix++) { for (var iy = 0; iy < indentSize; iy++) { var wallX = indentX + ix; var wallY = indentY + iy; if (wallX >= 0 && wallX < worldGrid.width && wallY >= 0 && wallY < worldGrid.height) { worldGrid.walls[wallX][wallY] = true; } } } } } }; // Check if room overlaps with existing rooms self.roomOverlaps = function (room, existingRooms) { for (var i = 0; i < existingRooms.length; i++) { var existing = existingRooms[i]; // Add padding between rooms if (room.x < existing.x + existing.width + 2 && room.x + room.width + 2 > existing.x && room.y < existing.y + existing.height + 2 && room.y + room.height + 2 > existing.y) { return true; } } return false; }; // Connect room to nearest existing room self.connectToNearestRoom = function (newRoom, existingRooms) { var nearestRoom = existingRooms[0]; var minDistance = Infinity; // Find nearest room for (var i = 0; i < existingRooms.length; i++) { if (existingRooms[i] !== newRoom) { var dx = newRoom.centerX - existingRooms[i].centerX; var dy = newRoom.centerY - existingRooms[i].centerY; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < minDistance) { minDistance = distance; nearestRoom = existingRooms[i]; } } } // Create L-shaped corridor self.createCorridor(newRoom.centerX, newRoom.centerY, nearestRoom.centerX, nearestRoom.centerY); }; // Connect room to nearest existing room with curved corridor self.connectToNearestRoomCurved = function (newRoom, existingRooms) { var nearestRoom = existingRooms[0]; var minDistance = Infinity; // Find nearest room for (var i = 0; i < existingRooms.length; i++) { if (existingRooms[i] !== newRoom) { var dx = newRoom.centerX - existingRooms[i].centerX; var dy = newRoom.centerY - existingRooms[i].centerY; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < minDistance) { minDistance = distance; nearestRoom = existingRooms[i]; } } } // Always create at least one connection to guarantee room connectivity self.createCurvedCorridor(newRoom.centerX, newRoom.centerY, nearestRoom.centerX, nearestRoom.centerY); // Create additional curved corridors with 30% probability for variety if (Math.random() < 0.3 && existingRooms.length > 1) { // Find second nearest room for additional connection var secondNearestRoom = null; var secondMinDistance = Infinity; for (var i = 0; i < existingRooms.length; i++) { if (existingRooms[i] !== newRoom && existingRooms[i] !== nearestRoom) { var dx = newRoom.centerX - existingRooms[i].centerX; var dy = newRoom.centerY - existingRooms[i].centerY; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < secondMinDistance) { secondMinDistance = distance; secondNearestRoom = existingRooms[i]; } } } if (secondNearestRoom) { self.createCurvedCorridor(newRoom.centerX, newRoom.centerY, secondNearestRoom.centerX, secondNearestRoom.centerY); } } }; // Create guaranteed exits to adjacent chunks self.createChunkExits = function (offsetX, offsetY, mainRoom) { // Create exits on all four sides of the chunk var midX = offsetX + Math.floor(self.chunkSize / 2); var midY = offsetY + Math.floor(self.chunkSize / 2); // Ensure at least one exit exists to guarantee connectivity between chunks var exitCount = 0; var possibleExits = []; // Check which exits are possible if (offsetX > 0) possibleExits.push('left'); if (offsetX + self.chunkSize < worldGrid.width) possibleExits.push('right'); if (offsetY > 0) possibleExits.push('top'); if (offsetY + self.chunkSize < worldGrid.height) possibleExits.push('bottom'); // Guarantee at least one exit if possible if (possibleExits.length > 0) { var guaranteedExit = possibleExits[Math.floor(Math.random() * possibleExits.length)]; if (guaranteedExit === 'left') { self.createCorridor(mainRoom.centerX, mainRoom.centerY, offsetX, midY); exitCount++; } else if (guaranteedExit === 'right') { self.createCorridor(mainRoom.centerX, mainRoom.centerY, offsetX + self.chunkSize - 1, midY); exitCount++; } else if (guaranteedExit === 'top') { self.createCorridor(mainRoom.centerX, mainRoom.centerY, midX, offsetY); exitCount++; } else if (guaranteedExit === 'bottom') { self.createCorridor(mainRoom.centerX, mainRoom.centerY, midX, offsetY + self.chunkSize - 1); exitCount++; } } // Left exit with 30% probability (if not already created) if (offsetX > 0 && Math.random() < 0.3) { self.createCorridor(mainRoom.centerX, mainRoom.centerY, offsetX, midY); // Ensure the exit extends into the adjacent chunk area for (var i = 0; i < self.hallwayWidth; i++) { var exitX = offsetX - 1; var exitY = midY + i - Math.floor(self.hallwayWidth / 2); if (exitX >= 0 && exitY >= 0 && exitY < worldGrid.height && worldGrid.walls[exitX]) { worldGrid.walls[exitX][exitY] = false; } } } // Right exit with 30% probability (if not already created) if (offsetX + self.chunkSize < worldGrid.width && Math.random() < 0.3) { self.createCorridor(mainRoom.centerX, mainRoom.centerY, offsetX + self.chunkSize - 1, midY); // Ensure the exit extends into the adjacent chunk area for (var i = 0; i < self.hallwayWidth; i++) { var exitX = offsetX + self.chunkSize; var exitY = midY + i - Math.floor(self.hallwayWidth / 2); if (exitX < worldGrid.width && exitY >= 0 && exitY < worldGrid.height && worldGrid.walls[exitX]) { worldGrid.walls[exitX][exitY] = false; } } } // Top exit with 30% probability (if not already created) if (offsetY > 0 && Math.random() < 0.3) { self.createCorridor(mainRoom.centerX, mainRoom.centerY, midX, offsetY); // Ensure the exit extends into the adjacent chunk area for (var i = 0; i < self.hallwayWidth; i++) { var exitX = midX + i - Math.floor(self.hallwayWidth / 2); var exitY = offsetY - 1; if (exitY >= 0 && exitX >= 0 && exitX < worldGrid.width && worldGrid.walls[exitX]) { worldGrid.walls[exitX][exitY] = false; } } } // Bottom exit with 30% probability (if not already created) if (offsetY + self.chunkSize < worldGrid.height && Math.random() < 0.3) { self.createCorridor(mainRoom.centerX, mainRoom.centerY, midX, offsetY + self.chunkSize - 1); // Ensure the exit extends into the adjacent chunk area for (var i = 0; i < self.hallwayWidth; i++) { var exitX = midX + i - Math.floor(self.hallwayWidth / 2); var exitY = offsetY + self.chunkSize; if (exitY < worldGrid.height && exitX >= 0 && exitX < worldGrid.width && worldGrid.walls[exitX]) { worldGrid.walls[exitX][exitY] = false; } } } }; // Create L-shaped corridor between two points with much shorter segments self.createCorridor = function (x1, y1, x2, y2) { // Create narrower corridors for shorter navigation paths var halfWidth = Math.floor(self.hallwayWidth / 2); // Calculate distance and limit corridor length to make them much shorter var dx = Math.abs(x2 - x1); var dy = Math.abs(y2 - y1); var maxCorridorLength = 3; // Maximum corridor segment length // If distance is too long, create intermediate points for shorter segments if (dx > maxCorridorLength || dy > maxCorridorLength) { var midX = x1 + Math.sign(x2 - x1) * Math.min(maxCorridorLength, dx); var midY = y1 + Math.sign(y2 - y1) * Math.min(maxCorridorLength, dy); // Create first short segment self.createShortSegment(x1, y1, midX, y1, halfWidth); // Create second short segment self.createShortSegment(midX, y1, midX, midY, halfWidth); // If we haven't reached the destination, create final segment if (midX !== x2 || midY !== y2) { self.createShortSegment(midX, midY, x2, y2, halfWidth); } } else { // Choose random direction for short L-shaped corridor if (Math.random() < 0.5) { // Horizontal first, then vertical - but keep it short self.createShortSegment(x1, y1, x2, y1, halfWidth); self.createShortSegment(x2, y1, x2, y2, halfWidth); } else { // Vertical first, then horizontal - but keep it short self.createShortSegment(x1, y1, x1, y2, halfWidth); self.createShortSegment(x1, y2, x2, y2, halfWidth); } } }; // Create a short corridor segment with limited length self.createShortSegment = function (x1, y1, x2, y2, halfWidth) { var startX = Math.min(x1, x2); var endX = Math.max(x1, x2); var startY = Math.min(y1, y2); var endY = Math.max(y1, y2); // Limit segment length to make corridors much shorter var maxSegmentLength = 2; endX = Math.min(endX, startX + maxSegmentLength); endY = Math.min(endY, startY + maxSegmentLength); for (var x = startX; x <= endX; x++) { for (var y = startY; y <= endY; y++) { for (var w = -halfWidth; w <= halfWidth; w++) { var corridorX = x + (x1 === x2 ? w : 0); var corridorY = y + (y1 === y2 ? w : 0); if (corridorX >= 0 && corridorX < worldGrid.width && corridorY >= 0 && corridorY < worldGrid.height) { worldGrid.walls[corridorX][corridorY] = false; } } } } }; // Create curved and winding corridor between two points self.createCurvedCorridor = function (x1, y1, x2, y2) { var corridorType = Math.random(); var halfWidth = Math.floor(self.hallwayWidth / 2); if (corridorType < 0.3) { // S-shaped curve var midX = Math.floor((x1 + x2) / 2) + Math.floor(Math.random() * 4) - 2; var midY = Math.floor((y1 + y2) / 2) + Math.floor(Math.random() * 4) - 2; self.createSmoothPath(x1, y1, midX, midY); self.createSmoothPath(midX, midY, x2, y2); } else if (corridorType < 0.6) { // Zigzag corridor var steps = Math.floor(Math.abs(x2 - x1) + Math.abs(y2 - y1)) / 3; var currentX = x1; var currentY = y1; var stepX = (x2 - x1) / steps; var stepY = (y2 - y1) / steps; for (var i = 0; i < steps; i++) { var nextX = Math.floor(currentX + stepX + Math.random() * 2 - 1); var nextY = Math.floor(currentY + stepY + Math.random() * 2 - 1); self.createSmoothPath(Math.floor(currentX), Math.floor(currentY), nextX, nextY); currentX = nextX; currentY = nextY; } self.createSmoothPath(Math.floor(currentX), Math.floor(currentY), x2, y2); } else { // Wide curved path with multiple control points var controlPoints = []; controlPoints.push({ x: x1, y: y1 }); // Add 1-2 random control points var numControls = Math.floor(Math.random() * 2) + 1; for (var i = 0; i < numControls; i++) { var t = (i + 1) / (numControls + 1); var controlX = Math.floor(x1 + (x2 - x1) * t + Math.random() * 6 - 3); var controlY = Math.floor(y1 + (y2 - y1) * t + Math.random() * 6 - 3); controlPoints.push({ x: controlX, y: controlY }); } controlPoints.push({ x: x2, y: y2 }); // Connect all control points for (var i = 0; i < controlPoints.length - 1; i++) { self.createSmoothPath(controlPoints[i].x, controlPoints[i].y, controlPoints[i + 1].x, controlPoints[i + 1].y); } } }; // Create smooth path between two points with width - much shorter segments self.createSmoothPath = function (x1, y1, x2, y2) { var dx = x2 - x1; var dy = y2 - y1; var distance = Math.sqrt(dx * dx + dy * dy); // Limit path length to make corridors much shorter var maxPathLength = 4; if (distance > maxPathLength) { // Create shorter intermediate path var ratio = maxPathLength / distance; x2 = Math.floor(x1 + dx * ratio); y2 = Math.floor(y1 + dy * ratio); dx = x2 - x1; dy = y2 - y1; distance = maxPathLength; } var steps = Math.min(Math.floor(distance) + 1, 3); // Limit steps for shorter paths var halfWidth = Math.floor(self.hallwayWidth / 2); for (var i = 0; i <= steps; i++) { var t = i / steps; var x = Math.floor(x1 + dx * t); var y = Math.floor(y1 + dy * t); // Carve corridor with width for (var w = -halfWidth; w <= halfWidth; w++) { for (var h = -halfWidth; h <= halfWidth; h++) { var corridorX = x + w; var corridorY = y + h; if (corridorX >= 0 && corridorX < worldGrid.width && corridorY >= 0 && corridorY < worldGrid.height) { worldGrid.walls[corridorX][corridorY] = false; } } } } }; // Add random pillars for atmosphere self.addRandomPillars = function (offsetX, offsetY) { for (var x = offsetX + 2; x < offsetX + self.chunkSize - 2; x++) { for (var y = offsetY + 2; y < offsetY + self.chunkSize - 2; y++) { if (x >= 0 && x < worldGrid.width && y >= 0 && y < worldGrid.height) { // Only add pillars in open areas with low probability if (!worldGrid.walls[x][y] && Math.random() < 0.008) { // Check if surrounded by enough open space (3x3 area) var canPlace = true; for (var dx = -1; dx <= 1; dx++) { for (var dy = -1; dy <= 1; dy++) { var checkX = x + dx; var checkY = y + dy; if (checkX >= 0 && checkX < worldGrid.width && checkY >= 0 && checkY < worldGrid.height) { if (worldGrid.walls[checkX][checkY]) { canPlace = false; break; } } } if (!canPlace) break; } if (canPlace) { worldGrid.walls[x][y] = true; } } } } } }; // Get room size based on probability distribution // Small rooms: 50%, Medium rooms: 35%, Large rooms: 15% self.getRoomSizeByProbability = function () { var random = Math.random() * 100; if (random < 50) { // Small rooms (50% probability) - 1x1 to 2x2 var size = Math.floor(Math.random() * 2) + 1; return { width: size, height: size }; } else if (random < 85) { // Medium rooms (35% probability) - 3x3 to 4x4 var size = Math.floor(Math.random() * 2) + 3; return { width: size, height: size }; } else { // Large rooms (15% probability) - 5x5 to 6x6 var size = Math.floor(Math.random() * 2) + 5; return { width: size, height: size }; } }; // Generate chunks around player position self.generateAroundPlayer = function (playerX, playerY) { var playerChunkX = Math.floor(playerX / (worldGrid.cellSize * self.chunkSize)); var playerChunkY = Math.floor(playerY / (worldGrid.cellSize * self.chunkSize)); // Generate chunks in a 3x3 area around player for (var dx = -1; dx <= 1; dx++) { for (var dy = -1; dy <= 1; dy++) { var chunkX = playerChunkX + dx; var chunkY = playerChunkY + dy; self.generateChunk(chunkX, chunkY); } } }; return self; }); var RaycastRenderer = Container.expand(function () { var self = Container.call(this); self.screenWidth = 2732; self.screenHeight = 2048; self.numRays = 128; // Number of rays to cast self.wallColumns = []; self.floorColumns = []; self.ceilingColumns = []; // Initialize rendering columns for (var i = 0; i < self.numRays; i++) { var stripWidth = self.screenWidth / self.numRays; // Wall column var wallCol = self.addChild(LK.getAsset('wallSegment', { anchorX: 0.5, anchorY: 0.5 })); wallCol.x = i * stripWidth + stripWidth / 2; wallCol.y = self.screenHeight / 2; wallCol.width = stripWidth + 1; // Small overlap to prevent gaps wallCol.visible = false; self.wallColumns.push(wallCol); // Floor column var floorCol = self.addChild(LK.getAsset('floorStrip', { anchorX: 0.5, anchorY: 0 })); floorCol.x = i * stripWidth + stripWidth / 2; floorCol.width = stripWidth + 1; floorCol.visible = false; self.floorColumns.push(floorCol); // Ceiling column var ceilCol = self.addChild(LK.getAsset('ceilingStrip', { anchorX: 0.5, anchorY: 1 })); ceilCol.x = i * stripWidth + stripWidth / 2; ceilCol.width = stripWidth + 1; ceilCol.visible = false; self.ceilingColumns.push(ceilCol); } self.render = function (player) { var fov = Math.PI / 2; // 90 degrees field of view for classic raycasting var halfFov = fov / 2; var stripWidth = self.screenWidth / self.numRays; var screenCenter = self.screenHeight / 2; var pitchOffset = player.pitch * 300; // Cast rays across the field of view for (var i = 0; i < self.numRays; i++) { var rayAngle = player.angle - halfFov + i / self.numRays * fov; var rayData = self.castRay(player.x, player.y, rayAngle); var distance = rayData.distance; var wallHeight = 0; var wallCol = self.wallColumns[i]; var floorCol = self.floorColumns[i]; var ceilCol = self.ceilingColumns[i]; // Check if column is within horizontal bounds with tighter constraints var columnX = wallCol.x; var withinBounds = columnX >= -stripWidth / 2 && columnX <= self.screenWidth + stripWidth / 2; if (rayData.hit && withinBounds) { // Fish-eye correction var correctedDistance = distance * Math.cos(rayAngle - player.angle); // Calculate wall height based on distance wallHeight = Math.max(50, worldGrid.cellSize * 800 / (correctedDistance + 1)); // Wall rendering wallCol.height = wallHeight; wallCol.x = Math.max(0, Math.min(self.screenWidth, wallCol.x)); // Clamp X position to screen bounds wallCol.y = screenCenter + pitchOffset; wallCol.visible = true; // Distance-based shading var shadingFactor = Math.max(0.15, 1.0 - correctedDistance / 800); var tintValue = Math.floor(shadingFactor * 255); wallCol.tint = tintValue << 16 | tintValue << 8 | tintValue; // Floor rendering with distance-based shading var wallBottom = screenCenter + wallHeight / 2 + pitchOffset; var floorHeight = self.screenHeight - wallBottom; floorCol.y = wallBottom; floorCol.height = Math.max(1, floorHeight); floorCol.visible = true; // Calculate proper floor distance - distance from player to the floor point being rendered // The floor distance increases as we look further down from the wall base var floorCenter = wallBottom + floorHeight / 2; // Center point of floor strip var floorDistanceFromWall = Math.abs(floorCenter - (screenCenter + pitchOffset)) / 10; // Normalize distance var actualFloorDistance = correctedDistance + floorDistanceFromWall; // Calculate floor shading based on actual floor distance - closer is lighter, farther is darker var floorShadingFactor = Math.max(0.1, Math.min(1.0, 1.2 - actualFloorDistance / 500)); var floorTintValue = Math.floor(floorShadingFactor * 180); // Base floor brightness floorCol.tint = floorTintValue << 16 | floorTintValue << 8 | Math.floor(floorTintValue * 0.7); // Slight yellow tint for floor // Ceiling rendering var ceilHeight = screenCenter - wallHeight / 2 + pitchOffset; ceilCol.y = ceilHeight; ceilCol.height = Math.max(1, ceilHeight); ceilCol.visible = true; ceilCol.tint = 0x333333; } else { // No wall hit or outside bounds - hide columns wallCol.visible = false; floorCol.visible = false; ceilCol.visible = false; } } }; // DDA (Digital Differential Analyzer) raycasting algorithm self.castRay = function (startX, startY, angle) { var rayX = startX; var rayY = startY; var rayDirX = Math.cos(angle); var rayDirY = Math.sin(angle); // Which grid cell we're in var mapX = Math.floor(rayX / worldGrid.cellSize); var mapY = Math.floor(rayY / worldGrid.cellSize); // Length of ray from current position to x or y side var deltaDistX = Math.abs(1 / rayDirX); var deltaDistY = Math.abs(1 / rayDirY); // Calculate step and initial sideDist var stepX, sideDistX; var stepY, sideDistY; if (rayDirX < 0) { stepX = -1; sideDistX = (rayX / worldGrid.cellSize - mapX) * deltaDistX; } else { stepX = 1; sideDistX = (mapX + 1.0 - rayX / worldGrid.cellSize) * deltaDistX; } if (rayDirY < 0) { stepY = -1; sideDistY = (rayY / worldGrid.cellSize - mapY) * deltaDistY; } else { stepY = 1; sideDistY = (mapY + 1.0 - rayY / worldGrid.cellSize) * deltaDistY; } // Perform DDA var hit = false; var side = 0; // 0 if x-side, 1 if y-side var maxSteps = 100; var steps = 0; while (!hit && steps < maxSteps) { steps++; // Jump to next map square, either in x-direction, or in y-direction if (sideDistX < sideDistY) { sideDistX += deltaDistX; mapX += stepX; side = 0; } else { sideDistY += deltaDistY; mapY += stepY; side = 1; } // Check if ray has hit a wall if (worldGrid.hasWallAt(mapX * worldGrid.cellSize, mapY * worldGrid.cellSize)) { hit = true; } } var distance = 0; if (hit) { // Calculate distance if (side === 0) { distance = (mapX - rayX / worldGrid.cellSize + (1 - stepX) / 2) / rayDirX; } else { distance = (mapY - rayY / worldGrid.cellSize + (1 - stepY) / 2) / rayDirY; } distance = Math.abs(distance * worldGrid.cellSize); } return { hit: hit, distance: distance, side: side, mapX: mapX, mapY: mapY }; }; return self; }); var SensitivityConfig = Container.expand(function () { var self = Container.call(this); // Load saved sensitivity or default to 50 self.sensitivity = storage.sensitivity || 50; self.isVisible = false; // Create background panel var background = self.addChild(LK.getAsset('untexturedArea', { anchorX: 0, anchorY: 0, width: 300, height: 200, alpha: 0.8 })); background.tint = 0x222222; // Create title text var titleText = new Text2('Sensitivity', { size: 40, fill: 0xFFFFFF }); titleText.anchor.set(0.5, 0); titleText.x = 150; titleText.y = 20; self.addChild(titleText); // Create sensitivity value text var valueText = new Text2(self.sensitivity.toString(), { size: 35, fill: 0xFFFFFF }); valueText.anchor.set(0.5, 0); valueText.x = 150; valueText.y = 70; self.addChild(valueText); // Create decrease button (larger for mobile) var decreaseBtn = self.addChild(LK.getAsset('untexturedArea', { anchorX: 0.5, anchorY: 0.5, width: 70, height: 60 })); decreaseBtn.x = 80; decreaseBtn.y = 130; decreaseBtn.tint = 0x666666; var decreaseText = new Text2('-', { size: 40, fill: 0xFFFFFF }); decreaseText.anchor.set(0.5, 0.5); decreaseText.x = 80; decreaseText.y = 130; self.addChild(decreaseText); // Create increase button (larger for mobile) var increaseBtn = self.addChild(LK.getAsset('untexturedArea', { anchorX: 0.5, anchorY: 0.5, width: 70, height: 60 })); increaseBtn.x = 220; increaseBtn.y = 130; increaseBtn.tint = 0x666666; var increaseText = new Text2('+', { size: 40, fill: 0xFFFFFF }); increaseText.anchor.set(0.5, 0.5); increaseText.x = 220; increaseText.y = 130; self.addChild(increaseText); // Update sensitivity display self.updateDisplay = function () { valueText.setText(self.sensitivity.toString()); // Save to storage storage.sensitivity = self.sensitivity; }; // Toggle visibility self.toggle = function () { self.isVisible = !self.isVisible; self.visible = self.isVisible; }; // Handle decrease button with visual feedback decreaseBtn.down = function (x, y, obj) { decreaseBtn.tint = 0x888888; // Lighten on press if (self.sensitivity > 0) { self.sensitivity = Math.max(0, self.sensitivity - 5); self.updateDisplay(); } }; decreaseBtn.up = function (x, y, obj) { decreaseBtn.tint = 0x666666; // Reset color on release }; // Handle increase button with visual feedback increaseBtn.down = function (x, y, obj) { increaseBtn.tint = 0x888888; // Lighten on press if (self.sensitivity < 100) { self.sensitivity = Math.min(100, self.sensitivity + 5); self.updateDisplay(); } }; increaseBtn.up = function (x, y, obj) { increaseBtn.tint = 0x666666; // Reset color on release }; // Add background click handler to prevent game interactions background.down = function (x, y, obj) { // Prevent event from bubbling to game return true; }; background.up = function (x, y, obj) { // Prevent event from bubbling to game return true; }; background.move = function (x, y, obj) { // Prevent event from bubbling to game return true; }; // Initially hidden self.visible = false; return self; }); /**** * Initialize Game ****/ // Create player var game = new LK.Game({ backgroundColor: 0x000000, orientation: 'landscape' }); /**** * Game Code ****/ // World coordinate system - grid-based layout with procedural generation var worldGrid = { cellSize: 200, width: 100, // Expanded world size for infinite generation height: 100, walls: [], // Will store wall positions // Initialize world grid with walls initializeGrid: function initializeGrid() { // Initialize walls array first - fill entire world with walls initially this.walls = []; for (var x = 0; x < this.width; x++) { this.walls[x] = []; for (var y = 0; y < this.height; y++) { // Start with all walls - procedural generation will carve out spaces this.walls[x][y] = true; } } // Create a starting room around spawn point (3x3 room) var spawnX = Math.floor(this.width / 2); var spawnY = Math.floor(this.height / 2); for (var x = spawnX - 1; x <= spawnX + 1; x++) { for (var y = spawnY - 1; y <= spawnY + 1; y++) { if (x >= 0 && x < this.width && y >= 0 && y < this.height) { this.walls[x][y] = false; } } } }, // Check if a world position has a wall hasWallAt: function hasWallAt(worldX, worldY) { var gridX = Math.floor(worldX / this.cellSize); var gridY = Math.floor(worldY / this.cellSize); if (gridX < 0 || gridX >= this.width || gridY < 0 || gridY >= this.height) { return true; // Outside bounds = wall } return this.walls[gridX][gridY]; }, // Check collision with wall boundaries (with player radius) - optimized checkCollision: function checkCollision(worldX, worldY, radius) { radius = radius || 20; // Default player radius var gridX = Math.floor(worldX / this.cellSize); var gridY = Math.floor(worldY / this.cellSize); // Quick bounds check first if (gridX < 1 || gridX >= this.width - 1 || gridY < 1 || gridY >= this.height - 1) { return true; // Near or outside bounds = collision } // Optimized collision check - only check center and edges that matter for movement var checkPoints = [ // Center point { x: worldX, y: worldY }, // Movement-relevant edges { x: worldX - radius, y: worldY }, // Left edge { x: worldX + radius, y: worldY }, // Right edge { x: worldX, y: worldY - radius }, // Top edge { x: worldX, y: worldY + radius } // Bottom edge ]; for (var i = 0; i < checkPoints.length; i++) { var point = checkPoints[i]; var pointGridX = Math.floor(point.x / this.cellSize); var pointGridY = Math.floor(point.y / this.cellSize); // Quick bounds check if (pointGridX < 0 || pointGridX >= this.width || pointGridY < 0 || pointGridY >= this.height) { return true; } // Check wall collision with early exit if (this.walls[pointGridX][pointGridY]) { return true; } } return false; }, // Convert screen coordinates to world coordinates screenToWorld: function screenToWorld(screenX, screenY) { return { x: screenX, y: screenY }; }, // Convert world coordinates to screen coordinates worldToScreen: function worldToScreen(worldX, worldY) { return { x: worldX, y: worldY }; } }; // Initialize the world grid worldGrid.initializeGrid(); // Create procedural generator var procGen = new ProcGen(); // Generate initial chunks around spawn point procGen.generateAroundPlayer(worldGrid.width * worldGrid.cellSize / 2, worldGrid.height * worldGrid.cellSize / 2); // Add wall line completion system worldGrid.completeWallLines = function () { // Trace horizontal lines and complete them for (var y = 0; y < this.height; y++) { var wallStart = -1; var wallEnd = -1; // Find wall segments in this row for (var x = 0; x < this.width; x++) { if (this.walls[x][y]) { if (wallStart === -1) { wallStart = x; // Start of wall segment } wallEnd = x; // Update end of wall segment } else { // If we found a wall segment, complete the line between start and end if (wallStart !== -1 && wallEnd !== -1 && wallEnd > wallStart) { for (var fillX = wallStart; fillX <= wallEnd; fillX++) { this.walls[fillX][y] = true; // Fill the gap } } wallStart = -1; // Reset for next segment wallEnd = -1; } } // Complete any remaining segment at end of row if (wallStart !== -1 && wallEnd !== -1 && wallEnd > wallStart) { for (var fillX = wallStart; fillX <= wallEnd; fillX++) { this.walls[fillX][y] = true; } } } // Trace vertical lines and complete them for (var x = 0; x < this.width; x++) { var wallStart = -1; var wallEnd = -1; // Find wall segments in this column for (var y = 0; y < this.height; y++) { if (this.walls[x][y]) { if (wallStart === -1) { wallStart = y; // Start of wall segment } wallEnd = y; // Update end of wall segment } else { // If we found a wall segment, complete the line between start and end if (wallStart !== -1 && wallEnd !== -1 && wallEnd > wallStart) { for (var fillY = wallStart; fillY <= wallEnd; fillY++) { this.walls[x][fillY] = true; // Fill the gap } } wallStart = -1; // Reset for next segment wallEnd = -1; } } // Complete any remaining segment at end of column if (wallStart !== -1 && wallEnd !== -1 && wallEnd > wallStart) { for (var fillY = wallStart; fillY <= wallEnd; fillY++) { this.walls[x][fillY] = true; } } } }; // Apply wall line completion after initial generation worldGrid.completeWallLines(); // Add dead-end detection system worldGrid.detectAndFillDeadEnds = function () { // Find all areas without exits and mark them as walls var visited = []; // Initialize visited array for (var x = 0; x < this.width; x++) { visited[x] = []; for (var y = 0; y < this.height; y++) { visited[x][y] = false; } } // Function to check if a position has at least one exit var hasExit = function hasExit(startX, startY) { if (worldGrid.walls[startX][startY]) return false; // Wall positions don't need exits var localVisited = []; for (var x = 0; x < worldGrid.width; x++) { localVisited[x] = []; for (var y = 0; y < worldGrid.height; y++) { localVisited[x][y] = false; } } var area = []; var stack = [{ x: startX, y: startY }]; // Flood fill to find connected area while (stack.length > 0) { var current = stack.pop(); var x = current.x; var y = current.y; if (x < 0 || x >= worldGrid.width || y < 0 || y >= worldGrid.height) continue; if (localVisited[x][y] || worldGrid.walls[x][y]) continue; localVisited[x][y] = true; area.push({ x: x, y: y }); // Add adjacent cells stack.push({ x: x + 1, y: y }); stack.push({ x: x - 1, y: y }); stack.push({ x: x, y: y + 1 }); stack.push({ x: x, y: y - 1 }); } // Check if any cell in the area has an exit (connection to other areas) for (var i = 0; i < area.length; i++) { var cell = area[i]; var directions = [{ dx: 1, dy: 0 }, { dx: -1, dy: 0 }, { dx: 0, dy: 1 }, { dx: 0, dy: -1 }]; for (var d = 0; d < directions.length; d++) { var checkX = cell.x + directions[d].dx; var checkY = cell.y + directions[d].dy; // Check if this leads to a different open area or chunk boundary if (checkX < 0 || checkX >= worldGrid.width || checkY < 0 || checkY >= worldGrid.height) { return true; // Exit to world boundary } if (!worldGrid.walls[checkX][checkY] && !localVisited[checkX][checkY]) { // This connects to another area, so it has an exit return true; } } } // No exit found, mark entire area as walls for (var i = 0; i < area.length; i++) { worldGrid.walls[area[i].x][area[i].y] = true; } return false; }; // Check all open areas for exits for (var x = 0; x < this.width; x++) { for (var y = 0; y < this.height; y++) { if (!visited[x][y] && !this.walls[x][y]) { visited[x][y] = true; hasExit(x, y); } } } }; // Apply dead-end detection after wall completion worldGrid.detectAndFillDeadEnds(); // Create geometric wall renderer var wallRenderer = new GeometricWallRenderer(); game.addChild(wallRenderer); // Create ceiling tile renderer var ceilingTileRenderer = new CeilingTileRenderer(); game.addChild(ceilingTileRenderer); ceilingTileRenderer.generateTiles(); // Function to find a safe spawn position function findSafeSpawnPosition(startX, startY, searchRadius) { searchRadius = searchRadius || 5; // First check if starting position is safe if (!worldGrid.checkCollision(startX, startY)) { return { x: startX, y: startY }; } // Search in expanding circles for a safe position for (var radius = 1; radius <= searchRadius; radius++) { for (var angle = 0; angle < Math.PI * 2; angle += Math.PI / 8) { var testX = startX + Math.cos(angle) * radius * worldGrid.cellSize; var testY = startY + Math.sin(angle) * radius * worldGrid.cellSize; // Check bounds if (testX >= worldGrid.cellSize && testX < (worldGrid.width - 1) * worldGrid.cellSize && testY >= worldGrid.cellSize && testY < (worldGrid.height - 1) * worldGrid.cellSize) { if (!worldGrid.checkCollision(testX, testY)) { return { x: testX, y: testY }; } } } } // If no safe position found in search radius, force create one var fallbackX = Math.floor(worldGrid.width / 2) * worldGrid.cellSize; var fallbackY = Math.floor(worldGrid.height / 2) * worldGrid.cellSize; // Clear a 3x3 area around fallback position var fallbackGridX = Math.floor(fallbackX / worldGrid.cellSize); var fallbackGridY = Math.floor(fallbackY / worldGrid.cellSize); for (var dx = -1; dx <= 1; dx++) { for (var dy = -1; dy <= 1; dy++) { var clearX = fallbackGridX + dx; var clearY = fallbackGridY + dy; if (clearX >= 0 && clearX < worldGrid.width && clearY >= 0 && clearY < worldGrid.height) { worldGrid.walls[clearX][clearY] = false; } } } return { x: fallbackX, y: fallbackY }; } // Create player var player = new Player(); // Find safe spawn position var spawnCenter = { x: worldGrid.width * worldGrid.cellSize / 2, y: worldGrid.height * worldGrid.cellSize / 2 }; var safeSpawn = findSafeSpawnPosition(spawnCenter.x, spawnCenter.y, 10); // Position player at safe spawn location player.x = safeSpawn.x; player.y = safeSpawn.y; player.targetX = safeSpawn.x; player.targetY = safeSpawn.y; game.addChild(player); // Create raycasting renderer var raycastRenderer = new RaycastRenderer(); game.addChild(raycastRenderer); // FPS counter variables var fpsCounter = 0; var fpsDisplay = 0; var lastFpsTime = Date.now(); // Create coordinate display text var coordXText = new Text2('X: 0', { size: 60, fill: 0xFFFFFF }); coordXText.anchor.set(0, 0); coordXText.x = 120; // Avoid top-left 100x100 area coordXText.y = 120; LK.gui.addChild(coordXText); var coordZText = new Text2('Z: 0', { size: 60, fill: 0xFFFFFF }); coordZText.anchor.set(0, 0); coordZText.x = 120; // Avoid top-left 100x100 area coordZText.y = 200; LK.gui.addChild(coordZText); // Create FPS display text var fpsText = new Text2('FPS: 60', { size: 60, fill: 0x00FF00 }); fpsText.anchor.set(0, 0); fpsText.x = 120; // Avoid top-left 100x100 area fpsText.y = 280; LK.gui.addChild(fpsText); // Create settings button in top-right corner (larger for mobile) var settingsButton = LK.getAsset('untexturedArea', { anchorX: 1, anchorY: 0, width: 120, height: 120 }); settingsButton.tint = 0x444444; settingsButton.alpha = 0.7; LK.gui.topRight.addChild(settingsButton); var settingsText = new Text2('⚙', { size: 60, fill: 0xFFFFFF }); settingsText.anchor.set(0.5, 0.5); settingsText.x = -60; settingsText.y = 60; LK.gui.topRight.addChild(settingsText); // Create sensitivity configuration panel var sensitivityConfig = new SensitivityConfig(); sensitivityConfig.x = 2732 - 320; sensitivityConfig.y = 100; LK.gui.addChild(sensitivityConfig); // Create movement crosshair for better mobile controls var movementCrosshair = new MovementCrosshair(); movementCrosshair.x = 200; // Position on left side movementCrosshair.y = 2048 - 200; // Bottom left area LK.gui.addChild(movementCrosshair); // Movement flags var moveForward = false; var moveBackward = false; var turnLeft = false; var turnRight = false; var lookUp = false; var lookDown = false; // Touch controls for movement var touchStartX = 0; var touchStartY = 0; var touchActive = false; // Settings button click handler settingsButton.down = function (x, y, obj) { sensitivityConfig.toggle(); }; game.down = function (x, y, obj) { touchStartX = x; touchStartY = y; touchActive = true; // Forward movement on touch moveForward = true; }; game.up = function (x, y, obj) { touchActive = false; moveForward = false; moveBackward = false; turnLeft = false; turnRight = false; lookUp = false; lookDown = false; // Reset crosshair if not actively being used if (!movementCrosshair.activeButton) { movementCrosshair.resetMovement(); } }; game.move = function (x, y, obj) { if (!touchActive) return; var deltaX = x - touchStartX; var deltaY = y - touchStartY; // Horizontal movement for turning if (Math.abs(deltaX) > 50) { if (deltaX > 0) { turnRight = true; turnLeft = false; } else { turnLeft = true; turnRight = false; } } else { turnLeft = false; turnRight = false; } // Vertical movement - split between forward/backward and look up/down if (Math.abs(deltaY) > 50) { // If touch is in upper part of screen, use for looking up/down if (y < 1024) { // Upper half of screen for vertical look if (deltaY < 0) { lookUp = true; lookDown = false; } else { lookDown = true; lookUp = false; } moveForward = false; moveBackward = false; } else { // Lower half of screen for movement if (deltaY < 0) { moveForward = true; moveBackward = false; } else { moveBackward = true; moveForward = false; } lookUp = false; lookDown = false; } } else { lookUp = false; lookDown = false; } }; game.update = function () { // Update player rotation speed based on sensitivity (0-100 maps to 0.05-0.2) var sensitivityValue = sensitivityConfig.sensitivity; player.rotSpeed = 0.05 + sensitivityValue / 100 * 0.15; // Get movement state from crosshair var crosshairState = movementCrosshair.getMovementState(); // Handle movement (combine touch controls and crosshair) if (moveForward || crosshairState.forward) { player.moveForward(); } if (moveBackward || crosshairState.backward) { player.moveBackward(); } if (turnLeft || crosshairState.left) { player.turnLeft(); } if (turnRight || crosshairState.right) { player.turnRight(); } if (lookUp) { player.lookUp(); } if (lookDown) { player.lookDown(); } // Apply smooth interpolation player.updateSmooth(); // Generate new chunks as player moves if (LK.ticks % 30 === 0) { // Check every 30 frames for performance procGen.generateAroundPlayer(player.x, player.y); } // Render the raycasted view raycastRenderer.render(player); // Render walls wallRenderer.render(player); // Render ceiling tiles ceilingTileRenderer.render(player); // Update FPS counter fpsCounter++; var currentTime = Date.now(); if (currentTime - lastFpsTime >= 1000) { // Update every second fpsDisplay = fpsCounter; fpsCounter = 0; lastFpsTime = currentTime; // Color code FPS display based on performance var fpsColor = 0x00FF00; // Green for good FPS (60+) if (fpsDisplay < 30) { fpsColor = 0xFF0000; // Red for poor FPS } else if (fpsDisplay < 50) { fpsColor = 0xFFFF00; // Yellow for moderate FPS } fpsText.fill = fpsColor; fpsText.setText('FPS: ' + fpsDisplay); } // Update coordinate display var gridX = Math.floor(player.x / worldGrid.cellSize); var gridZ = Math.floor(player.y / worldGrid.cellSize); coordXText.setText('X: ' + gridX); coordZText.setText('Z: ' + gridZ); };
===================================================================
--- original.js
+++ change.js
@@ -1424,8 +1424,111 @@
}
};
// Apply wall line completion after initial generation
worldGrid.completeWallLines();
+// Add dead-end detection system
+worldGrid.detectAndFillDeadEnds = function () {
+ // Find all areas without exits and mark them as walls
+ var visited = [];
+ // Initialize visited array
+ for (var x = 0; x < this.width; x++) {
+ visited[x] = [];
+ for (var y = 0; y < this.height; y++) {
+ visited[x][y] = false;
+ }
+ }
+ // Function to check if a position has at least one exit
+ var hasExit = function hasExit(startX, startY) {
+ if (worldGrid.walls[startX][startY]) return false; // Wall positions don't need exits
+ var localVisited = [];
+ for (var x = 0; x < worldGrid.width; x++) {
+ localVisited[x] = [];
+ for (var y = 0; y < worldGrid.height; y++) {
+ localVisited[x][y] = false;
+ }
+ }
+ var area = [];
+ var stack = [{
+ x: startX,
+ y: startY
+ }];
+ // Flood fill to find connected area
+ while (stack.length > 0) {
+ var current = stack.pop();
+ var x = current.x;
+ var y = current.y;
+ if (x < 0 || x >= worldGrid.width || y < 0 || y >= worldGrid.height) continue;
+ if (localVisited[x][y] || worldGrid.walls[x][y]) continue;
+ localVisited[x][y] = true;
+ area.push({
+ x: x,
+ y: y
+ });
+ // Add adjacent cells
+ stack.push({
+ x: x + 1,
+ y: y
+ });
+ stack.push({
+ x: x - 1,
+ y: y
+ });
+ stack.push({
+ x: x,
+ y: y + 1
+ });
+ stack.push({
+ x: x,
+ y: y - 1
+ });
+ }
+ // Check if any cell in the area has an exit (connection to other areas)
+ for (var i = 0; i < area.length; i++) {
+ var cell = area[i];
+ var directions = [{
+ dx: 1,
+ dy: 0
+ }, {
+ dx: -1,
+ dy: 0
+ }, {
+ dx: 0,
+ dy: 1
+ }, {
+ dx: 0,
+ dy: -1
+ }];
+ for (var d = 0; d < directions.length; d++) {
+ var checkX = cell.x + directions[d].dx;
+ var checkY = cell.y + directions[d].dy;
+ // Check if this leads to a different open area or chunk boundary
+ if (checkX < 0 || checkX >= worldGrid.width || checkY < 0 || checkY >= worldGrid.height) {
+ return true; // Exit to world boundary
+ }
+ if (!worldGrid.walls[checkX][checkY] && !localVisited[checkX][checkY]) {
+ // This connects to another area, so it has an exit
+ return true;
+ }
+ }
+ }
+ // No exit found, mark entire area as walls
+ for (var i = 0; i < area.length; i++) {
+ worldGrid.walls[area[i].x][area[i].y] = true;
+ }
+ return false;
+ };
+ // Check all open areas for exits
+ for (var x = 0; x < this.width; x++) {
+ for (var y = 0; y < this.height; y++) {
+ if (!visited[x][y] && !this.walls[x][y]) {
+ visited[x][y] = true;
+ hasExit(x, y);
+ }
+ }
+ }
+};
+// Apply dead-end detection after wall completion
+worldGrid.detectAndFillDeadEnds();
// Create geometric wall renderer
var wallRenderer = new GeometricWallRenderer();
game.addChild(wallRenderer);
// Create ceiling tile renderer