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 ****/ // Collectible (ellipse) // Obstacle (box) // Ceiling slice (horizontal rectangle, will be stretched/scaled for perspective) // Floor slice (horizontal rectangle, will be stretched/scaled for perspective) // Corridor wall slice (vertical rectangle, will be stretched/scaled for perspective) // --- Raycasting & Corridor Parameters --- var corridor = { width: 3, // 3 lanes: 0=left, 1=center, 2=right laneWidth: 400, // virtual lane width in world units 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: 120, fill: "#fff" }); scoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(scoreTxt); var distTxt = new Text2('0m', { size: 60, fill: "#aaa" }); distTxt.anchor.set(0.5, 0); LK.gui.top.addChild(distTxt); distTxt.y = 120; // --- 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; }; game.move = function (x, y, obj) { if (lastTouchX === null) return; var dx = x - lastTouchX; if (Math.abs(dx) > 80 && !player.moving && player.alive) { // Swipe left/right if (dx > 0 && player.lane < 2) { movePlayerTo(player.lane + 1); } else if (dx < 0 && player.lane > 0) { movePlayerTo(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; } }); // 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++) { var z = i + 1.2; 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 = 180 * playerP.scale * 1.2; var playerH = 220 * 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
@@ -1,6 +1,384 @@
-/****
+/****
+* 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
+****/
+// Collectible (ellipse)
+// Obstacle (box)
+// Ceiling slice (horizontal rectangle, will be stretched/scaled for perspective)
+// Floor slice (horizontal rectangle, will be stretched/scaled for perspective)
+// Corridor wall slice (vertical rectangle, will be stretched/scaled for perspective)
+// --- Raycasting & Corridor Parameters ---
+var corridor = {
+ width: 3,
+ // 3 lanes: 0=left, 1=center, 2=right
+ laneWidth: 400,
+ // virtual lane width in world units
+ 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: 120,
+ fill: "#fff"
+});
+scoreTxt.anchor.set(0.5, 0);
+LK.gui.top.addChild(scoreTxt);
+var distTxt = new Text2('0m', {
+ size: 60,
+ fill: "#aaa"
+});
+distTxt.anchor.set(0.5, 0);
+LK.gui.top.addChild(distTxt);
+distTxt.y = 120;
+// --- 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;
+};
+game.move = function (x, y, obj) {
+ if (lastTouchX === null) return;
+ var dx = x - lastTouchX;
+ if (Math.abs(dx) > 80 && !player.moving && player.alive) {
+ // Swipe left/right
+ if (dx > 0 && player.lane < 2) {
+ movePlayerTo(player.lane + 1);
+ } else if (dx < 0 && player.lane > 0) {
+ movePlayerTo(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;
+ }
+ });
+ // 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++) {
+ var z = i + 1.2;
+ 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 = 180 * playerP.scale * 1.2;
+ var playerH = 220 * 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();
});
\ No newline at end of file