Neon Dash 3D
by AtomicBolt46655 lines15.6 KB🛠️ Three.js (3D graphics)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Neon Dash 3D</title>
<style>
* { box-sizing: border-box; }
body {
margin: 0;
overflow: hidden;
background: #111827;
font-family: Arial, sans-serif;
color: white;
}
canvas { display: block; }
#hud {
position: absolute;
top: 15px;
left: 15px;
z-index: 2;
display: flex;
gap: 12px;
flex-wrap: wrap;
}
.stat {
background: #101827dd;
padding: 10px 15px;
border: 2px solid #36d9ff;
border-radius: 12px;
font-weight: bold;
font-size: 17px;
}
#message {
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
background: #07101dbb;
z-index: 5;
text-align: center;
}
.panel {
background: #17243a;
border: 3px solid #36d9ff;
border-radius: 22px;
padding: 28px;
width: min(90%, 420px);
box-shadow: 0 0 30px #00bfff66;
}
h1 {
color: #36d9ff;
font-size: 38px;
margin: 0 0 12px;
}
button {
background: #20c9e8;
color: #061321;
border: 0;
padding: 13px 22px;
font-size: 18px;
font-weight: bold;
border-radius: 12px;
cursor: pointer;
margin-top: 12px;
}
button:hover { background: #83edff; }
#controls {
position: absolute;
bottom: 20px;
left: 0;
right: 0;
display: flex;
justify-content: space-between;
padding: 0 20px;
z-index: 3;
pointer-events: none;
}
.control-group {
display: flex;
gap: 10px;
}
.control {
pointer-events: auto;
width: 58px;
height: 58px;
padding: 0;
border-radius: 50%;
background: #14253dcc;
color: white;
border: 2px solid #36d9ff;
font-size: 24px;
touch-action: none;
}
.small {
font-size: 13px;
color: #cbd5e1;
line-height: 1.5;
}
</style>
</head>
<body>
<div id="hud">
<div class="stat">⭐ Score: <span id="score">0</span></div>
<div class="stat">🪙 Coins: <span id="coins">0</span></div>
<div class="stat">⚡ Speed: <span id="speed">1</span></div>
</div>
<div id="message">
<div class="panel">
<h1 id="title">NEON DASH 3D</h1>
<p id="description">
Run through the city, dodge obstacles and collect coins!
</p>
<p class="small">
A / D or ← / → = Move<br>
SPACE or ↑ = Jump<br>
S or ↓ = Slide
</p>
<button id="startButton">START RUNNING</button>
</div>
</div>
<div id="controls">
<div class="control-group">
<button class="control" id="left">◀</button>
<button class="control" id="right">▶</button>
</div>
<div class="control-group">
<button class="control" id="slide">▼</button>
<button class="control" id="jump">▲</button>
</div>
</div>
<script type="importmap">
{
"imports": {
"three": "https://cdn.jsdelivr.net/npm/three@0.160.0/build/three.module.js"
}
}
</script>
<script type="module">
import * as THREE from "three";
// ---------- GAME SETUP ----------
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x87ceeb);
scene.fog = new THREE.Fog(0x87ceeb, 35, 125);
const camera = new THREE.PerspectiveCamera(
65, window.innerWidth / window.innerHeight, 0.1, 200
);
camera.position.set(0, 7, 13);
camera.lookAt(0, 1, -8);
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
document.body.appendChild(renderer.domElement);
scene.add(new THREE.HemisphereLight(0xffffff, 0x526078, 2));
const sunlight = new THREE.DirectionalLight(0xffffff, 2.3);
sunlight.position.set(-8, 15, 8);
sunlight.castShadow = true;
scene.add(sunlight);
const laneX = [-2.5, 0, 2.5];
const playerZ = 5;
let playerLane = 1;
let targetX = 0;
let jumping = false;
let sliding = false;
let jumpTime = 0;
let slideTime = 0;
let running = false;
let gameOver = false;
let score = 0;
let coins = 0;
let speed = 22;
let distance = 0;
let spawnDistance = 0;
let objects = [];
let scenery = [];
let lastTime = 0;
// ---------- MATERIALS ----------
const materials = {
road: new THREE.MeshStandardMaterial({ color: 0x303b4d }),
line: new THREE.MeshStandardMaterial({ color: 0xffffff }),
rail: new THREE.MeshStandardMaterial({ color: 0x777f8c, metalness: 0.7 }),
blue: new THREE.MeshStandardMaterial({ color: 0x168de0 }),
red: new THREE.MeshStandardMaterial({ color: 0xff4d58 }),
yellow: new THREE.MeshStandardMaterial({ color: 0xffd52a }),
purple: new THREE.MeshStandardMaterial({ color: 0x884dff }),
green: new THREE.MeshStandardMaterial({ color: 0x24d18c }),
skin: new THREE.MeshStandardMaterial({ color: 0xffc28c }),
dark: new THREE.MeshStandardMaterial({ color: 0x182238 }),
white: new THREE.MeshStandardMaterial({ color: 0xffffff }),
gold: new THREE.MeshStandardMaterial({
color: 0xffc928, metalness: 0.7, roughness: 0.2
})
};
// ---------- GROUND ----------
const ground = new THREE.Mesh(
new THREE.BoxGeometry(9, 0.3, 150),
materials.road
);
ground.position.set(0, -0.2, -40);
ground.receiveShadow = true;
scene.add(ground);
// Lane markings
for (const x of [-1.25, 1.25]) {
for (let z = 10; z > -100; z -= 8) {
const stripe = new THREE.Mesh(
new THREE.BoxGeometry(0.06, 0.03, 3),
materials.line
);
stripe.position.set(x, -0.025, z);
stripe.userData.movingStripe = true;
scene.add(stripe);
scenery.push(stripe);
}
}
// Track rails
for (const x of [-3.9, 3.9]) {
const rail = new THREE.Mesh(
new THREE.BoxGeometry(0.16, 0.35, 150),
materials.rail
);
rail.position.set(x, 0.05, -40);
scene.add(rail);
}
// ---------- CITY SCENERY ----------
function addBuilding(x, z) {
const height = 7 + Math.random() * 17;
const width = 4 + Math.random() * 3;
const building = new THREE.Group();
const body = new THREE.Mesh(
new THREE.BoxGeometry(width, height, 5),
new THREE.MeshStandardMaterial({
color: new THREE.Color().setHSL(
0.55 + Math.random() * 0.25, 0.35, 0.3
)
})
);
body.position.y = height / 2;
body.castShadow = true;
building.add(body);
const windowMaterial = new THREE.MeshStandardMaterial({
color: 0xffe59a,
emissive: 0x775522,
emissiveIntensity: 0.4
});
for (let y = 2; y < height - 1; y += 2.5) {
for (let wx = -width / 2 + 0.8; wx < width / 2 - 0.4; wx += 1.3) {
const windowMesh = new THREE.Mesh(
new THREE.BoxGeometry(0.55, 0.9, 0.08),
windowMaterial
);
windowMesh.position.set(wx, y, 2.53);
building.add(windowMesh);
}
}
building.position.set(x, 0, z);
scene.add(building);
scenery.push(building);
}
for (let z = -100; z < 25; z += 13) {
addBuilding(-9, z);
addBuilding(9, z - 5);
}
// ---------- PLAYER ----------
const player = new THREE.Group();
scene.add(player);
player.position.set(0, 0, playerZ);
const body = new THREE.Mesh(
new THREE.BoxGeometry(0.8, 1.1, 0.48),
materials.blue
);
body.position.y = 1.15;
body.castShadow = true;
player.add(body);
const head = new THREE.Mesh(
new THREE.SphereGeometry(0.36, 20, 16),
materials.skin
);
head.position.y = 1.95;
head.castShadow = true;
player.add(head);
const hair = new THREE.Mesh(
new THREE.SphereGeometry(0.37, 16, 12, 0, Math.PI * 2, 0, Math.PI / 2),
materials.dark
);
hair.position.y = 2.08;
player.add(hair);
const leftLeg = new THREE.Mesh(
new THREE.BoxGeometry(0.28, 0.65, 0.3),
materials.dark
);
leftLeg.position.set(-0.23, 0.35, 0);
player.add(leftLeg);
const rightLeg = leftLeg.clone();
rightLeg.position.x = 0.23;
player.add(rightLeg);
const leftArm = new THREE.Mesh(
new THREE.BoxGeometry(0.22, 0.7, 0.25),
materials.blue
);
leftArm.position.set(-0.52, 1.2, 0);
player.add(leftArm);
const rightArm = leftArm.clone();
rightArm.position.x = 0.52;
player.add(rightArm);
// ---------- OBSTACLES AND COINS ----------
function createObstacle(lane, z, type) {
const group = new THREE.Group();
let mesh;
if (type === "barrier") {
mesh = new THREE.Mesh(
new THREE.BoxGeometry(1.7, 0.85, 0.7),
materials.red
);
mesh.position.y = 0.5;
} else if (type === "arch") {
// A high overhead obstacle: slide underneath it.
const top = new THREE.Mesh(
new THREE.BoxGeometry(1.8, 0.45, 0.7),
materials.purple
);
top.position.y = 2.15;
group.add(top);
for (const x of [-0.72, 0.72]) {
const leg = new THREE.Mesh(
new THREE.BoxGeometry(0.22, 2.2, 0.7),
materials.purple
);
leg.position.set(x, 1.1, 0);
group.add(leg);
}
} else {
mesh = new THREE.Mesh(
new THREE.BoxGeometry(1.8, 2.6, 2.1),
materials.green
);
mesh.position.y = 1.3;
}
if (mesh) {
mesh.castShadow = true;
group.add(mesh);
}
group.position.set(laneX[lane], 0, z);
scene.add(group);
objects.push({ mesh: group, type, lane, z, kind: "obstacle" });
}
function createCoin(lane, z, height = 1.3) {
const coin = new THREE.Mesh(
new THREE.TorusGeometry(0.35, 0.12, 10, 20),
materials.gold
);
coin.position.set(laneX[lane], height, z);
coin.rotation.y = Math.PI / 2;
coin.castShadow = true;
scene.add(coin);
objects.push({
mesh: coin,
type: "coin",
lane,
z,
kind: "coin",
baseY: height
});
}
function spawnRow() {
const blockedLane = Math.floor(Math.random() * 3);
const obstacleTypes = ["barrier", "arch", "block"];
const type = obstacleTypes[Math.floor(Math.random() * 3)];
createObstacle(blockedLane, -90, type);
// Add a coin trail in one of the other lanes.
let coinLane = (blockedLane + 1 + Math.floor(Math.random() * 2)) % 3;
for (let i = 0; i < 5; i++) {
createCoin(coinLane, -90 - i * 2.2, 1.2);
}
// Sometimes add coins in the remaining lane too.
if (Math.random() < 0.45) {
const otherLane = 3 - blockedLane - coinLane;
for (let i = 0; i < 3; i++) {
createCoin(otherLane, -93 - i * 2.2, 1.2);
}
}
}
// ---------- CONTROLS ----------
function moveLeft() {
if (!running) return;
playerLane = Math.max(0, playerLane - 1);
targetX = laneX[playerLane];
}
function moveRight() {
if (!running) return;
playerLane = Math.min(2, playerLane + 1);
targetX = laneX[playerLane];
}
function jump() {
if (!running || jumping || sliding) return;
jumping = true;
jumpTime = 0;
}
function slide() {
if (!running || sliding || jumping) return;
sliding = true;
slideTime = 0;
}
document.addEventListener("keydown", (e) => {
if (["ArrowLeft", "ArrowRight", "ArrowUp", "ArrowDown", " "].includes(e.key)) {
e.preventDefault();
}
if (e.repeat) return;
if (e.key === "ArrowLeft" || e.key.toLowerCase() === "a") moveLeft();
if (e.key === "ArrowRight" || e.key.toLowerCase() === "d") moveRight();
if (e.key === "ArrowUp" || e.key === " ") jump();
if (e.key === "ArrowDown" || e.key.toLowerCase() === "s") slide();
});
function bindButton(id, action) {
const button = document.getElementById(id);
button.addEventListener("pointerdown", (e) => {
e.preventDefault();
action();
});
}
bindButton("left", moveLeft);
bindButton("right", moveRight);
bindButton("jump", jump);
bindButton("slide", slide);
// ---------- GAME LOGIC ----------
function updateHUD() {
document.getElementById("score").textContent = Math.floor(score);
document.getElementById("coins").textContent = coins;
document.getElementById("speed").textContent =
(speed / 22).toFixed(1) + "x";
}
function endGame() {
running = false;
gameOver = true;
document.getElementById("title").textContent = "GAME OVER!";
document.getElementById("description").innerHTML =
"⭐ Score: " + Math.floor(score) +
"<br>🪙 Coins collected: " + coins +
"<br><br>Can you beat your score?";
document.getElementById("startButton").textContent = "PLAY AGAIN";
document.getElementById("message").style.display = "flex";
}
function startGame() {
for (const obj of objects) scene.remove(obj.mesh);
objects = [];
score = 0;
coins = 0;
speed = 22;
distance = 0;
spawnDistance = 0;
playerLane = 1;
targetX = 0;
player.position.set(0, 0, playerZ);
player.scale.set(1, 1, 1);
jumping = false;
sliding = false;
gameOver = false;
running = true;
document.getElementById("message").style.display = "none";
updateHUD();
}
document.getElementById("startButton").addEventListener("click", startGame);
// ---------- ANIMATION ----------
const clock = new THREE.Clock();
function animate() {
requestAnimationFrame(animate);
const delta = Math.min(clock.getDelta(), 0.04);
if (running) {
distance += speed * delta;
score += speed * delta * 0.5;
speed = Math.min(42, 22 + distance * 0.018);
// Move player smoothly between lanes.
player.position.x += (targetX - player.position.x) *
Math.min(1, delta * 13);
// Jump animation.
if (jumping) {
jumpTime += delta;
const t = jumpTime / 0.85;
player.position.y = Math.sin(Math.PI * t) * 2.3;
if (t >= 1) {
jumping = false;
player.position.y = 0;
}
}
// Slide animation.
if (sliding) {
slideTime += delta;
player.scale.y = 0.48;
player.position.y = 0;
if (slideTime >= 0.75) {
sliding = false;
player.scale.y = 1;
}
} else if (!jumping) {
player.scale.y = 1;
}
// Running animation.
if (!jumping && !sliding) {
leftLeg.rotation.x = Math.sin(distance * 0.35) * 0.65;
rightLeg.rotation.x = -leftLeg.rotation.x;
leftArm.rotation.x = -leftLeg.rotation.x;
rightArm.rotation.x = leftLeg.rotation.x;
}
// Move lane markings.
for (const item of scenery) {
if (item.userData.movingStripe) {
item.position.z += speed * delta;
if (item.position.z > 14) item.position.z -= 112;
} else {
item.position.z += speed * delta;
if (item.position.z > 30) {
item.position.z -= 130;
}
}
}
// Spawn obstacle rows.
spawnDistance += speed * delta;
if (spawnDistance > 23) {
spawnDistance = 0;
spawnRow();
}
// Move obstacles and coins toward the player.
for (let i = objects.length - 1; i >= 0; i--) {
const obj = objects[i];
obj.z += speed * delta;
obj.mesh.position.z = obj.z;
if (obj.kind === "coin") {
obj.mesh.rotation.x += delta * 3;
obj.mesh.position.y = obj.baseY + Math.sin(distance * 0.1 + i) * 0.12;
// Collect coin.
if (
Math.abs(obj.z - playerZ) < 1.25 &&
Math.abs(player.position.x - laneX[obj.lane]) < 0.85
) {
coins++;
score += 10;
scene.remove(obj.mesh);
objects.splice(i, 1);
continue;
}
} else {
// Check collision with an obstacle.
if (
Math.abs(obj.z - playerZ) < 1.0 &&
Math.abs(player.position.x - laneX[obj.lane]) < 0.85
) {
let hit = true;
if (obj.type === "barrier" && jumping && player.position.y > 0.9) {
hit = false;
}
if (obj.type === "arch" && sliding) {
hit = false;
}
if (hit) {
endGame();
}
}
}
// Remove objects behind the player.
if (obj.z > 18) {
scene.remove(obj.mesh);
objects.splice(i, 1);
}
}
updateHUD();
}
// Camera follows player gently.
camera.position.x += (player.position.x * 0.25 - camera.position.x) *
Math.min(1, delta * 3);
camera.lookAt(player.position.x * 0.15, 1.1, -8);
renderer.render(scene, camera);
}
animate();
// ---------- RESIZE ----------
window.addEventListener("resize", () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
</script>
</body>
</html>Game Source: Neon Dash 3D
Creator: AtomicBolt46
Libraries: three
Complexity: complex (655 lines, 15.6 KB)
The full source code is displayed above on this page.
Remix Instructions
To remix this game, copy the source code above and modify it. Add a ARCADELAB header at the top with "remix_of: neon-dash-3d-atomicbolt46" to link back to the original. Then publish at arcadelab.ai/publish.