User prompt
Make the monster move towards the player
User prompt
Make it so the projectile is hooked up to the 2d plane and when I shoot it, moves forwards. delete the old code for it.
User prompt
Make it so there is never a place that is walled off in a level
User prompt
Add a yo element that shows how many more monsters I need to get
User prompt
Make the gate go to a random position each time a level generates
User prompt
Make the level # reset when the game resets
User prompt
Add a way to exit to the next level
User prompt
Add a gate to exit the level
User prompt
Please fix the bug: 'TypeError: undefined is not an object (evaluating 'tween.Ease.quartOut')' in or related to this line: 'tween(monster, {' Line Number: 1070
User prompt
Please fix the bug: 'TypeError: easing is not a function. (In 'easing(t)', 'easing' is "easeOut")' in or related to this line: 'if (Math.abs(angle) < HALF_FOV && dist < MAX_RENDER_DISTANCE) {' Line Number: 658
User prompt
Make the enemies glide towards the player
User prompt
Make it so the enemies go towards the player
User prompt
Give the projectile a 3 second cool down ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Make it so there can only be 2 projectiles on screen at a time
User prompt
Make the clones of the projectile spawn on the other side of it.
User prompt
Make them not spin
User prompt
Replace all the projectile code with code that make the projectile move from right to left on screen when you press the button
User prompt
Make it so the projectile shows on the right side of the screen than glides to the right when you press the button
User prompt
Make it so the projectile stays shown when I hold the button
User prompt
Make the projectile show when you press the button
User prompt
Delete the cooldown for the weapon
User prompt
Give the projectile a lot more range
User prompt
Give the projectile much more range and slow it down a lot
User prompt
Move the map to the middle too of the screen
User prompt
Make the projectile flash on screen for longer ↪💡 Consider importing and using the following plugins: @upit/tween.v1
/**** * 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, scaleX: 2.5, scaleY: 2.5 }); var arrowSprite = self.attachAsset('buttonArrow', { anchorX: 0.5, anchorY: 0.5, scaleX: 2.0, scaleY: 2.0 }); // 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, scaleX: 3.5, scaleY: 3.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, scaleX: 0.5, scaleY: 0.5 }); self.dirX = 0; self.dirY = 0; self.speed = 0.2; self.range = 5; self.distanceTraveled = 0; self.lastDistanceTraveled = 0; self.visible = true; self.update = function (deltaTime) { // Store last distance for collision detection self.lastDistanceTraveled = self.distanceTraveled; // Update distance traveled based on speed var moveDist = self.speed * deltaTime; self.distanceTraveled += moveDist; // Rotate projectile for visual effect projectileSprite.rotation += 0.1; // Create pulsating effect for better visibility at long distances if (LK.ticks % 20 === 0) { tween(projectileSprite, { scaleX: 1.2, scaleY: 1.2 }, { duration: 200, onFinish: function onFinish() { tween(projectileSprite, { scaleX: 1.0, scaleY: 1.0 }, { duration: 200 }); } }); } // Return true if projectile has reached its maximum range 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.002; // Reduced from 0.005 to make movement slower var PLAYER_TURN_SPEED = 0.002; // Reduced from 0.005 to make turning slower 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 with increased size and more centered position controlButtons.up = new ControlButton('up'); controlButtons.up.x = 400; controlButtons.up.y = 2732 - 700; game.addChild(controlButtons.up); controlButtons.right = new ControlButton('right'); controlButtons.right.x = 650; controlButtons.right.y = 2732 - 500; game.addChild(controlButtons.right); controlButtons.down = new ControlButton('down'); controlButtons.down.x = 400; controlButtons.down.y = 2732 - 300; game.addChild(controlButtons.down); controlButtons.left = new ControlButton('left'); controlButtons.left.x = 150; controlButtons.left.y = 2732 - 500; game.addChild(controlButtons.left); // Create attack button more centered on the right side controlButtons.attack = new ControlButton('attack'); controlButtons.attack.x = 2048 - 400; controlButtons.attack.y = 2732 - 500; 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 || !map[mapCheckY] || !map[mapCheckY][mapCheckX]) { 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 || !map[mapCheckY] || !map[mapCheckY][mapCheckX]) { 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 && map[cellY] && map[cellY][cellX]) { 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(); // Start the projectile at a position slightly in front of the player projectile.dirX = Math.cos(player.dir); projectile.dirY = Math.sin(player.dir); // Initialize distanceTraveled to a small value so it starts in front of the player projectile.distanceTraveled = 0.5; // Calculate initial world position var mapX = player.x + projectile.distanceTraveled * projectile.dirX; var mapY = player.y + projectile.distanceTraveled * projectile.dirY; // Calculate initial screen position var dirToProjectile = Math.atan2(mapY - player.y, mapX - player.x); var angleToPlayer = dirToProjectile - player.dir; // Normalize angle while (angleToPlayer < -Math.PI) angleToPlayer += Math.PI * 2; while (angleToPlayer > Math.PI) angleToPlayer -= Math.PI * 2; // Calculate screen position var screenX = (0.5 + angleToPlayer / FOV) * 2048; var distToProjectile = projectile.distanceTraveled; var height = WALL_HEIGHT_FACTOR / distToProjectile; // Set initial position on screen projectile.x = screenX; projectile.y = 2732 / 2; var scale = height / 200; projectile.scale.set(scale, scale); // Adjust speed and range settings projectile.speed = 0.02; // Reduced speed to make projectiles last longer projectile.range = 20; // Increased range to allow projectiles to travel farther // Add visual pulse effect to make projectile more visible tween(projectile, { alpha: 0.5, scaleX: scale * 1.5, scaleY: scale * 1.5 }, { duration: 300, onFinish: function onFinish() { tween(projectile, { alpha: 1, scaleX: scale, scaleY: scale }, { duration: 300 }); } }); // Add to the game projectiles.push(projectile); game.addChild(projectile); } function updateProjectiles(deltaTime) { for (var i = projectiles.length - 1; i >= 0; i--) { var projectile = projectiles[i]; // Store last position for collision detection var lastMapX = player.x + projectile.lastDistanceTraveled * projectile.dirX; var lastMapY = player.y + projectile.lastDistanceTraveled * projectile.dirY; // Update projectile position var remove = projectile.update(deltaTime); // Calculate the projectile's current world coordinates var mapX = player.x + projectile.distanceTraveled * projectile.dirX; var mapY = player.y + projectile.distanceTraveled * projectile.dirY; // 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 || !map[cellY] || !map[cellY][cellX]) { 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; } } // Ray casting check for the projectile var rayHit = castRayToPoint(player.x, player.y, mapX, mapY); // Update projectile visual position in the 3D view based on its position in the map var dirToProjectile = Math.atan2(mapY - player.y, mapX - player.x); var angleToPlayer = dirToProjectile - player.dir; // Normalize angle while (angleToPlayer < -Math.PI) angleToPlayer += Math.PI * 2; while (angleToPlayer > Math.PI) angleToPlayer -= Math.PI * 2; // Calculate screen position if (Math.abs(angleToPlayer) < HALF_FOV && (!rayHit.hit || rayHit.dist > projectile.distanceTraveled - 0.1)) { var screenX = (0.5 + angleToPlayer / FOV) * 2048; var distToProjectile = projectile.distanceTraveled; var height = WALL_HEIGHT_FACTOR / distToProjectile; projectile.x = screenX; projectile.y = 2732 / 2; var scale = height / 200; projectile.scale.set(scale, scale); projectile.visible = true; } else { projectile.visible = false; } 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 };
/****
* 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,
scaleX: 2.5,
scaleY: 2.5
});
var arrowSprite = self.attachAsset('buttonArrow', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2.0,
scaleY: 2.0
});
// 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,
scaleX: 3.5,
scaleY: 3.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,
scaleX: 0.5,
scaleY: 0.5
});
self.dirX = 0;
self.dirY = 0;
self.speed = 0.2;
self.range = 5;
self.distanceTraveled = 0;
self.lastDistanceTraveled = 0;
self.visible = true;
self.update = function (deltaTime) {
// Store last distance for collision detection
self.lastDistanceTraveled = self.distanceTraveled;
// Update distance traveled based on speed
var moveDist = self.speed * deltaTime;
self.distanceTraveled += moveDist;
// Rotate projectile for visual effect
projectileSprite.rotation += 0.1;
// Create pulsating effect for better visibility at long distances
if (LK.ticks % 20 === 0) {
tween(projectileSprite, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 200,
onFinish: function onFinish() {
tween(projectileSprite, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 200
});
}
});
}
// Return true if projectile has reached its maximum range
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.002; // Reduced from 0.005 to make movement slower
var PLAYER_TURN_SPEED = 0.002; // Reduced from 0.005 to make turning slower
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 with increased size and more centered position
controlButtons.up = new ControlButton('up');
controlButtons.up.x = 400;
controlButtons.up.y = 2732 - 700;
game.addChild(controlButtons.up);
controlButtons.right = new ControlButton('right');
controlButtons.right.x = 650;
controlButtons.right.y = 2732 - 500;
game.addChild(controlButtons.right);
controlButtons.down = new ControlButton('down');
controlButtons.down.x = 400;
controlButtons.down.y = 2732 - 300;
game.addChild(controlButtons.down);
controlButtons.left = new ControlButton('left');
controlButtons.left.x = 150;
controlButtons.left.y = 2732 - 500;
game.addChild(controlButtons.left);
// Create attack button more centered on the right side
controlButtons.attack = new ControlButton('attack');
controlButtons.attack.x = 2048 - 400;
controlButtons.attack.y = 2732 - 500;
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 || !map[mapCheckY] || !map[mapCheckY][mapCheckX]) {
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 || !map[mapCheckY] || !map[mapCheckY][mapCheckX]) {
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 && map[cellY] && map[cellY][cellX]) {
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();
// Start the projectile at a position slightly in front of the player
projectile.dirX = Math.cos(player.dir);
projectile.dirY = Math.sin(player.dir);
// Initialize distanceTraveled to a small value so it starts in front of the player
projectile.distanceTraveled = 0.5;
// Calculate initial world position
var mapX = player.x + projectile.distanceTraveled * projectile.dirX;
var mapY = player.y + projectile.distanceTraveled * projectile.dirY;
// Calculate initial screen position
var dirToProjectile = Math.atan2(mapY - player.y, mapX - player.x);
var angleToPlayer = dirToProjectile - player.dir;
// Normalize angle
while (angleToPlayer < -Math.PI) angleToPlayer += Math.PI * 2;
while (angleToPlayer > Math.PI) angleToPlayer -= Math.PI * 2;
// Calculate screen position
var screenX = (0.5 + angleToPlayer / FOV) * 2048;
var distToProjectile = projectile.distanceTraveled;
var height = WALL_HEIGHT_FACTOR / distToProjectile;
// Set initial position on screen
projectile.x = screenX;
projectile.y = 2732 / 2;
var scale = height / 200;
projectile.scale.set(scale, scale);
// Adjust speed and range settings
projectile.speed = 0.02; // Reduced speed to make projectiles last longer
projectile.range = 20; // Increased range to allow projectiles to travel farther
// Add visual pulse effect to make projectile more visible
tween(projectile, {
alpha: 0.5,
scaleX: scale * 1.5,
scaleY: scale * 1.5
}, {
duration: 300,
onFinish: function onFinish() {
tween(projectile, {
alpha: 1,
scaleX: scale,
scaleY: scale
}, {
duration: 300
});
}
});
// Add to the game
projectiles.push(projectile);
game.addChild(projectile);
}
function updateProjectiles(deltaTime) {
for (var i = projectiles.length - 1; i >= 0; i--) {
var projectile = projectiles[i];
// Store last position for collision detection
var lastMapX = player.x + projectile.lastDistanceTraveled * projectile.dirX;
var lastMapY = player.y + projectile.lastDistanceTraveled * projectile.dirY;
// Update projectile position
var remove = projectile.update(deltaTime);
// Calculate the projectile's current world coordinates
var mapX = player.x + projectile.distanceTraveled * projectile.dirX;
var mapY = player.y + projectile.distanceTraveled * projectile.dirY;
// 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 || !map[cellY] || !map[cellY][cellX]) {
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;
}
}
// Ray casting check for the projectile
var rayHit = castRayToPoint(player.x, player.y, mapX, mapY);
// Update projectile visual position in the 3D view based on its position in the map
var dirToProjectile = Math.atan2(mapY - player.y, mapX - player.x);
var angleToPlayer = dirToProjectile - player.dir;
// Normalize angle
while (angleToPlayer < -Math.PI) angleToPlayer += Math.PI * 2;
while (angleToPlayer > Math.PI) angleToPlayer -= Math.PI * 2;
// Calculate screen position
if (Math.abs(angleToPlayer) < HALF_FOV && (!rayHit.hit || rayHit.dist > projectile.distanceTraveled - 0.1)) {
var screenX = (0.5 + angleToPlayer / FOV) * 2048;
var distToProjectile = projectile.distanceTraveled;
var height = WALL_HEIGHT_FACTOR / distToProjectile;
projectile.x = screenX;
projectile.y = 2732 / 2;
var scale = height / 200;
projectile.scale.set(scale, scale);
projectile.visible = true;
} else {
projectile.visible = false;
}
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
};
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.