3D Runner Mobile
by GlitchBolt30440 lines9.8 KB🛠️ Three.js (3D graphics)
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<title>3D Runner Mobile</title>
<style>
* { box-sizing: border-box; }
body {
margin: 0;
overflow: hidden;
font-family: Arial, sans-serif;
background: #87ceeb;
touch-action: none;
}
canvas {
display: block;
width: 100%;
height: 100%;
}
#hud {
position: fixed;
top: 15px;
left: 15px;
color: white;
font-size: 20px;
font-weight: bold;
text-shadow: 2px 2px 4px black;
z-index: 2;
}
#controls {
position: fixed;
bottom: 22px;
left: 0;
right: 0;
display: flex;
justify-content: space-around;
align-items: center;
z-index: 3;
}
button {
border: 2px solid #ffffff99;
border-radius: 18px;
background: #162b49cc;
color: white;
font-size: 25px;
font-weight: bold;
width: 76px;
height: 65px;
touch-action: none;
user-select: none;
}
button:active {
background: #00aaff;
transform: scale(.94);
}
#overlay {
position: fixed;
inset: 0;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
background: #07182acb;
color: white;
text-align: center;
z-index: 5;
padding: 20px;
}
#overlay h1 { font-size: 34px; }
#overlay p { line-height: 1.6; }
#start {
width: auto;
padding: 0 28px;
background: #00a86b;
border: none;
}
</style>
</head>
<body>
<div id="hud">
🪙 Skor: <span id="score">0</span><br>
🏆 Terbaik: <span id="best">0</span>
</div>
<div id="controls">
<button id="left">◀</button>
<button id="jump">⬆</button>
<button id="right">▶</button>
</div>
<div id="overlay">
<h1 id="title">🏃 3D RUNNER</h1>
<p id="message">
Hindari rintangan dan kumpulkan koin!<br>
Gunakan tombol kiri, kanan, dan lompat.
</p>
<button id="start">▶ MULAI</button>
</div>
<script src="https://cdn.jsdelivr.net/npm/three@0.160.0/build/three.min.js"></script>
<script>
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x87ceeb);
scene.fog = new THREE.Fog(0x87ceeb, 25, 90);
const camera = new THREE.PerspectiveCamera(
65, window.innerWidth / window.innerHeight, 0.1, 150
);
camera.position.set(0, 5, 9);
camera.lookAt(0, 1, -5);
const renderer = new THREE.WebGLRenderer({
antialias: true,
powerPreference: "high-performance"
});
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 1.5));
renderer.shadowMap.enabled = false;
document.body.appendChild(renderer.domElement);
// Pencahayaan
scene.add(new THREE.HemisphereLight(0xffffff, 0x456044, 2));
const sun = new THREE.DirectionalLight(0xffffff, 2);
sun.position.set(5, 10, 5);
scene.add(sun);
// Tanah
const ground = new THREE.Mesh(
new THREE.PlaneGeometry(200, 200),
new THREE.MeshStandardMaterial({ color: 0x278b48 })
);
ground.rotation.x = -Math.PI / 2;
ground.position.set(0, -0.12, -35);
scene.add(ground);
// Jalan
const road = new THREE.Mesh(
new THREE.PlaneGeometry(8, 150),
new THREE.MeshStandardMaterial({ color: 0x333b4b })
);
road.rotation.x = -Math.PI / 2;
road.position.set(0, -0.08, -35);
scene.add(road);
// Garis jalan
for (const x of [-1.33, 1.33]) {
for (let i = 0; i < 20; i++) {
const line = new THREE.Mesh(
new THREE.BoxGeometry(0.07, 0.025, 2),
new THREE.MeshStandardMaterial({ color: 0xffffff })
);
line.position.set(x, -0.04, -i * 5);
scene.add(line);
}
}
// Membuat pemain
const player = new THREE.Group();
scene.add(player);
const body = new THREE.Mesh(
new THREE.BoxGeometry(0.8, 1, 0.55),
new THREE.MeshStandardMaterial({ color: 0x00aaff })
);
body.position.y = 1;
player.add(body);
const head = new THREE.Mesh(
new THREE.SphereGeometry(0.34, 16, 12),
new THREE.MeshStandardMaterial({ color: 0xffcc99 })
);
head.position.y = 1.72;
player.add(head);
// Kaki
const legs = [];
for (const x of [-0.23, 0.23]) {
const leg = new THREE.Mesh(
new THREE.BoxGeometry(0.23, 0.55, 0.25),
new THREE.MeshStandardMaterial({ color: 0x182b80 })
);
leg.position.set(x, 0.28, 0);
player.add(leg);
legs.push(leg);
}
const lanes = [-2, 0, 2];
let lane = 1;
let jumping = false;
let velocityY = 0;
let score = 0;
let speed = 0.24;
let running = false;
let spawnTimer = 0;
let obstacles = [];
let coins = [];
let clock = new THREE.Clock();
const scoreEl = document.getElementById("score");
const bestEl = document.getElementById("best");
const overlay = document.getElementById("overlay");
const title = document.getElementById("title");
const message = document.getElementById("message");
let best = 0;
try {
best = Number(localStorage.getItem("runnerBest")) || 0;
} catch(e) {}
bestEl.textContent = best;
function moveLeft() {
if (running) lane = Math.max(0, lane - 1);
}
function moveRight() {
if (running) lane = Math.min(2, lane + 1);
}
function jump() {
if (running && !jumping) {
jumping = true;
velocityY = 0.22;
}
}
document.getElementById("left").addEventListener("pointerdown",
e => { e.preventDefault(); moveLeft(); });
document.getElementById("right").addEventListener("pointerdown",
e => { e.preventDefault(); moveRight(); });
document.getElementById("jump").addEventListener("pointerdown",
e => { e.preventDefault(); jump(); });
window.addEventListener("keydown", e => {
if (e.key === "ArrowLeft" || e.key === "a") moveLeft();
if (e.key === "ArrowRight" || e.key === "d") moveRight();
if (e.key === "ArrowUp" || e.key === " ") jump();
});
// Kontrol geser layar
let touchX = 0;
let touchY = 0;
renderer.domElement.addEventListener("touchstart", e => {
touchX = e.changedTouches[0].clientX;
touchY = e.changedTouches[0].clientY;
}, { passive: true });
renderer.domElement.addEventListener("touchend", e => {
const dx = e.changedTouches[0].clientX - touchX;
const dy = e.changedTouches[0].clientY - touchY;
if (Math.abs(dx) > 35) {
dx < 0 ? moveLeft() : moveRight();
} else if (dy < -35) {
jump();
}
}, { passive: true });
// Membuat rintangan
function makeObstacle() {
const obj = new THREE.Mesh(
new THREE.BoxGeometry(1.15, 1.2, 0.9),
new THREE.MeshStandardMaterial({ color: 0xf04444 })
);
const ln = Math.floor(Math.random() * 3);
obj.position.set(lanes[ln], 0.6, -48);
scene.add(obj);
obstacles.push(obj);
}
// Membuat koin
function makeCoin() {
const obj = new THREE.Mesh(
new THREE.TorusGeometry(0.32, 0.1, 8, 16),
new THREE.MeshStandardMaterial({
color: 0xffd700,
metalness: 0.5,
roughness: 0.3
})
);
const ln = Math.floor(Math.random() * 3);
obj.position.set(lanes[ln], 1.1, -48);
scene.add(obj);
coins.push(obj);
}
function clearObjects(list) {
for (const obj of list) scene.remove(obj);
list.length = 0;
}
function startGame() {
clearObjects(obstacles);
clearObjects(coins);
lane = 1;
player.position.set(0, 0, 0);
player.rotation.set(0, 0, 0);
jumping = false;
velocityY = 0;
score = 0;
speed = 0.24;
spawnTimer = 0;
scoreEl.textContent = score;
running = true;
clock.getDelta();
overlay.style.display = "none";
}
document.getElementById("start").addEventListener("click", startGame);
function gameOver() {
running = false;
if (score > best) {
best = score;
bestEl.textContent = best;
try {
localStorage.setItem("runnerBest", best);
} catch(e) {}
}
title.textContent = "💥 GAME OVER";
message.innerHTML =
"Skor kamu: <b>" + score + "</b><br>" +
"Coba lagi dan kumpulkan lebih banyak koin!";
document.getElementById("start").textContent = "🔄 MAIN LAGI";
overlay.style.display = "flex";
}
// Loop game
function animate() {
requestAnimationFrame(animate);
const dt = Math.min(clock.getDelta(), 0.05);
if (running) {
const step = dt * 60;
spawnTimer += dt;
// Gerakan kiri-kanan yang halus
player.position.x +=
(lanes[lane] - player.position.x) * Math.min(1, dt * 12);
// Animasi lari
if (!jumping) {
const t = performance.now() * 0.012;
legs[0].rotation.x = Math.sin(t) * 0.7;
legs[1].rotation.x = -Math.sin(t) * 0.7;
}
// Melompat
if (jumping) {
player.position.y += velocityY * step;
velocityY -= 0.012 * step;
if (player.position.y <= 0) {
player.position.y = 0;
velocityY = 0;
jumping = false;
}
}
// Tambahkan rintangan dan koin
if (spawnTimer > 0.85) {
spawnTimer = 0;
makeObstacle();
if (Math.random() > 0.25) makeCoin();
}
speed = Math.min(0.48, speed + dt * 0.003);
// Gerakkan rintangan
for (let i = obstacles.length - 1; i >= 0; i--) {
const o = obstacles[i];
o.position.z += speed * step;
if (
Math.abs(o.position.z) < 0.8 &&
Math.abs(o.position.x - player.position.x) < 0.8 &&
player.position.y < 0.75
) {
gameOver();
break;
}
if (o.position.z > 8) {
scene.remove(o);
obstacles.splice(i, 1);
}
}
// Gerakkan dan ambil koin
for (let i = coins.length - 1; i >= 0; i--) {
const c = coins[i];
c.position.z += speed * step;
c.rotation.y += 0.08 * step;
if (
Math.abs(c.position.z) < 0.9 &&
Math.abs(c.position.x - player.position.x) < 0.9 &&
Math.abs(c.position.y - (player.position.y + 1)) < 1
) {
score += 10;
scoreEl.textContent = score;
scene.remove(c);
coins.splice(i, 1);
continue;
}
if (c.position.z > 8) {
scene.remove(c);
coins.splice(i, 1);
}
}
// Skor bertambah seiring waktu
score += dt * 2;
scoreEl.textContent = Math.floor(score);
}
renderer.render(scene, camera);
}
window.addEventListener("resize", () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
animate();
</script>
</body>
</html>
Game Source: 3D Runner Mobile
Creator: GlitchBolt30
Libraries: three
Complexity: complex (440 lines, 9.8 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: 3d-runner-mobile-glitchbolt30" to link back to the original. Then publish at arcadelab.ai/publish.