User prompt
Please fix the bug: 'TypeError: undefined is not an object (evaluating 'map[cellY][cellX]')' in or related to this line: 'if (cellX < 0 || cellX >= MAP_SIZE || cellY < 0 || cellY >= MAP_SIZE) {' Line Number: 763
Code edit (1 edits merged)
Please save this source code
User prompt
RayCaster Dungeon Crawler
Initial prompt
3d ray casting
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1"); /**** * Classes ****/ var ControlButton = Container.expand(function (direction) { var self = Container.call(this); var buttonSprite = self.attachAsset('controlButton', { anchorX: 0.5, anchorY: 0.5 }); var arrowSprite = self.attachAsset('buttonArrow', { anchorX: 0.5, anchorY: 0.5 }); // Set arrow direction based on button type if (direction === 'up') { arrowSprite.rotation = 0; } else if (direction === 'right') { arrowSprite.rotation = Math.PI / 2; } else if (direction === 'down') { arrowSprite.rotation = Math.PI; } else if (direction === 'left') { arrowSprite.rotation = Math.PI * 1.5; } else if (direction === 'attack') { arrowSprite.visible = false; buttonSprite = self.attachAsset('attackButton', { anchorX: 0.5, anchorY: 0.5 }); } self.direction = direction; self.pressed = false; self.down = function (x, y, obj) { self.pressed = true; buttonSprite.alpha = 0.7; }; self.up = function (x, y, obj) { self.pressed = false; buttonSprite.alpha = 1; }; return self; }); var MapCell = Container.expand(function () { var self = Container.call(this); self.type = 0; // 0 = floor, 1 = wall self.monster = null; self.treasure = null; self.setType = function (type) { self.type = type; self.updateVisual(); }; self.updateVisual = function () { self.removeChildren(); if (self.type === 1) { self.attachAsset('mapWall', { anchorX: 0, anchorY: 0 }); } else { self.attachAsset('mapFloor', { anchorX: 0, anchorY: 0 }); } }; self.addMonster = function () { if (self.type === 0 && !self.monster && !self.treasure) { self.monster = true; return true; } return false; }; self.addTreasure = function () { if (self.type === 0 && !self.monster && !self.treasure) { self.treasure = true; return true; } return false; }; self.removeMonster = function () { self.monster = null; }; self.removeTreasure = function () { self.treasure = null; }; return self; }); var Monster = Container.expand(function () { var self = Container.call(this); var monsterSprite = self.attachAsset('monster', { anchorX: 0.5, anchorY: 0.5 }); self.mapX = 0; self.mapY = 0; self.health = 3; self.takeDamage = function () { self.health -= 1; LK.getSound('hit').play(); // Visual feedback for hit tween(monsterSprite, { alpha: 0.2 }, { duration: 100, onFinish: function onFinish() { tween(monsterSprite, { alpha: 1 }, { duration: 100 }); } }); return self.health <= 0; }; return self; }); var Projectile = Container.expand(function () { var self = Container.call(this); var projectileSprite = self.attachAsset('projectile', { anchorX: 0.5, anchorY: 0.5 }); self.dirX = 0; self.dirY = 0; self.speed = 0.2; self.range = 5; self.distanceTraveled = 0; self.update = function (deltaTime) { // Move projectile var moveX = self.dirX * self.speed * deltaTime; var moveY = self.dirY * self.speed * deltaTime; self.x += moveX; self.y += moveY; self.distanceTraveled += Math.sqrt(moveX * moveX + moveY * moveY); return self.distanceTraveled >= self.range; }; return self; }); var RaycastStrip = Container.expand(function () { var self = Container.call(this); var wall = self.attachAsset('wall', { anchorX: 0, anchorY: 0 }); var ceiling = self.attachAsset('ceiling', { anchorX: 0, anchorY: 0 }); var floor = self.attachAsset('floor', { anchorX: 0, anchorY: 0 }); self.updateStrip = function (stripWidth, wallHeight, stripIdx, wallType, distance) { // Wall setup wall.width = stripWidth; wall.height = wallHeight; wall.y = (2732 - wallHeight) / 2; // Ceiling setup ceiling.width = stripWidth; ceiling.height = wall.y; ceiling.y = 0; // Floor setup floor.width = stripWidth; floor.height = ceiling.height; floor.y = wall.y + wallHeight; // Adjust positions self.x = stripIdx * stripWidth; // Add distance shading effect var shade = Math.max(0.3, 1 - distance / 10); wall.alpha = shade; ceiling.alpha = shade * 0.7; floor.alpha = shade * 0.8; }; return self; }); var Treasure = Container.expand(function () { var self = Container.call(this); var treasureSprite = self.attachAsset('treasure', { anchorX: 0.5, anchorY: 0.5 }); self.mapX = 0; self.mapY = 0; self.value = 1; // Animate the treasure to make it more appealing var _animateTreasure = function animateTreasure() { tween(treasureSprite, { rotation: Math.PI * 2 }, { duration: 2000, onFinish: function onFinish() { treasureSprite.rotation = 0; _animateTreasure(); } }); }; _animateTreasure(); return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x111111 }); /**** * Game Code ****/ // Game constants var MAP_SIZE = 16; var CELL_SIZE = 20; var MINI_MAP_SCALE = 1; var STRIP_WIDTH = 8; var NUM_RAYS = Math.ceil(2048 / STRIP_WIDTH); var FOV = Math.PI / 3; // 60 degrees field of view var HALF_FOV = FOV / 2; var PLAYER_MOVE_SPEED = 0.05; var PLAYER_TURN_SPEED = 0.04; var WALL_HEIGHT_FACTOR = 600; var MAX_RENDER_DISTANCE = 16; var MONSTER_COUNT = 5; var TREASURE_COUNT = 10; // Game state var map = []; var player = { x: 1.5, y: 1.5, dir: 0, health: 5, score: 0, level: storage.level || 1 }; var controls = { forward: false, backward: false, left: false, right: false, attack: false }; var monsters = []; var treasures = []; var projectiles = []; var lastTime = Date.now(); // UI elements var miniMap; var rayCastView; var healthText; var scoreText; var levelText; var controlButtons = {}; var playerMarker; // Setup game function setupGame() { // Create the rayCast view container rayCastView = new Container(); game.addChild(rayCastView); // Create raycast strips for (var i = 0; i < NUM_RAYS; i++) { var strip = new RaycastStrip(); rayCastView.addChild(strip); } // Create minimap container miniMap = new Container(); miniMap.x = 20; miniMap.y = 20; game.addChild(miniMap); // Generate map generateMap(); // Create player marker playerMarker = game.addChild(LK.getAsset('player', { anchorX: 0.5, anchorY: 0.5 })); // Create UI elements createUI(); // Create control buttons createControlButtons(); // Start background music LK.playMusic('dungeon'); } function generateMap() { // Clear existing map miniMap.removeChildren(); map = []; // Remove existing monsters and treasures for (var i = 0; i < monsters.length; i++) { monsters[i].destroy(); } monsters = []; for (var i = 0; i < treasures.length; i++) { treasures[i].destroy(); } treasures = []; // Clear projectiles for (var i = 0; i < projectiles.length; i++) { projectiles[i].destroy(); } projectiles = []; // Generate base map with borders for (var y = 0; y < MAP_SIZE; y++) { map[y] = []; for (var x = 0; x < MAP_SIZE; x++) { var cell = new MapCell(); cell.x = x * CELL_SIZE * MINI_MAP_SCALE; cell.y = y * CELL_SIZE * MINI_MAP_SCALE; // Create outer walls if (x === 0 || y === 0 || x === MAP_SIZE - 1 || y === MAP_SIZE - 1) { cell.setType(1); // Wall } else { // Random interior walls based on level difficulty var wallChance = 0.2 + player.level * 0.03; if (Math.random() < wallChance && !(x === 1 && y === 1)) { // Ensure starting position is clear cell.setType(1); // Wall } else { cell.setType(0); // Floor } } map[y][x] = cell; miniMap.addChild(cell); } } // Create monsters var monstersToPlace = MONSTER_COUNT + Math.floor(player.level * 0.5); for (var i = 0; i < monstersToPlace; i++) { placeMonster(); } // Create treasures var treasuresToPlace = TREASURE_COUNT; for (var i = 0; i < treasuresToPlace; i++) { placeTreasure(); } // Reset player position player.x = 1.5; player.y = 1.5; player.dir = 0; } function placeMonster() { // Find a random empty cell var x, y; var attempts = 0; do { x = Math.floor(Math.random() * (MAP_SIZE - 2)) + 1; y = Math.floor(Math.random() * (MAP_SIZE - 2)) + 1; attempts++; // Make sure it's not too close to the player var distToPlayer = Math.sqrt(Math.pow(x - player.x, 2) + Math.pow(y - player.y, 2)); if (attempts > 100) break; // Prevent infinite loop } while (map[y][x].type !== 0 || map[y][x].monster || map[y][x].treasure || distToPlayer < 3); if (attempts <= 100) { map[y][x].addMonster(); var monster = new Monster(); monster.mapX = x; monster.mapY = y; monster.health = 2 + Math.floor(player.level / 3); // Monsters get tougher with level monsters.push(monster); game.addChild(monster); } } function placeTreasure() { // Find a random empty cell var x, y; var attempts = 0; do { x = Math.floor(Math.random() * (MAP_SIZE - 2)) + 1; y = Math.floor(Math.random() * (MAP_SIZE - 2)) + 1; attempts++; if (attempts > 100) break; // Prevent infinite loop } while (map[y][x].type !== 0 || map[y][x].monster || map[y][x].treasure); if (attempts <= 100) { map[y][x].addTreasure(); var treasure = new Treasure(); treasure.mapX = x; treasure.mapY = y; treasure.value = 1 + Math.floor(Math.random() * player.level); treasures.push(treasure); game.addChild(treasure); } } function createUI() { // Health display healthText = new Text2('Health: ' + player.health, { size: 40, fill: 0xFF5555 }); healthText.anchor.set(0, 0); LK.gui.topRight.addChild(healthText); healthText.x = -200; healthText.y = 20; // Score display scoreText = new Text2('Score: ' + player.score, { size: 40, fill: 0xFFFF55 }); scoreText.anchor.set(0, 0); LK.gui.topRight.addChild(scoreText); scoreText.x = -200; scoreText.y = 80; // Level display levelText = new Text2('Level: ' + player.level, { size: 40, fill: 0x55FF55 }); levelText.anchor.set(0, 0); LK.gui.topRight.addChild(levelText); levelText.x = -200; levelText.y = 140; // Update UI displays updateUI(); } function updateUI() { healthText.setText('Health: ' + player.health); scoreText.setText('Score: ' + player.score); levelText.setText('Level: ' + player.level); // Update score in LK system LK.setScore(player.score); } function createControlButtons() { // Create directional buttons controlButtons.up = new ControlButton('up'); controlButtons.up.x = 200; controlButtons.up.y = 2732 - 300; game.addChild(controlButtons.up); controlButtons.right = new ControlButton('right'); controlButtons.right.x = 350; controlButtons.right.y = 2732 - 150; game.addChild(controlButtons.right); controlButtons.down = new ControlButton('down'); controlButtons.down.x = 200; controlButtons.down.y = 2732 - 150; game.addChild(controlButtons.down); controlButtons.left = new ControlButton('left'); controlButtons.left.x = 50; controlButtons.left.y = 2732 - 150; game.addChild(controlButtons.left); // Create attack button on the right side controlButtons.attack = new ControlButton('attack'); controlButtons.attack.x = 2048 - 200; controlButtons.attack.y = 2732 - 200; game.addChild(controlButtons.attack); } function rayCasting() { var rayAngle, distToWall, rayDirX, rayDirY, mapCheckX, mapCheckY; var distX, distY; var rayStartX = player.x; var rayStartY = player.y; for (var rayIdx = 0; rayIdx < NUM_RAYS; rayIdx++) { // Calculate ray angle (center ray + offset based on ray index) rayAngle = player.dir - HALF_FOV + rayIdx / NUM_RAYS * FOV; // Get direction vector rayDirX = Math.cos(rayAngle); rayDirY = Math.sin(rayAngle); // Distance to wall distToWall = 0; hitWall = false; // Step size for ray casting var stepSizeX = Math.abs(1 / rayDirX); var stepSizeY = Math.abs(1 / rayDirY); // Which block we're checking mapCheckX = Math.floor(rayStartX); mapCheckY = Math.floor(rayStartY); // Length of ray from current position to next x or y-side var sideDistX, sideDistY; // Direction to step in x or y direction (either +1 or -1) var stepX = rayDirX >= 0 ? 1 : -1; var stepY = rayDirY >= 0 ? 1 : -1; // Calculate distance to first x and y side if (rayDirX < 0) { sideDistX = (rayStartX - mapCheckX) * stepSizeX; } else { sideDistX = (mapCheckX + 1.0 - rayStartX) * stepSizeX; } if (rayDirY < 0) { sideDistY = (rayStartY - mapCheckY) * stepSizeY; } else { sideDistY = (mapCheckY + 1.0 - rayStartY) * stepSizeY; } // Perform DDA (Digital Differential Analysis) var hit = false; var side = 0; // 0 for x-side, 1 for y-side var maxDistance = MAX_RENDER_DISTANCE; while (!hit && distToWall < maxDistance) { // Jump to next map square if (sideDistX < sideDistY) { sideDistX += stepSizeX; mapCheckX += stepX; side = 0; } else { sideDistY += stepSizeY; mapCheckY += stepY; side = 1; } // Check if ray has hit a wall if (mapCheckX < 0 || mapCheckX >= MAP_SIZE || mapCheckY < 0 || mapCheckY >= MAP_SIZE) { hit = true; distToWall = maxDistance; } else if (map[mapCheckY][mapCheckX].type === 1) { hit = true; // Calculate exact distance to avoid fisheye effect if (side === 0) { distToWall = (mapCheckX - rayStartX + (1 - stepX) / 2) / rayDirX; } else { distToWall = (mapCheckY - rayStartY + (1 - stepY) / 2) / rayDirY; } } } // Calculate height of wall based on distance var wallHeight = Math.min(2732, WALL_HEIGHT_FACTOR / distToWall); // Update the strip var strip = rayCastView.children[rayIdx]; strip.updateStrip(STRIP_WIDTH, wallHeight, rayIdx, 1, distToWall); } // Render monsters and treasures renderEntities(); } function renderEntities() { // First, hide all entities for (var i = 0; i < monsters.length; i++) { monsters[i].visible = false; } for (var i = 0; i < treasures.length; i++) { treasures[i].visible = false; } // Calculate entity positions relative to player for (var i = 0; i < monsters.length; i++) { var monster = monsters[i]; // Vector from player to monster var dx = monster.mapX - player.x; var dy = monster.mapY - player.y; // Distance to monster var dist = Math.sqrt(dx * dx + dy * dy); // Angle between player's direction and monster var angle = Math.atan2(dy, dx) - player.dir; // Normalize angle while (angle < -Math.PI) angle += Math.PI * 2; while (angle > Math.PI) angle -= Math.PI * 2; // Check if monster is in field of view if (Math.abs(angle) < HALF_FOV && dist < MAX_RENDER_DISTANCE) { // Check if there's a wall between player and monster var rayDirX = Math.cos(player.dir + angle); var rayDirY = Math.sin(player.dir + angle); var rayHit = castRayToPoint(player.x, player.y, monster.mapX, monster.mapY); if (!rayHit.hit || rayHit.dist > dist - 0.5) { // Monster is visible monster.visible = true; // Calculate screen position var screenX = (0.5 + angle / FOV) * 2048; // Calculate height based on distance var height = WALL_HEIGHT_FACTOR / dist; // Position monster monster.x = screenX; monster.y = 2732 / 2; // Scale monster based on distance var scale = height / 100; monster.scale.set(scale, scale); } } } // Render treasures with the same logic for (var i = 0; i < treasures.length; i++) { var treasure = treasures[i]; var dx = treasure.mapX - player.x; var dy = treasure.mapY - player.y; var dist = Math.sqrt(dx * dx + dy * dy); var angle = Math.atan2(dy, dx) - player.dir; while (angle < -Math.PI) angle += Math.PI * 2; while (angle > Math.PI) angle -= Math.PI * 2; if (Math.abs(angle) < HALF_FOV && dist < MAX_RENDER_DISTANCE) { var rayHit = castRayToPoint(player.x, player.y, treasure.mapX, treasure.mapY); if (!rayHit.hit || rayHit.dist > dist - 0.5) { treasure.visible = true; var screenX = (0.5 + angle / FOV) * 2048; var height = WALL_HEIGHT_FACTOR / dist; treasure.x = screenX; treasure.y = 2732 / 2; var scale = height / 100; treasure.scale.set(scale, scale); } } } } function castRayToPoint(startX, startY, targetX, targetY) { var rayDirX = targetX - startX; var rayDirY = targetY - startY; var distance = Math.sqrt(rayDirX * rayDirX + rayDirY * rayDirY); rayDirX /= distance; rayDirY /= distance; var mapCheckX = Math.floor(startX); var mapCheckY = Math.floor(startY); var stepSizeX = Math.abs(1 / rayDirX); var stepSizeY = Math.abs(1 / rayDirY); var stepX = rayDirX >= 0 ? 1 : -1; var stepY = rayDirY >= 0 ? 1 : -1; var sideDistX, sideDistY; if (rayDirX < 0) { sideDistX = (startX - mapCheckX) * stepSizeX; } else { sideDistX = (mapCheckX + 1.0 - startX) * stepSizeX; } if (rayDirY < 0) { sideDistY = (startY - mapCheckY) * stepSizeY; } else { sideDistY = (mapCheckY + 1.0 - startY) * stepSizeY; } var hit = false; var side = 0; var distToWall = 0; while (!hit && distToWall < distance) { if (sideDistX < sideDistY) { sideDistX += stepSizeX; mapCheckX += stepX; side = 0; distToWall = sideDistX - stepSizeX; } else { sideDistY += stepSizeY; mapCheckY += stepY; side = 1; distToWall = sideDistY - stepSizeY; } if (mapCheckX < 0 || mapCheckX >= MAP_SIZE || mapCheckY < 0 || mapCheckY >= MAP_SIZE) { break; } else if (map[mapCheckY][mapCheckX].type === 1) { hit = true; } } return { hit: hit, dist: distToWall }; } function updateControls() { // Read from control buttons controls.forward = controlButtons.up.pressed; controls.backward = controlButtons.down.pressed; controls.left = controlButtons.left.pressed; controls.right = controlButtons.right.pressed; controls.attack = controlButtons.attack.pressed; } function updatePlayerMovement(deltaTime) { var moveSpeed = PLAYER_MOVE_SPEED * deltaTime; var turnSpeed = PLAYER_TURN_SPEED * deltaTime; var dx = 0, dy = 0; var didMove = false; // Handle rotation if (controls.left) { player.dir -= turnSpeed; while (player.dir < 0) player.dir += Math.PI * 2; } if (controls.right) { player.dir += turnSpeed; while (player.dir >= Math.PI * 2) player.dir -= Math.PI * 2; } // Handle movement if (controls.forward) { dx += Math.cos(player.dir) * moveSpeed; dy += Math.sin(player.dir) * moveSpeed; didMove = true; } if (controls.backward) { dx -= Math.cos(player.dir) * moveSpeed; dy -= Math.sin(player.dir) * moveSpeed; didMove = true; } // Collision detection var newX = player.x + dx; var newY = player.y + dy; var cellX = Math.floor(newX); var cellY = Math.floor(newY); // Check if we can move to the new position if (cellX >= 0 && cellX < MAP_SIZE && cellY >= 0 && cellY < MAP_SIZE) { if (map[cellY][cellX].type === 0) { player.x = newX; player.y = newY; if (didMove && LK.ticks % 20 === 0) { LK.getSound('walk').play(); } } } // Attack handling if (controls.attack && LK.ticks % 15 === 0) { attackAction(); } // Check for collisions with monsters checkMonsterCollisions(); // Check for collisions with treasures checkTreasureCollisions(); // Update player marker on minimap updateMiniMap(); } function attackAction() { LK.getSound('attack').play(); // Create a projectile var projectile = new Projectile(); projectile.x = 2048 / 2; projectile.y = 2732 - 300; projectile.dirX = Math.cos(player.dir); projectile.dirY = Math.sin(player.dir); projectiles.push(projectile); game.addChild(projectile); // Projectile visual effect tween(projectile, { y: 2732 / 2 }, { duration: 100, easing: tween.easeOut }); } function updateProjectiles(deltaTime) { for (var i = projectiles.length - 1; i >= 0; i--) { var projectile = projectiles[i]; // Convert screen projectile to map projectile var mapX = player.x + projectile.distanceTraveled * Math.cos(player.dir); var mapY = player.y + projectile.distanceTraveled * Math.sin(player.dir); // Check if projectile hits a wall var cellX = Math.floor(mapX); var cellY = Math.floor(mapY); var hitWall = false; if (cellX < 0 || cellX >= MAP_SIZE || cellY < 0 || cellY >= MAP_SIZE) { hitWall = true; } else if (map[cellY][cellX].type === 1) { hitWall = true; } // Check if projectile hits a monster var hitMonster = false; var hitMonsterIndex = -1; for (var j = 0; j < monsters.length; j++) { var monster = monsters[j]; var dx = monster.mapX - mapX; var dy = monster.mapY - mapY; var dist = Math.sqrt(dx * dx + dy * dy); if (dist < 0.5) { hitMonster = true; hitMonsterIndex = j; break; } } // Update projectile var remove = projectile.update(deltaTime); if (remove || hitWall || hitMonster) { // Handle monster hit if (hitMonster && hitMonsterIndex !== -1) { var monster = monsters[hitMonsterIndex]; var killed = monster.takeDamage(); if (killed) { // Remove monster map[Math.floor(monster.mapY)][Math.floor(monster.mapX)].removeMonster(); monster.destroy(); monsters.splice(hitMonsterIndex, 1); // Increase score player.score += 10; updateUI(); // Check for level completion checkLevelCompletion(); } } // Remove projectile projectile.destroy(); projectiles.splice(i, 1); } } } function checkMonsterCollisions() { for (var i = 0; i < monsters.length; i++) { var monster = monsters[i]; var dx = monster.mapX - player.x; var dy = monster.mapY - player.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist < 0.5) { // Player hit by monster player.health--; updateUI(); // Visual feedback LK.effects.flashScreen(0xff0000, 300); // Play sound LK.getSound('hit').play(); // Push player back slightly player.x -= dx * 0.3; player.y -= dy * 0.3; // Check game over if (player.health <= 0) { LK.showGameOver(); } break; } } } function checkTreasureCollisions() { for (var i = treasures.length - 1; i >= 0; i--) { var treasure = treasures[i]; var dx = treasure.mapX - player.x; var dy = treasure.mapY - player.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist < 0.5) { // Collect treasure player.score += treasure.value * 5; updateUI(); // Play sound LK.getSound('collect').play(); // Remove treasure map[Math.floor(treasure.mapY)][Math.floor(treasure.mapX)].removeTreasure(); treasure.destroy(); treasures.splice(i, 1); // Check level completion checkLevelCompletion(); } } } function checkLevelCompletion() { // Level is complete if all monsters are defeated and all treasures collected if (monsters.length === 0 && treasures.length === 0) { // Level up player.level++; storage.level = player.level; // Restore some health player.health = Math.min(player.health + 2, 5); // Update UI updateUI(); // Show level complete var levelCompleteText = new Text2('Level ' + (player.level - 1) + ' Complete!', { size: 80, fill: 0x55FF55 }); levelCompleteText.anchor.set(0.5, 0.5); levelCompleteText.x = 2048 / 2; levelCompleteText.y = 2732 / 2; game.addChild(levelCompleteText); // Generate new level after a delay LK.setTimeout(function () { levelCompleteText.destroy(); generateMap(); }, 2000); } } function updateMiniMap() { // Update player marker position on minimap playerMarker.x = miniMap.x + player.x * CELL_SIZE * MINI_MAP_SCALE; playerMarker.y = miniMap.y + player.y * CELL_SIZE * MINI_MAP_SCALE; // Draw player direction indicator var dirX = Math.cos(player.dir) * 15; var dirY = Math.sin(player.dir) * 15; } // Game update method game.update = function () { var currentTime = Date.now(); var deltaTime = currentTime - lastTime; lastTime = currentTime; // Update controls updateControls(); // Update player updatePlayerMovement(deltaTime); // Update projectiles updateProjectiles(deltaTime); // Update raycast view rayCasting(); }; // Game initialization setupGame(); // Event handlers game.down = function (x, y, obj) { // Handle general screen press }; game.up = function (x, y, obj) { // Handle general screen release }; game.move = function (x, y, obj) { // Handle general screen move };
===================================================================
--- original.js
+++ change.js
@@ -1,6 +1,875 @@
-/****
+/****
+* Plugins
+****/
+var tween = LK.import("@upit/tween.v1");
+var storage = LK.import("@upit/storage.v1");
+
+/****
+* Classes
+****/
+var ControlButton = Container.expand(function (direction) {
+ var self = Container.call(this);
+ var buttonSprite = self.attachAsset('controlButton', {
+ anchorX: 0.5,
+ anchorY: 0.5
+ });
+ var arrowSprite = self.attachAsset('buttonArrow', {
+ anchorX: 0.5,
+ anchorY: 0.5
+ });
+ // Set arrow direction based on button type
+ if (direction === 'up') {
+ arrowSprite.rotation = 0;
+ } else if (direction === 'right') {
+ arrowSprite.rotation = Math.PI / 2;
+ } else if (direction === 'down') {
+ arrowSprite.rotation = Math.PI;
+ } else if (direction === 'left') {
+ arrowSprite.rotation = Math.PI * 1.5;
+ } else if (direction === 'attack') {
+ arrowSprite.visible = false;
+ buttonSprite = self.attachAsset('attackButton', {
+ anchorX: 0.5,
+ anchorY: 0.5
+ });
+ }
+ self.direction = direction;
+ self.pressed = false;
+ self.down = function (x, y, obj) {
+ self.pressed = true;
+ buttonSprite.alpha = 0.7;
+ };
+ self.up = function (x, y, obj) {
+ self.pressed = false;
+ buttonSprite.alpha = 1;
+ };
+ return self;
+});
+var MapCell = Container.expand(function () {
+ var self = Container.call(this);
+ self.type = 0; // 0 = floor, 1 = wall
+ self.monster = null;
+ self.treasure = null;
+ self.setType = function (type) {
+ self.type = type;
+ self.updateVisual();
+ };
+ self.updateVisual = function () {
+ self.removeChildren();
+ if (self.type === 1) {
+ self.attachAsset('mapWall', {
+ anchorX: 0,
+ anchorY: 0
+ });
+ } else {
+ self.attachAsset('mapFloor', {
+ anchorX: 0,
+ anchorY: 0
+ });
+ }
+ };
+ self.addMonster = function () {
+ if (self.type === 0 && !self.monster && !self.treasure) {
+ self.monster = true;
+ return true;
+ }
+ return false;
+ };
+ self.addTreasure = function () {
+ if (self.type === 0 && !self.monster && !self.treasure) {
+ self.treasure = true;
+ return true;
+ }
+ return false;
+ };
+ self.removeMonster = function () {
+ self.monster = null;
+ };
+ self.removeTreasure = function () {
+ self.treasure = null;
+ };
+ return self;
+});
+var Monster = Container.expand(function () {
+ var self = Container.call(this);
+ var monsterSprite = self.attachAsset('monster', {
+ anchorX: 0.5,
+ anchorY: 0.5
+ });
+ self.mapX = 0;
+ self.mapY = 0;
+ self.health = 3;
+ self.takeDamage = function () {
+ self.health -= 1;
+ LK.getSound('hit').play();
+ // Visual feedback for hit
+ tween(monsterSprite, {
+ alpha: 0.2
+ }, {
+ duration: 100,
+ onFinish: function onFinish() {
+ tween(monsterSprite, {
+ alpha: 1
+ }, {
+ duration: 100
+ });
+ }
+ });
+ return self.health <= 0;
+ };
+ return self;
+});
+var Projectile = Container.expand(function () {
+ var self = Container.call(this);
+ var projectileSprite = self.attachAsset('projectile', {
+ anchorX: 0.5,
+ anchorY: 0.5
+ });
+ self.dirX = 0;
+ self.dirY = 0;
+ self.speed = 0.2;
+ self.range = 5;
+ self.distanceTraveled = 0;
+ self.update = function (deltaTime) {
+ // Move projectile
+ var moveX = self.dirX * self.speed * deltaTime;
+ var moveY = self.dirY * self.speed * deltaTime;
+ self.x += moveX;
+ self.y += moveY;
+ self.distanceTraveled += Math.sqrt(moveX * moveX + moveY * moveY);
+ return self.distanceTraveled >= self.range;
+ };
+ return self;
+});
+var RaycastStrip = Container.expand(function () {
+ var self = Container.call(this);
+ var wall = self.attachAsset('wall', {
+ anchorX: 0,
+ anchorY: 0
+ });
+ var ceiling = self.attachAsset('ceiling', {
+ anchorX: 0,
+ anchorY: 0
+ });
+ var floor = self.attachAsset('floor', {
+ anchorX: 0,
+ anchorY: 0
+ });
+ self.updateStrip = function (stripWidth, wallHeight, stripIdx, wallType, distance) {
+ // Wall setup
+ wall.width = stripWidth;
+ wall.height = wallHeight;
+ wall.y = (2732 - wallHeight) / 2;
+ // Ceiling setup
+ ceiling.width = stripWidth;
+ ceiling.height = wall.y;
+ ceiling.y = 0;
+ // Floor setup
+ floor.width = stripWidth;
+ floor.height = ceiling.height;
+ floor.y = wall.y + wallHeight;
+ // Adjust positions
+ self.x = stripIdx * stripWidth;
+ // Add distance shading effect
+ var shade = Math.max(0.3, 1 - distance / 10);
+ wall.alpha = shade;
+ ceiling.alpha = shade * 0.7;
+ floor.alpha = shade * 0.8;
+ };
+ return self;
+});
+var Treasure = Container.expand(function () {
+ var self = Container.call(this);
+ var treasureSprite = self.attachAsset('treasure', {
+ anchorX: 0.5,
+ anchorY: 0.5
+ });
+ self.mapX = 0;
+ self.mapY = 0;
+ self.value = 1;
+ // Animate the treasure to make it more appealing
+ var _animateTreasure = function animateTreasure() {
+ tween(treasureSprite, {
+ rotation: Math.PI * 2
+ }, {
+ duration: 2000,
+ onFinish: function onFinish() {
+ treasureSprite.rotation = 0;
+ _animateTreasure();
+ }
+ });
+ };
+ _animateTreasure();
+ return self;
+});
+
+/****
* Initialize Game
-****/
+****/
var game = new LK.Game({
- backgroundColor: 0x000000
-});
\ No newline at end of file
+ backgroundColor: 0x111111
+});
+
+/****
+* Game Code
+****/
+// Game constants
+var MAP_SIZE = 16;
+var CELL_SIZE = 20;
+var MINI_MAP_SCALE = 1;
+var STRIP_WIDTH = 8;
+var NUM_RAYS = Math.ceil(2048 / STRIP_WIDTH);
+var FOV = Math.PI / 3; // 60 degrees field of view
+var HALF_FOV = FOV / 2;
+var PLAYER_MOVE_SPEED = 0.05;
+var PLAYER_TURN_SPEED = 0.04;
+var WALL_HEIGHT_FACTOR = 600;
+var MAX_RENDER_DISTANCE = 16;
+var MONSTER_COUNT = 5;
+var TREASURE_COUNT = 10;
+// Game state
+var map = [];
+var player = {
+ x: 1.5,
+ y: 1.5,
+ dir: 0,
+ health: 5,
+ score: 0,
+ level: storage.level || 1
+};
+var controls = {
+ forward: false,
+ backward: false,
+ left: false,
+ right: false,
+ attack: false
+};
+var monsters = [];
+var treasures = [];
+var projectiles = [];
+var lastTime = Date.now();
+// UI elements
+var miniMap;
+var rayCastView;
+var healthText;
+var scoreText;
+var levelText;
+var controlButtons = {};
+var playerMarker;
+// Setup game
+function setupGame() {
+ // Create the rayCast view container
+ rayCastView = new Container();
+ game.addChild(rayCastView);
+ // Create raycast strips
+ for (var i = 0; i < NUM_RAYS; i++) {
+ var strip = new RaycastStrip();
+ rayCastView.addChild(strip);
+ }
+ // Create minimap container
+ miniMap = new Container();
+ miniMap.x = 20;
+ miniMap.y = 20;
+ game.addChild(miniMap);
+ // Generate map
+ generateMap();
+ // Create player marker
+ playerMarker = game.addChild(LK.getAsset('player', {
+ anchorX: 0.5,
+ anchorY: 0.5
+ }));
+ // Create UI elements
+ createUI();
+ // Create control buttons
+ createControlButtons();
+ // Start background music
+ LK.playMusic('dungeon');
+}
+function generateMap() {
+ // Clear existing map
+ miniMap.removeChildren();
+ map = [];
+ // Remove existing monsters and treasures
+ for (var i = 0; i < monsters.length; i++) {
+ monsters[i].destroy();
+ }
+ monsters = [];
+ for (var i = 0; i < treasures.length; i++) {
+ treasures[i].destroy();
+ }
+ treasures = [];
+ // Clear projectiles
+ for (var i = 0; i < projectiles.length; i++) {
+ projectiles[i].destroy();
+ }
+ projectiles = [];
+ // Generate base map with borders
+ for (var y = 0; y < MAP_SIZE; y++) {
+ map[y] = [];
+ for (var x = 0; x < MAP_SIZE; x++) {
+ var cell = new MapCell();
+ cell.x = x * CELL_SIZE * MINI_MAP_SCALE;
+ cell.y = y * CELL_SIZE * MINI_MAP_SCALE;
+ // Create outer walls
+ if (x === 0 || y === 0 || x === MAP_SIZE - 1 || y === MAP_SIZE - 1) {
+ cell.setType(1); // Wall
+ } else {
+ // Random interior walls based on level difficulty
+ var wallChance = 0.2 + player.level * 0.03;
+ if (Math.random() < wallChance && !(x === 1 && y === 1)) {
+ // Ensure starting position is clear
+ cell.setType(1); // Wall
+ } else {
+ cell.setType(0); // Floor
+ }
+ }
+ map[y][x] = cell;
+ miniMap.addChild(cell);
+ }
+ }
+ // Create monsters
+ var monstersToPlace = MONSTER_COUNT + Math.floor(player.level * 0.5);
+ for (var i = 0; i < monstersToPlace; i++) {
+ placeMonster();
+ }
+ // Create treasures
+ var treasuresToPlace = TREASURE_COUNT;
+ for (var i = 0; i < treasuresToPlace; i++) {
+ placeTreasure();
+ }
+ // Reset player position
+ player.x = 1.5;
+ player.y = 1.5;
+ player.dir = 0;
+}
+function placeMonster() {
+ // Find a random empty cell
+ var x, y;
+ var attempts = 0;
+ do {
+ x = Math.floor(Math.random() * (MAP_SIZE - 2)) + 1;
+ y = Math.floor(Math.random() * (MAP_SIZE - 2)) + 1;
+ attempts++;
+ // Make sure it's not too close to the player
+ var distToPlayer = Math.sqrt(Math.pow(x - player.x, 2) + Math.pow(y - player.y, 2));
+ if (attempts > 100) break; // Prevent infinite loop
+ } while (map[y][x].type !== 0 || map[y][x].monster || map[y][x].treasure || distToPlayer < 3);
+ if (attempts <= 100) {
+ map[y][x].addMonster();
+ var monster = new Monster();
+ monster.mapX = x;
+ monster.mapY = y;
+ monster.health = 2 + Math.floor(player.level / 3); // Monsters get tougher with level
+ monsters.push(monster);
+ game.addChild(monster);
+ }
+}
+function placeTreasure() {
+ // Find a random empty cell
+ var x, y;
+ var attempts = 0;
+ do {
+ x = Math.floor(Math.random() * (MAP_SIZE - 2)) + 1;
+ y = Math.floor(Math.random() * (MAP_SIZE - 2)) + 1;
+ attempts++;
+ if (attempts > 100) break; // Prevent infinite loop
+ } while (map[y][x].type !== 0 || map[y][x].monster || map[y][x].treasure);
+ if (attempts <= 100) {
+ map[y][x].addTreasure();
+ var treasure = new Treasure();
+ treasure.mapX = x;
+ treasure.mapY = y;
+ treasure.value = 1 + Math.floor(Math.random() * player.level);
+ treasures.push(treasure);
+ game.addChild(treasure);
+ }
+}
+function createUI() {
+ // Health display
+ healthText = new Text2('Health: ' + player.health, {
+ size: 40,
+ fill: 0xFF5555
+ });
+ healthText.anchor.set(0, 0);
+ LK.gui.topRight.addChild(healthText);
+ healthText.x = -200;
+ healthText.y = 20;
+ // Score display
+ scoreText = new Text2('Score: ' + player.score, {
+ size: 40,
+ fill: 0xFFFF55
+ });
+ scoreText.anchor.set(0, 0);
+ LK.gui.topRight.addChild(scoreText);
+ scoreText.x = -200;
+ scoreText.y = 80;
+ // Level display
+ levelText = new Text2('Level: ' + player.level, {
+ size: 40,
+ fill: 0x55FF55
+ });
+ levelText.anchor.set(0, 0);
+ LK.gui.topRight.addChild(levelText);
+ levelText.x = -200;
+ levelText.y = 140;
+ // Update UI displays
+ updateUI();
+}
+function updateUI() {
+ healthText.setText('Health: ' + player.health);
+ scoreText.setText('Score: ' + player.score);
+ levelText.setText('Level: ' + player.level);
+ // Update score in LK system
+ LK.setScore(player.score);
+}
+function createControlButtons() {
+ // Create directional buttons
+ controlButtons.up = new ControlButton('up');
+ controlButtons.up.x = 200;
+ controlButtons.up.y = 2732 - 300;
+ game.addChild(controlButtons.up);
+ controlButtons.right = new ControlButton('right');
+ controlButtons.right.x = 350;
+ controlButtons.right.y = 2732 - 150;
+ game.addChild(controlButtons.right);
+ controlButtons.down = new ControlButton('down');
+ controlButtons.down.x = 200;
+ controlButtons.down.y = 2732 - 150;
+ game.addChild(controlButtons.down);
+ controlButtons.left = new ControlButton('left');
+ controlButtons.left.x = 50;
+ controlButtons.left.y = 2732 - 150;
+ game.addChild(controlButtons.left);
+ // Create attack button on the right side
+ controlButtons.attack = new ControlButton('attack');
+ controlButtons.attack.x = 2048 - 200;
+ controlButtons.attack.y = 2732 - 200;
+ game.addChild(controlButtons.attack);
+}
+function rayCasting() {
+ var rayAngle, distToWall, rayDirX, rayDirY, mapCheckX, mapCheckY;
+ var distX, distY;
+ var rayStartX = player.x;
+ var rayStartY = player.y;
+ for (var rayIdx = 0; rayIdx < NUM_RAYS; rayIdx++) {
+ // Calculate ray angle (center ray + offset based on ray index)
+ rayAngle = player.dir - HALF_FOV + rayIdx / NUM_RAYS * FOV;
+ // Get direction vector
+ rayDirX = Math.cos(rayAngle);
+ rayDirY = Math.sin(rayAngle);
+ // Distance to wall
+ distToWall = 0;
+ hitWall = false;
+ // Step size for ray casting
+ var stepSizeX = Math.abs(1 / rayDirX);
+ var stepSizeY = Math.abs(1 / rayDirY);
+ // Which block we're checking
+ mapCheckX = Math.floor(rayStartX);
+ mapCheckY = Math.floor(rayStartY);
+ // Length of ray from current position to next x or y-side
+ var sideDistX, sideDistY;
+ // Direction to step in x or y direction (either +1 or -1)
+ var stepX = rayDirX >= 0 ? 1 : -1;
+ var stepY = rayDirY >= 0 ? 1 : -1;
+ // Calculate distance to first x and y side
+ if (rayDirX < 0) {
+ sideDistX = (rayStartX - mapCheckX) * stepSizeX;
+ } else {
+ sideDistX = (mapCheckX + 1.0 - rayStartX) * stepSizeX;
+ }
+ if (rayDirY < 0) {
+ sideDistY = (rayStartY - mapCheckY) * stepSizeY;
+ } else {
+ sideDistY = (mapCheckY + 1.0 - rayStartY) * stepSizeY;
+ }
+ // Perform DDA (Digital Differential Analysis)
+ var hit = false;
+ var side = 0; // 0 for x-side, 1 for y-side
+ var maxDistance = MAX_RENDER_DISTANCE;
+ while (!hit && distToWall < maxDistance) {
+ // Jump to next map square
+ if (sideDistX < sideDistY) {
+ sideDistX += stepSizeX;
+ mapCheckX += stepX;
+ side = 0;
+ } else {
+ sideDistY += stepSizeY;
+ mapCheckY += stepY;
+ side = 1;
+ }
+ // Check if ray has hit a wall
+ if (mapCheckX < 0 || mapCheckX >= MAP_SIZE || mapCheckY < 0 || mapCheckY >= MAP_SIZE) {
+ hit = true;
+ distToWall = maxDistance;
+ } else if (map[mapCheckY][mapCheckX].type === 1) {
+ hit = true;
+ // Calculate exact distance to avoid fisheye effect
+ if (side === 0) {
+ distToWall = (mapCheckX - rayStartX + (1 - stepX) / 2) / rayDirX;
+ } else {
+ distToWall = (mapCheckY - rayStartY + (1 - stepY) / 2) / rayDirY;
+ }
+ }
+ }
+ // Calculate height of wall based on distance
+ var wallHeight = Math.min(2732, WALL_HEIGHT_FACTOR / distToWall);
+ // Update the strip
+ var strip = rayCastView.children[rayIdx];
+ strip.updateStrip(STRIP_WIDTH, wallHeight, rayIdx, 1, distToWall);
+ }
+ // Render monsters and treasures
+ renderEntities();
+}
+function renderEntities() {
+ // First, hide all entities
+ for (var i = 0; i < monsters.length; i++) {
+ monsters[i].visible = false;
+ }
+ for (var i = 0; i < treasures.length; i++) {
+ treasures[i].visible = false;
+ }
+ // Calculate entity positions relative to player
+ for (var i = 0; i < monsters.length; i++) {
+ var monster = monsters[i];
+ // Vector from player to monster
+ var dx = monster.mapX - player.x;
+ var dy = monster.mapY - player.y;
+ // Distance to monster
+ var dist = Math.sqrt(dx * dx + dy * dy);
+ // Angle between player's direction and monster
+ var angle = Math.atan2(dy, dx) - player.dir;
+ // Normalize angle
+ while (angle < -Math.PI) angle += Math.PI * 2;
+ while (angle > Math.PI) angle -= Math.PI * 2;
+ // Check if monster is in field of view
+ if (Math.abs(angle) < HALF_FOV && dist < MAX_RENDER_DISTANCE) {
+ // Check if there's a wall between player and monster
+ var rayDirX = Math.cos(player.dir + angle);
+ var rayDirY = Math.sin(player.dir + angle);
+ var rayHit = castRayToPoint(player.x, player.y, monster.mapX, monster.mapY);
+ if (!rayHit.hit || rayHit.dist > dist - 0.5) {
+ // Monster is visible
+ monster.visible = true;
+ // Calculate screen position
+ var screenX = (0.5 + angle / FOV) * 2048;
+ // Calculate height based on distance
+ var height = WALL_HEIGHT_FACTOR / dist;
+ // Position monster
+ monster.x = screenX;
+ monster.y = 2732 / 2;
+ // Scale monster based on distance
+ var scale = height / 100;
+ monster.scale.set(scale, scale);
+ }
+ }
+ }
+ // Render treasures with the same logic
+ for (var i = 0; i < treasures.length; i++) {
+ var treasure = treasures[i];
+ var dx = treasure.mapX - player.x;
+ var dy = treasure.mapY - player.y;
+ var dist = Math.sqrt(dx * dx + dy * dy);
+ var angle = Math.atan2(dy, dx) - player.dir;
+ while (angle < -Math.PI) angle += Math.PI * 2;
+ while (angle > Math.PI) angle -= Math.PI * 2;
+ if (Math.abs(angle) < HALF_FOV && dist < MAX_RENDER_DISTANCE) {
+ var rayHit = castRayToPoint(player.x, player.y, treasure.mapX, treasure.mapY);
+ if (!rayHit.hit || rayHit.dist > dist - 0.5) {
+ treasure.visible = true;
+ var screenX = (0.5 + angle / FOV) * 2048;
+ var height = WALL_HEIGHT_FACTOR / dist;
+ treasure.x = screenX;
+ treasure.y = 2732 / 2;
+ var scale = height / 100;
+ treasure.scale.set(scale, scale);
+ }
+ }
+ }
+}
+function castRayToPoint(startX, startY, targetX, targetY) {
+ var rayDirX = targetX - startX;
+ var rayDirY = targetY - startY;
+ var distance = Math.sqrt(rayDirX * rayDirX + rayDirY * rayDirY);
+ rayDirX /= distance;
+ rayDirY /= distance;
+ var mapCheckX = Math.floor(startX);
+ var mapCheckY = Math.floor(startY);
+ var stepSizeX = Math.abs(1 / rayDirX);
+ var stepSizeY = Math.abs(1 / rayDirY);
+ var stepX = rayDirX >= 0 ? 1 : -1;
+ var stepY = rayDirY >= 0 ? 1 : -1;
+ var sideDistX, sideDistY;
+ if (rayDirX < 0) {
+ sideDistX = (startX - mapCheckX) * stepSizeX;
+ } else {
+ sideDistX = (mapCheckX + 1.0 - startX) * stepSizeX;
+ }
+ if (rayDirY < 0) {
+ sideDistY = (startY - mapCheckY) * stepSizeY;
+ } else {
+ sideDistY = (mapCheckY + 1.0 - startY) * stepSizeY;
+ }
+ var hit = false;
+ var side = 0;
+ var distToWall = 0;
+ while (!hit && distToWall < distance) {
+ if (sideDistX < sideDistY) {
+ sideDistX += stepSizeX;
+ mapCheckX += stepX;
+ side = 0;
+ distToWall = sideDistX - stepSizeX;
+ } else {
+ sideDistY += stepSizeY;
+ mapCheckY += stepY;
+ side = 1;
+ distToWall = sideDistY - stepSizeY;
+ }
+ if (mapCheckX < 0 || mapCheckX >= MAP_SIZE || mapCheckY < 0 || mapCheckY >= MAP_SIZE) {
+ break;
+ } else if (map[mapCheckY][mapCheckX].type === 1) {
+ hit = true;
+ }
+ }
+ return {
+ hit: hit,
+ dist: distToWall
+ };
+}
+function updateControls() {
+ // Read from control buttons
+ controls.forward = controlButtons.up.pressed;
+ controls.backward = controlButtons.down.pressed;
+ controls.left = controlButtons.left.pressed;
+ controls.right = controlButtons.right.pressed;
+ controls.attack = controlButtons.attack.pressed;
+}
+function updatePlayerMovement(deltaTime) {
+ var moveSpeed = PLAYER_MOVE_SPEED * deltaTime;
+ var turnSpeed = PLAYER_TURN_SPEED * deltaTime;
+ var dx = 0,
+ dy = 0;
+ var didMove = false;
+ // Handle rotation
+ if (controls.left) {
+ player.dir -= turnSpeed;
+ while (player.dir < 0) player.dir += Math.PI * 2;
+ }
+ if (controls.right) {
+ player.dir += turnSpeed;
+ while (player.dir >= Math.PI * 2) player.dir -= Math.PI * 2;
+ }
+ // Handle movement
+ if (controls.forward) {
+ dx += Math.cos(player.dir) * moveSpeed;
+ dy += Math.sin(player.dir) * moveSpeed;
+ didMove = true;
+ }
+ if (controls.backward) {
+ dx -= Math.cos(player.dir) * moveSpeed;
+ dy -= Math.sin(player.dir) * moveSpeed;
+ didMove = true;
+ }
+ // Collision detection
+ var newX = player.x + dx;
+ var newY = player.y + dy;
+ var cellX = Math.floor(newX);
+ var cellY = Math.floor(newY);
+ // Check if we can move to the new position
+ if (cellX >= 0 && cellX < MAP_SIZE && cellY >= 0 && cellY < MAP_SIZE) {
+ if (map[cellY][cellX].type === 0) {
+ player.x = newX;
+ player.y = newY;
+ if (didMove && LK.ticks % 20 === 0) {
+ LK.getSound('walk').play();
+ }
+ }
+ }
+ // Attack handling
+ if (controls.attack && LK.ticks % 15 === 0) {
+ attackAction();
+ }
+ // Check for collisions with monsters
+ checkMonsterCollisions();
+ // Check for collisions with treasures
+ checkTreasureCollisions();
+ // Update player marker on minimap
+ updateMiniMap();
+}
+function attackAction() {
+ LK.getSound('attack').play();
+ // Create a projectile
+ var projectile = new Projectile();
+ projectile.x = 2048 / 2;
+ projectile.y = 2732 - 300;
+ projectile.dirX = Math.cos(player.dir);
+ projectile.dirY = Math.sin(player.dir);
+ projectiles.push(projectile);
+ game.addChild(projectile);
+ // Projectile visual effect
+ tween(projectile, {
+ y: 2732 / 2
+ }, {
+ duration: 100,
+ easing: tween.easeOut
+ });
+}
+function updateProjectiles(deltaTime) {
+ for (var i = projectiles.length - 1; i >= 0; i--) {
+ var projectile = projectiles[i];
+ // Convert screen projectile to map projectile
+ var mapX = player.x + projectile.distanceTraveled * Math.cos(player.dir);
+ var mapY = player.y + projectile.distanceTraveled * Math.sin(player.dir);
+ // Check if projectile hits a wall
+ var cellX = Math.floor(mapX);
+ var cellY = Math.floor(mapY);
+ var hitWall = false;
+ if (cellX < 0 || cellX >= MAP_SIZE || cellY < 0 || cellY >= MAP_SIZE) {
+ hitWall = true;
+ } else if (map[cellY][cellX].type === 1) {
+ hitWall = true;
+ }
+ // Check if projectile hits a monster
+ var hitMonster = false;
+ var hitMonsterIndex = -1;
+ for (var j = 0; j < monsters.length; j++) {
+ var monster = monsters[j];
+ var dx = monster.mapX - mapX;
+ var dy = monster.mapY - mapY;
+ var dist = Math.sqrt(dx * dx + dy * dy);
+ if (dist < 0.5) {
+ hitMonster = true;
+ hitMonsterIndex = j;
+ break;
+ }
+ }
+ // Update projectile
+ var remove = projectile.update(deltaTime);
+ if (remove || hitWall || hitMonster) {
+ // Handle monster hit
+ if (hitMonster && hitMonsterIndex !== -1) {
+ var monster = monsters[hitMonsterIndex];
+ var killed = monster.takeDamage();
+ if (killed) {
+ // Remove monster
+ map[Math.floor(monster.mapY)][Math.floor(monster.mapX)].removeMonster();
+ monster.destroy();
+ monsters.splice(hitMonsterIndex, 1);
+ // Increase score
+ player.score += 10;
+ updateUI();
+ // Check for level completion
+ checkLevelCompletion();
+ }
+ }
+ // Remove projectile
+ projectile.destroy();
+ projectiles.splice(i, 1);
+ }
+ }
+}
+function checkMonsterCollisions() {
+ for (var i = 0; i < monsters.length; i++) {
+ var monster = monsters[i];
+ var dx = monster.mapX - player.x;
+ var dy = monster.mapY - player.y;
+ var dist = Math.sqrt(dx * dx + dy * dy);
+ if (dist < 0.5) {
+ // Player hit by monster
+ player.health--;
+ updateUI();
+ // Visual feedback
+ LK.effects.flashScreen(0xff0000, 300);
+ // Play sound
+ LK.getSound('hit').play();
+ // Push player back slightly
+ player.x -= dx * 0.3;
+ player.y -= dy * 0.3;
+ // Check game over
+ if (player.health <= 0) {
+ LK.showGameOver();
+ }
+ break;
+ }
+ }
+}
+function checkTreasureCollisions() {
+ for (var i = treasures.length - 1; i >= 0; i--) {
+ var treasure = treasures[i];
+ var dx = treasure.mapX - player.x;
+ var dy = treasure.mapY - player.y;
+ var dist = Math.sqrt(dx * dx + dy * dy);
+ if (dist < 0.5) {
+ // Collect treasure
+ player.score += treasure.value * 5;
+ updateUI();
+ // Play sound
+ LK.getSound('collect').play();
+ // Remove treasure
+ map[Math.floor(treasure.mapY)][Math.floor(treasure.mapX)].removeTreasure();
+ treasure.destroy();
+ treasures.splice(i, 1);
+ // Check level completion
+ checkLevelCompletion();
+ }
+ }
+}
+function checkLevelCompletion() {
+ // Level is complete if all monsters are defeated and all treasures collected
+ if (monsters.length === 0 && treasures.length === 0) {
+ // Level up
+ player.level++;
+ storage.level = player.level;
+ // Restore some health
+ player.health = Math.min(player.health + 2, 5);
+ // Update UI
+ updateUI();
+ // Show level complete
+ var levelCompleteText = new Text2('Level ' + (player.level - 1) + ' Complete!', {
+ size: 80,
+ fill: 0x55FF55
+ });
+ levelCompleteText.anchor.set(0.5, 0.5);
+ levelCompleteText.x = 2048 / 2;
+ levelCompleteText.y = 2732 / 2;
+ game.addChild(levelCompleteText);
+ // Generate new level after a delay
+ LK.setTimeout(function () {
+ levelCompleteText.destroy();
+ generateMap();
+ }, 2000);
+ }
+}
+function updateMiniMap() {
+ // Update player marker position on minimap
+ playerMarker.x = miniMap.x + player.x * CELL_SIZE * MINI_MAP_SCALE;
+ playerMarker.y = miniMap.y + player.y * CELL_SIZE * MINI_MAP_SCALE;
+ // Draw player direction indicator
+ var dirX = Math.cos(player.dir) * 15;
+ var dirY = Math.sin(player.dir) * 15;
+}
+// Game update method
+game.update = function () {
+ var currentTime = Date.now();
+ var deltaTime = currentTime - lastTime;
+ lastTime = currentTime;
+ // Update controls
+ updateControls();
+ // Update player
+ updatePlayerMovement(deltaTime);
+ // Update projectiles
+ updateProjectiles(deltaTime);
+ // Update raycast view
+ rayCasting();
+};
+// Game initialization
+setupGame();
+// Event handlers
+game.down = function (x, y, obj) {
+ // Handle general screen press
+};
+game.up = function (x, y, obj) {
+ // Handle general screen release
+};
+game.move = function (x, y, obj) {
+ // Handle general screen move
+};
\ No newline at end of file
Fullscreen modern App Store landscape banner, 16:9, high definition, for a game titled "RayCaster Dungeon Crawler" and with the description "A first-person dungeon crawler using ray casting technology to create a pseudo-3D experience. Navigate maze-like dungeons, defeat monsters, and collect treasures as you explore increasingly challenging levels with authentic retro visuals.". No text on banner!
Sword. In-Game asset. 2d. High contrast. No shadows
Tan wall. In-Game asset. 2d. High contrast. No shadows
Make a bunch of empty space above it.