User prompt
Make them scroll the other way
User prompt
Make the walls and ground scroll with the enemy’s and the coins
User prompt
Make more lanes
User prompt
Make the stuff all bigger
User prompt
Add better controls
Code edit (1 edits merged)
Please save this source code
User prompt
Raycast Runner
Initial prompt
Make a fake 3d game using raycasting
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ // Represents a single collectible in the corridor var Collectible = Container.expand(function () { var self = Container.call(this); var sprite = self.attachAsset('collectible', { anchorX: 0.5, anchorY: 1 }); self.lane = 1; self.depth = 0; self.type = 'collectible'; self.active = true; self.getHitbox = function () { return { x: self.x - sprite.width / 2, y: self.y - sprite.height, w: sprite.width, h: sprite.height }; }; return self; }); // Represents a single obstacle in the corridor var Obstacle = Container.expand(function () { var self = Container.call(this); var sprite = self.attachAsset('obstacle', { anchorX: 0.5, anchorY: 1 }); // Logical position in corridor space self.lane = 1; // 0=left, 1=center, 2=right self.depth = 0; // Distance from player (z) self.type = 'obstacle'; self.active = true; // For collision detection self.getHitbox = function () { // Returns {x, y, w, h} in screen space return { x: self.x - sprite.width / 2, y: self.y - sprite.height, w: sprite.width, h: sprite.height }; }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x000000 }); /**** * Game Code ****/ // --- Raycasting & Corridor Parameters --- // Corridor wall slice (vertical rectangle, will be stretched/scaled for perspective) // Floor slice (horizontal rectangle, will be stretched/scaled for perspective) // Ceiling slice (horizontal rectangle, will be stretched/scaled for perspective) // Obstacle (box) // Collectible (ellipse) var corridor = { width: 3, // 3 lanes: 0=left, 1=center, 2=right laneWidth: 700, // virtual lane width in world units (increased for bigger lanes) depthSlices: 16, // how many slices to render for perspective maxDepth: 16, // how far to render (in slices) fov: Math.PI / 3, // 60 deg field of view wallColor: 0x4444aa, floorColor: 0x222222, ceilingColor: 0x111133 }; // Player state var player = { lane: 1, // 0=left, 1=center, 2=right moving: false, moveTarget: 1, // target lane moveTween: null, alive: true }; // Game state var speed = 0.18; // units per frame (z axis) var speedIncrease = 0.00004; // per frame var maxSpeed = 0.38; var objects = []; // all obstacles and collectibles var spawnTimer = 0; var spawnInterval = 38; // frames between spawns var score = 0; var distance = 0; // how far player has run (for difficulty) var lastTouchX = null; // --- GUI --- var scoreTxt = new Text2('0', { size: 220, fill: "#fff" }); scoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(scoreTxt); var distTxt = new Text2('0m', { size: 120, fill: "#aaa" }); distTxt.anchor.set(0.5, 0); LK.gui.top.addChild(distTxt); distTxt.y = 220; // --- Corridor rendering nodes --- var wallNodes = []; var floorNodes = []; var ceilingNodes = []; for (var i = 0; i < corridor.depthSlices; i++) { // Left wall var wl = LK.getAsset('wallSlice', { anchorX: 0.5, anchorY: 1 }); game.addChild(wl); wallNodes.push(wl); // Right wall var wr = LK.getAsset('wallSlice', { anchorX: 0.5, anchorY: 1 }); game.addChild(wr); wallNodes.push(wr); // Floor var fl = LK.getAsset('floorSlice', { anchorX: 0.5, anchorY: 0 }); game.addChild(fl); floorNodes.push(fl); // Ceiling var cl = LK.getAsset('ceilingSlice', { anchorX: 0.5, anchorY: 1 }); game.addChild(cl); ceilingNodes.push(cl); } // --- Helper: Perspective projection for fake-3D --- function projectZ(z) { // Returns scale and y offset for a given depth (z) // Camera is at y = horizonY, looking down corridor var screenH = 2732; var horizonY = screenH * 0.42; var vanishY = horizonY; var baseScale = 1.0; // Perspective: scale = nearPlane / (z + nearPlane) var near = 1.2; var scale = near / (z + near); var y = vanishY + screenH * 0.32 / (z + 0.7); return { scale: scale, y: y }; } // --- Helper: X position for lane at given depth --- function laneX(lane, z) { // Center lane is at 2048/2, left/right offset by laneWidth * scale var centerX = 2048 / 2; var laneOffset = (lane - 1) * corridor.laneWidth; var p = projectZ(z); return centerX + laneOffset * p.scale; } // --- Spawning obstacles and collectibles --- function spawnObject() { // Randomly choose obstacle or collectible var isObstacle = Math.random() < 0.7; var lane = Math.floor(Math.random() * corridor.width); var depth = corridor.maxDepth + 2; // spawn just beyond farthest slice var obj; if (isObstacle) { obj = new Obstacle(); } else { obj = new Collectible(); } obj.lane = lane; obj.depth = depth; obj.active = true; objects.push(obj); game.addChild(obj); } // --- Input: Swipe or tap to move left/right --- game.down = function (x, y, obj) { lastTouchX = x; // Tap-to-move: if not moving, tap left/right of center to move if (!player.moving && player.alive) { var centerX = 2048 / 2; if (x < centerX - 250 && player.lane > 0) { movePlayerTo(player.lane - 1); } else if (x > centerX + 250 && player.lane < 2) { movePlayerTo(player.lane + 1); } } }; game.move = function (x, y, obj) { if (lastTouchX === null) return; var dx = x - lastTouchX; if (Math.abs(dx) > 180 && player.alive) { // If not moving, move immediately if (!player.moving) { if (dx > 0 && player.lane < 2) { movePlayerTo(player.lane + 1); } else if (dx < 0 && player.lane > 0) { movePlayerTo(player.lane - 1); } lastTouchX = x; } else { // If already moving, queue the next move if (typeof player.queuedMove === "undefined") player.queuedMove = null; if (dx > 0 && player.lane < 2) { player.queuedMove = player.lane + 1; } else if (dx < 0 && player.lane > 0) { player.queuedMove = player.lane - 1; } lastTouchX = x; } } }; game.up = function (x, y, obj) { lastTouchX = null; }; // --- Move player to lane with tween --- function movePlayerTo(targetLane) { if (player.moving || !player.alive) return; player.moveTarget = targetLane; player.moving = true; var start = player.lane; var end = targetLane; var t = { v: start }; player.moveTween = tween(t, { v: end }, { duration: 180, easing: tween.cubicOut, onFinish: function onFinish() { player.lane = end; player.moving = false; // If a move was queued during the tween, perform it now if (typeof player.queuedMove !== "undefined" && player.queuedMove !== null && player.queuedMove !== player.lane) { var nextLane = player.queuedMove; player.queuedMove = null; movePlayerTo(nextLane); } } }); // We'll interpolate player.lane visually in render player.laneTweenObj = t; } // --- Main update loop --- game.update = function () { if (!player.alive) return; // Increase speed over time speed += speedIncrease; if (speed > maxSpeed) speed = maxSpeed; distance += speed; distTxt.setText(Math.floor(distance) + "m"); // Spawn new objects spawnTimer--; if (spawnTimer <= 0) { spawnObject(); spawnTimer = spawnInterval - Math.floor(distance / 60); if (spawnTimer < 18) spawnTimer = 18; } // Move objects closer (decrease depth) for (var i = objects.length - 1; i >= 0; i--) { var obj = objects[i]; obj.depth -= speed; if (obj.depth < 0.2) { // Passed player, remove obj.destroy(); objects.splice(i, 1); continue; } // Project to screen var p = projectZ(obj.depth); obj.scaleX = obj.scaleY = p.scale * 1.2; obj.x = laneX(obj.lane, obj.depth); obj.y = p.y + 320 * p.scale; obj.visible = obj.depth > 0.2 && obj.depth < corridor.maxDepth + 2; } // Player lane interpolation (for smooth movement) var visLane = player.lane; if (player.moving && player.laneTweenObj) { visLane = player.laneTweenObj.v; } // --- Render corridor slices --- for (var i = 0; i < corridor.depthSlices; i++) { // Offset z by distance to make walls/floor/ceiling scroll opposite to objects var z = i + 1.2 - distance % 1; // reverse scroll direction var p = projectZ(z); // Walls var wallW = 80 * p.scale * 2.2; var wallH = 800 * p.scale * 2.2; // Left wall var wl = wallNodes[i * 2]; wl.x = laneX(0, z) - corridor.laneWidth * p.scale / 2 - wallW / 2; wl.y = p.y + wallH; wl.width = wallW; wl.height = wallH; wl.visible = true; // Right wall var wr = wallNodes[i * 2 + 1]; wr.x = laneX(2, z) + corridor.laneWidth * p.scale / 2 + wallW / 2; wr.y = p.y + wallH; wr.width = wallW; wr.height = wallH; wr.visible = true; // Floor var fl = floorNodes[i]; fl.x = 2048 / 2; fl.y = p.y + wallH; fl.width = corridor.laneWidth * corridor.width * p.scale * 1.1; fl.height = 80 * p.scale * 2.2; fl.visible = true; // Ceiling var cl = ceilingNodes[i]; cl.x = 2048 / 2; cl.y = p.y - 80 * p.scale * 1.2; cl.width = corridor.laneWidth * corridor.width * p.scale * 1.1; cl.height = 80 * p.scale * 2.2; cl.visible = true; } // --- Collision detection --- // Player is always at z=0, y=bottom of screen var playerZ = 0.7; var playerP = projectZ(playerZ); var playerX = laneX(visLane, playerZ); var playerY = playerP.y + 320 * playerP.scale; var playerW = 340 * playerP.scale * 1.2; var playerH = 420 * playerP.scale * 1.2; // For debugging, could render a player shadow here for (var i = objects.length - 1; i >= 0; i--) { var obj = objects[i]; if (!obj.active) continue; // Only check collision if close enough if (obj.depth < 1.2 && obj.depth > 0.2) { var dx = Math.abs(obj.x - playerX); var dy = Math.abs(obj.y - playerY); var hitW = (playerW + obj.width) / 2; var hitH = (playerH + obj.height) / 2; if (dx < hitW * 0.7 && dy < hitH * 0.7) { if (obj.type === 'obstacle') { // Hit obstacle: game over player.alive = false; LK.effects.flashScreen(0xff0000, 900); LK.showGameOver(); return; } else if (obj.type === 'collectible') { // Collect item obj.active = false; score += 1; LK.setScore(score); scoreTxt.setText(score); tween(obj, { scaleX: 2.2, scaleY: 2.2, alpha: 0 }, { duration: 220, easing: tween.cubicOut, onFinish: function onFinish() { obj.destroy(); } }); objects.splice(i, 1); } } } } }; // --- Reset game state on new game --- function resetGame() { // Remove all objects for (var i = 0; i < objects.length; i++) { objects[i].destroy(); } objects = []; player.lane = 1; player.moving = false; player.moveTarget = 1; player.moveTween = null; player.alive = true; speed = 0.18; score = 0; distance = 0; spawnTimer = 18; scoreTxt.setText('0'); distTxt.setText('0m'); } resetGame(); // --- LK handles game over and reset automatically, but listen for new game start --- LK.on('gameStart', function () { resetGame(); });
===================================================================
--- original.js
+++ change.js
@@ -301,10 +301,10 @@
visLane = player.laneTweenObj.v;
}
// --- Render corridor slices ---
for (var i = 0; i < corridor.depthSlices; i++) {
- // Offset z by distance to make walls/floor/ceiling scroll with objects
- var z = i + 1.2 + distance % 1; // distance mod 1 for smooth scroll
+ // Offset z by distance to make walls/floor/ceiling scroll opposite to objects
+ var z = i + 1.2 - distance % 1; // reverse scroll direction
var p = projectZ(z);
// Walls
var wallW = 80 * p.scale * 2.2;
var wallH = 800 * p.scale * 2.2;