Dhoni 7 - 3D Runner
by LaserHawk84539 lines14.2 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, maximum-scale=1.0, user-scalable=no">
<title>Dhoni 7 - 3D Runner</title>
<style>
* { box-sizing: border-box; }
body {
margin: 0;
overflow: hidden;
background: #87ceeb;
font-family: Arial, sans-serif;
touch-action: none;
}
canvas { display: block; }
#hud {
position: fixed;
top: 12px;
left: 12px;
color: white;
font-weight: bold;
font-size: 18px;
text-shadow: 2px 2px 4px black;
z-index: 5;
}
#controls {
position: fixed;
bottom: 20px;
left: 0;
right: 0;
display: flex;
justify-content: space-between;
padding: 0 18px;
z-index: 6;
pointer-events: none;
}
.control-group { display: flex; gap: 10px; }
button {
pointer-events: auto;
border: 2px solid white;
background: #101b32dd;
color: white;
border-radius: 16px;
width: 65px;
height: 65px;
font-size: 25px;
font-weight: bold;
touch-action: none;
}
#jumpBtn {
width: 82px;
height: 82px;
border-radius: 50%;
background: #ff8c00ee;
font-size: 17px;
}
#overlay {
position: fixed;
inset: 0;
z-index: 10;
background: #0009;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
color: white;
text-align: center;
padding: 20px;
}
#overlay h1 { font-size: 36px; margin: 0 0 12px; }
#overlay p { line-height: 1.5; }
#startBtn {
width: auto;
height: auto;
padding: 15px 28px;
background: #0c9c55;
font-size: 19px;
}
</style>
</head>
<body>
<div id="hud">
SCORE: <span id="score">0</span><br>
COINS: <span id="coins">0</span>
</div>
<div id="controls">
<div class="control-group">
<button id="leftBtn" aria-label="Move left">◀</button>
<button id="rightBtn" aria-label="Move right">▶</button>
</div>
<button id="jumpBtn">JUMP ⬆</button>
</div>
<div id="overlay">
<h1>DHONI RUNNER 7</h1>
<p>3 lanes • Collect coins • Dodge obstacles<br>Use arrows or buttons to move<br>Press JUMP to jump over obstacles</p>
<button id="startBtn">START GAME</button>
</div>
<script type="module">
import * as THREE from 'https://cdn.jsdelivr.net/npm/three@0.160.0/build/three.module.js';
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x87ceeb);
scene.fog = new THREE.Fog(0x87ceeb, 35, 115);
const camera = new THREE.PerspectiveCamera(
65, window.innerWidth / window.innerHeight, 0.1, 250
);
camera.position.set(0, 6, 10);
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
renderer.shadowMap.enabled = true;
document.body.appendChild(renderer.domElement);
scene.add(new THREE.HemisphereLight(0xffffff, 0x567044, 2));
const sun = new THREE.DirectionalLight(0xffffff, 2.2);
sun.position.set(-5, 12, 8);
sun.castShadow = true;
scene.add(sun);
const mat = (color, roughness = 0.8) =>
new THREE.MeshStandardMaterial({ color, roughness });
const green = mat(0x339344);
const roadMat = mat(0x333640);
const white = mat(0xffffff);
const blue = mat(0x0878e8);
const darkBlue = mat(0x063f91);
const skin = mat(0xb8794d);
const black = mat(0x202027);
const gold = mat(0xffc928);
const red = mat(0xf04438);
const boxGeo = new THREE.BoxGeometry(1, 1, 1);
function cube(parent, material, x, y, z, sx, sy, sz) {
const mesh = new THREE.Mesh(boxGeo, material);
mesh.position.set(x, y, z);
mesh.scale.set(sx, sy, sz);
mesh.castShadow = true;
mesh.receiveShadow = true;
parent.add(mesh);
return mesh;
}
const ground = new THREE.Mesh(
new THREE.PlaneGeometry(200, 240),
green
);
ground.rotation.x = -Math.PI / 2;
ground.position.set(0, -0.13, -65);
ground.receiveShadow = true;
scene.add(ground);
const road = new THREE.Mesh(
new THREE.PlaneGeometry(10, 220),
roadMat
);
road.rotation.x = -Math.PI / 2;
road.position.set(0, -0.08, -55);
scene.add(road);
// Road lane markings
for (const x of [-1.67, 1.67]) {
for (let i = 0; i < 22; i++) {
const dash = new THREE.Mesh(
new THREE.BoxGeometry(0.07, 0.025, 3),
white
);
dash.position.set(x, -0.045, 5 - i * 10);
scene.add(dash);
}
}
// Trees along both sides
const scenery = [];
function makeTree(x, z) {
const group = new THREE.Group();
group.position.set(x, 0, z);
cube(group, mat(0x79502d), 0, 1, 0, 0.45, 2, 0.45);
const leaves = new THREE.Mesh(
new THREE.ConeGeometry(1.25, 3.2, 7),
mat(0x16753c)
);
leaves.position.y = 3;
leaves.castShadow = true;
group.add(leaves);
scene.add(group);
scenery.push(group);
}
for (let i = 0; i < 24; i++) {
makeTree(-7 - Math.random() * 3, -i * 8 - 5);
makeTree(7 + Math.random() * 3, -i * 8 - 5);
}
// Player model, facing forward along -Z
const player = new THREE.Group();
scene.add(player);
player.position.set(0, 0, 2);
const leftLeg = new THREE.Group();
leftLeg.position.set(-0.23, 0.85, 0);
player.add(leftLeg);
cube(leftLeg, darkBlue, 0, -0.3, 0, 0.28, 0.8, 0.34);
cube(leftLeg, black, 0, -0.72, -0.08, 0.32, 0.16, 0.5);
const rightLeg = new THREE.Group();
rightLeg.position.set(0.23, 0.85, 0);
player.add(rightLeg);
cube(rightLeg, darkBlue, 0, -0.3, 0, 0.28, 0.8, 0.34);
cube(rightLeg, black, 0, -0.72, -0.08, 0.32, 0.16, 0.5);
// Body and blue jersey
cube(player, blue, 0, 1.65, 0, 0.9, 1.05, 0.48);
// Jersey text on the front, facing the camera
function makeJerseyTexture() {
const canvas = document.createElement('canvas');
canvas.width = 512;
canvas.height = 512;
const ctx = canvas.getContext('2d');
ctx.fillStyle = '#0878e8';
ctx.fillRect(0, 0, 512, 512);
ctx.fillStyle = '#ffffff';
ctx.textAlign = 'center';
ctx.font = 'bold 66px Arial';
ctx.fillText('DHONI', 256, 220);
ctx.font = 'bold 150px Arial';
ctx.fillText('7', 256, 380);
const texture = new THREE.CanvasTexture(canvas);
texture.colorSpace = THREE.SRGBColorSpace;
return texture;
}
const jerseyFront = new THREE.Mesh(
new THREE.PlaneGeometry(0.82, 0.95),
new THREE.MeshBasicMaterial({
map: makeJerseyTexture(),
side: THREE.DoubleSide
})
);
jerseyFront.position.set(0, 1.65, -0.247);
player.add(jerseyFront);
// Arms
const leftArm = new THREE.Group();
leftArm.position.set(-0.58, 1.95, 0);
player.add(leftArm);
cube(leftArm, blue, 0, -0.32, 0, 0.23, 0.75, 0.25);
cube(leftArm, skin, 0, -0.76, -0.01, 0.2, 0.22, 0.22);
const rightArm = new THREE.Group();
rightArm.position.set(0.58, 1.95, 0);
player.add(rightArm);
cube(rightArm, blue, 0, -0.32, 0, 0.23, 0.75, 0.25);
cube(rightArm, skin, 0, -0.76, -0.01, 0.2, 0.22, 0.22);
// Head, hair, face facing -Z
cube(player, skin, 0, 2.55, -0.02, 0.52, 0.55, 0.48);
cube(player, black, 0, 2.83, 0.015, 0.55, 0.18, 0.5);
// Eyes on the front
cube(player, white, -0.12, 2.59, -0.267, 0.09, 0.07, 0.025);
cube(player, white, 0.12, 2.59, -0.267, 0.09, 0.07, 0.025);
cube(player, black, -0.12, 2.59, -0.286, 0.035, 0.05, 0.015);
cube(player, black, 0.12, 2.59, -0.286, 0.035, 0.05, 0.015);
// Gameplay state
let running = false;
let score = 0;
let coinCount = 0;
let lane = 1;
const laneX = [-3, 0, 3];
let jumpY = 0;
let jumpVelocity = 0;
let isJumping = false;
let speed = 0.3;
let spawnTimer = 0;
let coinTimer = 0;
let lastTime = 0;
const obstacles = [];
const coins = [];
function makeObstacle() {
const laneIndex = Math.floor(Math.random() * 3);
const obstacle = new THREE.Group();
obstacle.position.set(laneX[laneIndex], 0, -75);
const color = Math.random() > 0.5 ? red : gold;
cube(obstacle, color, 0, 0.65, 0, 1.15, 1.3, 0.8);
scene.add(obstacle);
obstacles.push(obstacle);
}
function makeCoin() {
const laneIndex = Math.floor(Math.random() * 3);
const coin = new THREE.Mesh(
new THREE.TorusGeometry(0.38, 0.12, 10, 24),
new THREE.MeshStandardMaterial({
color: 0xffd700,
metalness: 0.65,
roughness: 0.25
})
);
coin.position.set(laneX[laneIndex], 1.25, -75);
coin.rotation.y = Math.PI / 2;
scene.add(coin);
coins.push(coin);
}
function updateHUD() {
document.getElementById('score').textContent = Math.floor(score);
document.getElementById('coins').textContent = coinCount;
}
function moveLane(direction) {
if (!running) return;
lane = Math.max(0, Math.min(2, lane + direction));
}
function jump() {
// This is the real jump action, not just a button animation.
if (!running || isJumping) return;
isJumping = true;
jumpVelocity = 0.24;
}
function startGame() {
score = 0;
coinCount = 0;
lane = 1;
jumpY = 0;
jumpVelocity = 0;
isJumping = false;
speed = 0.3;
spawnTimer = 0;
coinTimer = 0;
for (const item of obstacles) scene.remove(item);
for (const item of coins) scene.remove(item);
obstacles.length = 0;
coins.length = 0;
player.position.set(0, 0, 2);
running = true;
lastTime = performance.now();
document.getElementById('overlay').style.display = 'none';
}
function gameOver() {
running = false;
document.getElementById('overlay').style.display = 'flex';
document.querySelector('#overlay h1').textContent = 'GAME OVER!';
document.querySelector('#overlay p').innerHTML =
'Score: ' + Math.floor(score) +
'<br>Coins: ' + coinCount +
'<br>Press restart to play again';
document.getElementById('startBtn').textContent = 'PLAY AGAIN';
}
function animate(time) {
requestAnimationFrame(animate);
const dt = Math.min((time - lastTime) / 16.67 || 1, 2);
lastTime = time;
if (running) {
score += 0.15 * dt;
speed = Math.min(0.65, speed + 0.00005 * dt);
// Smoothly move between all three lanes
player.position.x += (laneX[lane] - player.position.x) * 0.22 * dt;
// WORKING JUMP PHYSICS: rises, slows, falls, then lands
if (isJumping) {
jumpY += jumpVelocity * dt;
jumpVelocity -= 0.012 * dt;
if (jumpY <= 0) {
jumpY = 0;
jumpVelocity = 0;
isJumping = false;
}
}
player.position.y = jumpY;
// Running animation
const t = time * 0.012;
if (!isJumping) {
leftLeg.rotation.x = Math.sin(t) * 0.7;
rightLeg.rotation.x = -Math.sin(t) * 0.7;
leftArm.rotation.x = -Math.sin(t) * 0.55;
rightArm.rotation.x = Math.sin(t) * 0.55;
} else {
leftLeg.rotation.x = 0.35;
rightLeg.rotation.x = -0.35;
leftArm.rotation.x = -0.5;
rightArm.rotation.x = -0.5;
}
// Recycle trees
for (const tree of scenery) {
tree.position.z += speed * dt;
if (tree.position.z > 12) tree.position.z -= 192;
}
spawnTimer += dt;
coinTimer += dt;
if (spawnTimer > 42) {
makeObstacle();
spawnTimer = 0;
}
if (coinTimer > 24) {
makeCoin();
coinTimer = 0;
}
// Move obstacles towards the player and detect collisions
for (let i = obstacles.length - 1; i >= 0; i--) {
const obstacle = obstacles[i];
obstacle.position.z += speed * dt;
if (
Math.abs(obstacle.position.z - player.position.z) < 0.85 &&
Math.abs(obstacle.position.x - player.position.x) < 1.05 &&
jumpY < 1.35
) {
gameOver();
}
if (obstacle.position.z > 12) {
scene.remove(obstacle);
obstacles.splice(i, 1);
}
}
// Collect coins
for (let i = coins.length - 1; i >= 0; i--) {
const coin = coins[i];
coin.position.z += speed * dt;
coin.rotation.y += 0.06 * dt;
if (
Math.abs(coin.position.z - player.position.z) < 1.0 &&
Math.abs(coin.position.x - player.position.x) < 1.0
) {
coinCount++;
scene.remove(coin);
coins.splice(i, 1);
continue;
}
if (coin.position.z > 12) {
scene.remove(coin);
coins.splice(i, 1);
}
}
updateHUD();
}
// Camera follows behind the character
camera.position.x += (player.position.x * 0.45 - camera.position.x) * 0.06;
camera.position.y = 5.8 + jumpY * 0.18;
camera.lookAt(player.position.x * 0.3, 1.2 + jumpY * 0.2, -5);
renderer.render(scene, camera);
}
requestAnimationFrame(animate);
// Connect all buttons to real actions
document.getElementById('startBtn').addEventListener('click', startGame);
document.getElementById('leftBtn').addEventListener('pointerdown', e => {
e.preventDefault();
moveLane(-1);
});
document.getElementById('rightBtn').addEventListener('pointerdown', e => {
e.preventDefault();
moveLane(1);
});
document.getElementById('jumpBtn').addEventListener('pointerdown', e => {
e.preventDefault();
jump();
});
// Keyboard support
window.addEventListener('keydown', e => {
if (e.code === 'ArrowLeft' || e.code === 'KeyA') moveLane(-1);
if (e.code === 'ArrowRight' || e.code === 'KeyD') moveLane(1);
if (e.code === 'Space' || e.code === 'ArrowUp' || e.code === 'KeyW') {
e.preventDefault();
jump();
}
});
// Swipe controls for mobile
let touchStartX = 0;
let touchStartY = 0;
renderer.domElement.addEventListener('touchstart', e => {
touchStartX = e.changedTouches[0].clientX;
touchStartY = e.changedTouches[0].clientY;
}, { passive: true });
renderer.domElement.addEventListener('touchend', e => {
const dx = e.changedTouches[0].clientX - touchStartX;
const dy = e.changedTouches[0].clientY - touchStartY;
if (Math.abs(dx) > 45 && Math.abs(dx) > Math.abs(dy)) {
moveLane(dx > 0 ? 1 : -1);
} else if (dy < -35) {
jump();
}
}, { passive: true });
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
</script>
</body>
</html>Game Source: Dhoni 7 - 3D Runner
Creator: LaserHawk84
Libraries: three
Complexity: complex (539 lines, 14.2 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: dhoni-7-3d-runner-laserhawk84" to link back to the original. Then publish at arcadelab.ai/publish.