Skip to content

Instantly share code, notes, and snippets.

@shricodev
Created June 15, 2025 09:58
Show Gist options
  • Save shricodev/9fc4d5727fa1fe66a2de485647fc79db to your computer and use it in GitHub Desktop.
Save shricodev/9fc4d5727fa1fe66a2de485647fc79db to your computer and use it in GitHub Desktop.
Bike Racing (Developed by Claude Opus 4 Model) - Blog Demo
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>3D Motorbike Racing Game</title>
<style>
body {
margin: 0;
padding: 0;
overflow: hidden;
font-family: Arial, sans-serif;
}
#gameCanvas {
width: 100%;
height: 100vh;
display: block;
}
#hud {
position: absolute;
top: 20px;
left: 20px;
color: white;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.8);
font-size: 20px;
font-weight: bold;
}
#controls {
position: absolute;
bottom: 20px;
left: 20px;
color: white;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.8);
font-size: 14px;
}
</style>
</head>
<body>
<div id="hud">
<div>Speed: <span id="speed">0</span> km/h</div>
<div>Position: <span id="position">1</span>/6</div>
</div>
<div id="controls">
W/S - Accelerate/Brake | A/D - Steer | Q/E - Kick Left/Right
</div>
<script type="module">
import * as THREE from "https://unpkg.com/[email protected]/build/three.module.js";
// Game Configuration
const CONFIG = {
playerMaxSpeed: 150,
playerAcceleration: 80,
playerBraking: 120,
playerSteering: 2,
enemySpeed: 100,
kickForce: 15,
trackLength: 2000,
trackWidth: 30,
cameraHeight: 8,
cameraDistance: 20,
};
// Game State
const gameState = {
keys: {},
playerBike: null,
enemyBikes: [],
playerSpeed: 0,
playerPosition: 0,
clouds: [],
trees: [],
flowers: [],
train: null,
trainDirection: 1,
};
// Scene Setup
const scene = new THREE.Scene();
scene.fog = new THREE.Fog(0x87ceeb, 100, 500);
const camera = new THREE.PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
0.1,
1000,
);
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
document.body.appendChild(renderer.domElement);
// Lighting
const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
directionalLight.position.set(50, 100, 50);
directionalLight.castShadow = true;
directionalLight.shadow.camera.left = -100;
directionalLight.shadow.camera.right = 100;
directionalLight.shadow.camera.top = 100;
directionalLight.shadow.camera.bottom = -100;
scene.add(directionalLight);
// Create Bike Geometry
function createBike(color = 0xff0000) {
const bikeGroup = new THREE.Group();
// Body
const bodyGeometry = new THREE.BoxGeometry(1, 1, 3);
const bodyMaterial = new THREE.MeshPhongMaterial({ color: color });
const body = new THREE.Mesh(bodyGeometry, bodyMaterial);
body.position.y = 0.5;
body.castShadow = true;
bikeGroup.add(body);
// Wheels
const wheelGeometry = new THREE.CylinderGeometry(0.5, 0.5, 0.3, 16);
const wheelMaterial = new THREE.MeshPhongMaterial({ color: 0x333333 });
const frontWheel = new THREE.Mesh(wheelGeometry, wheelMaterial);
frontWheel.rotation.z = Math.PI / 2;
frontWheel.position.set(0, 0, 1.2);
frontWheel.castShadow = true;
bikeGroup.add(frontWheel);
const backWheel = new THREE.Mesh(wheelGeometry, wheelMaterial);
backWheel.rotation.z = Math.PI / 2;
backWheel.position.set(0, 0, -1.2);
backWheel.castShadow = true;
bikeGroup.add(backWheel);
// Rider (using basic geometry instead of CapsuleGeometry)
const riderGroup = new THREE.Group();
// Rider body (cylinder)
const bodyGeom = new THREE.CylinderGeometry(0.3, 0.3, 1.2, 8);
const bodyMat = new THREE.MeshPhongMaterial({ color: 0x333333 });
const riderBody = new THREE.Mesh(bodyGeom, bodyMat);
riderBody.position.y = 0.6;
riderGroup.add(riderBody);
// Rider head (sphere)
const headGeom = new THREE.SphereGeometry(0.3, 8, 6);
const headMat = new THREE.MeshPhongMaterial({ color: 0xffdbca });
const riderHead = new THREE.Mesh(headGeom, headMat);
riderHead.position.y = 1.5;
riderGroup.add(riderHead);
// Helmet
const helmetGeom = new THREE.SphereGeometry(0.35, 8, 6);
const helmetMat = new THREE.MeshPhongMaterial({ color: color });
const helmet = new THREE.Mesh(helmetGeom, helmetMat);
helmet.position.y = 1.5;
helmet.scale.y = 0.8;
riderGroup.add(helmet);
riderGroup.position.y = 1;
riderGroup.rotation.x = Math.PI / 6;
bikeGroup.add(riderGroup);
// Add handlebars
const handlebarGeom = new THREE.CylinderGeometry(0.05, 0.05, 1.5);
const handlebarMat = new THREE.MeshPhongMaterial({ color: 0x222222 });
const handlebars = new THREE.Mesh(handlebarGeom, handlebarMat);
handlebars.rotation.z = Math.PI / 2;
handlebars.position.set(0, 1, 0.8);
bikeGroup.add(handlebars);
bikeGroup.userData = {
velocity: new THREE.Vector3(),
kickCooldown: 0,
wobble: 0,
};
return bikeGroup;
}
// Create Track
function createTrack() {
const trackGroup = new THREE.Group();
// Road segments
for (let i = 0; i < CONFIG.trackLength / 100; i++) {
const roadGeometry = new THREE.PlaneGeometry(CONFIG.trackWidth, 100);
const roadMaterial = new THREE.MeshPhongMaterial({
color: 0x444444,
side: THREE.DoubleSide,
});
const roadSegment = new THREE.Mesh(roadGeometry, roadMaterial);
roadSegment.rotation.x = -Math.PI / 2;
roadSegment.position.z = i * 100;
roadSegment.receiveShadow = true;
trackGroup.add(roadSegment);
// Lane markers (dashed lines)
for (let j = -1; j <= 1; j++) {
if (j !== 0) {
for (let k = 0; k < 10; k++) {
const lineGeometry = new THREE.PlaneGeometry(0.5, 8);
const lineMaterial = new THREE.MeshPhongMaterial({
color: 0xffffff,
});
const line = new THREE.Mesh(lineGeometry, lineMaterial);
line.rotation.x = -Math.PI / 2;
line.position.set(
(j * CONFIG.trackWidth) / 3,
0.01,
i * 100 + k * 10,
);
trackGroup.add(line);
}
}
}
}
// Guard rails
const railGeometry = new THREE.BoxGeometry(1, 2, CONFIG.trackLength);
const railMaterial = new THREE.MeshPhongMaterial({ color: 0x888888 });
const leftRail = new THREE.Mesh(railGeometry, railMaterial);
leftRail.position.set(
-CONFIG.trackWidth / 2 - 1,
1,
CONFIG.trackLength / 2,
);
leftRail.castShadow = true;
trackGroup.add(leftRail);
const rightRail = new THREE.Mesh(railGeometry, railMaterial);
rightRail.position.set(
CONFIG.trackWidth / 2 + 1,
1,
CONFIG.trackLength / 2,
);
rightRail.castShadow = true;
trackGroup.add(rightRail);
return trackGroup;
}
// Create Environment
function createEnvironment() {
// Sky
scene.background = new THREE.Color(0x87ceeb);
// Ground
const groundGeometry = new THREE.PlaneGeometry(
1000,
CONFIG.trackLength,
);
const groundMaterial = new THREE.MeshPhongMaterial({ color: 0x4b7c4b });
const ground = new THREE.Mesh(groundGeometry, groundMaterial);
ground.rotation.x = -Math.PI / 2;
ground.position.y = -0.1;
ground.position.z = CONFIG.trackLength / 2;
ground.receiveShadow = true;
scene.add(ground);
// Clouds
for (let i = 0; i < 20; i++) {
const cloudGroup = new THREE.Group();
for (let j = 0; j < 5; j++) {
const cloudGeometry = new THREE.SphereGeometry(
Math.random() * 10 + 5,
8,
6,
);
const cloudMaterial = new THREE.MeshPhongMaterial({
color: 0xffffff,
transparent: true,
opacity: 0.7,
});
const cloudPart = new THREE.Mesh(cloudGeometry, cloudMaterial);
cloudPart.position.set(
Math.random() * 20 - 10,
Math.random() * 5,
Math.random() * 10 - 5,
);
cloudGroup.add(cloudPart);
}
cloudGroup.position.set(
Math.random() * 200 - 100,
Math.random() * 50 + 50,
Math.random() * CONFIG.trackLength,
);
gameState.clouds.push(cloudGroup);
scene.add(cloudGroup);
}
// Trees
for (let i = 0; i < 100; i++) {
const treeGroup = new THREE.Group();
// Trunk
const trunkGeometry = new THREE.CylinderGeometry(1, 1.5, 8);
const trunkMaterial = new THREE.MeshPhongMaterial({
color: 0x8b4513,
});
const trunk = new THREE.Mesh(trunkGeometry, trunkMaterial);
trunk.position.y = 4;
trunk.castShadow = true;
treeGroup.add(trunk);
// Leaves (multiple cones for fuller look)
const leavesGeometry = new THREE.ConeGeometry(5, 10, 8);
const leavesMaterial = new THREE.MeshPhongMaterial({
color: 0x228b22,
});
const leaves = new THREE.Mesh(leavesGeometry, leavesMaterial);
leaves.position.y = 12;
leaves.castShadow = true;
treeGroup.add(leaves);
const leaves2 = new THREE.Mesh(leavesGeometry, leavesMaterial);
leaves2.position.y = 10;
leaves2.scale.set(0.8, 0.8, 0.8);
treeGroup.add(leaves2);
const side = Math.random() > 0.5 ? 1 : -1;
treeGroup.position.set(
side * (CONFIG.trackWidth / 2 + Math.random() * 30 + 10),
0,
Math.random() * CONFIG.trackLength,
);
gameState.trees.push(treeGroup);
scene.add(treeGroup);
}
// Flowers
for (let i = 0; i < 200; i++) {
const flowerGroup = new THREE.Group();
// Stem
const stemGeometry = new THREE.CylinderGeometry(0.1, 0.1, 2);
const stemMaterial = new THREE.MeshPhongMaterial({ color: 0x228b22 });
const stem = new THREE.Mesh(stemGeometry, stemMaterial);
stem.position.y = 1;
flowerGroup.add(stem);
// Petals
const petalGeometry = new THREE.SphereGeometry(0.5, 6, 4);
const colors = [0xff1493, 0xffff00, 0x00ced1, 0xff6347, 0xda70d6];
const petalMaterial = new THREE.MeshPhongMaterial({
color: colors[Math.floor(Math.random() * colors.length)],
});
// Center
const centerGeometry = new THREE.SphereGeometry(0.3, 6, 4);
const centerMaterial = new THREE.MeshPhongMaterial({
color: 0xffff00,
});
const center = new THREE.Mesh(centerGeometry, centerMaterial);
center.position.y = 2;
flowerGroup.add(center);
// Petals around center
for (let j = 0; j < 5; j++) {
const petal = new THREE.Mesh(petalGeometry, petalMaterial);
const angle = (j / 5) * Math.PI * 2;
petal.position.set(Math.cos(angle) * 0.7, 2, Math.sin(angle) * 0.7);
flowerGroup.add(petal);
}
const side = Math.random() > 0.5 ? 1 : -1;
flowerGroup.position.set(
side * (CONFIG.trackWidth / 2 + Math.random() * 10 + 5),
0,
Math.random() * CONFIG.trackLength,
);
gameState.flowers.push(flowerGroup);
scene.add(flowerGroup);
}
// Train track
const railGroup = new THREE.Group();
const railGeometry = new THREE.BoxGeometry(2, 0.5, CONFIG.trackLength);
const railMaterial = new THREE.MeshPhongMaterial({ color: 0x654321 });
const leftTrainRail = new THREE.Mesh(railGeometry, railMaterial);
leftTrainRail.position.set(-60, 0, CONFIG.trackLength / 2);
railGroup.add(leftTrainRail);
const rightTrainRail = new THREE.Mesh(railGeometry, railMaterial);
rightTrainRail.position.set(-55, 0, CONFIG.trackLength / 2);
railGroup.add(rightTrainRail);
scene.add(railGroup);
// Train
const trainGroup = new THREE.Group();
// Train cars
for (let i = 0; i < 5; i++) {
const carGeometry = new THREE.BoxGeometry(8, 6, 20);
const carMaterial = new THREE.MeshPhongMaterial({
color: i === 0 ? 0xff0000 : 0x0000ff,
});
const car = new THREE.Mesh(carGeometry, carMaterial);
car.position.z = i * 22;
car.position.y = 3;
car.castShadow = true;
trainGroup.add(car);
// Windows
const windowGeometry = new THREE.PlaneGeometry(2, 2);
const windowMaterial = new THREE.MeshPhongMaterial({
color: 0x87ceeb,
});
for (let w = 0; w < 3; w++) {
const windowLeft = new THREE.Mesh(windowGeometry, windowMaterial);
windowLeft.position.set(-4.1, 3, i * 22 + (w - 1) * 5);
windowLeft.rotation.y = Math.PI / 2;
trainGroup.add(windowLeft);
const windowRight = new THREE.Mesh(windowGeometry, windowMaterial);
windowRight.position.set(4.1, 3, i * 22 + (w - 1) * 5);
windowRight.rotation.y = -Math.PI / 2;
trainGroup.add(windowRight);
}
// Wheels
for (let j = 0; j < 4; j++) {
const wheelGeometry = new THREE.CylinderGeometry(1, 1, 10);
const wheelMaterial = new THREE.MeshPhongMaterial({
color: 0x333333,
});
const wheel = new THREE.Mesh(wheelGeometry, wheelMaterial);
wheel.rotation.z = Math.PI / 2;
wheel.position.set(
5 * (j < 2 ? 1 : -1),
0,
i * 22 + (j % 2) * 8 - 4,
);
trainGroup.add(wheel);
}
}
trainGroup.position.set(-57.5, 0, 0);
gameState.train = trainGroup;
scene.add(trainGroup);
}
// Initialize Game
function init() {
// Create track
const track = createTrack();
scene.add(track);
// Create environment
createEnvironment();
// Create player bike
gameState.playerBike = createBike(0xff0000);
gameState.playerBike.position.set(0, 0.5, 10);
scene.add(gameState.playerBike);
// Create enemy bikes
const enemyColors = [0x0000ff, 0x00ff00, 0xffff00, 0xff00ff, 0x00ffff];
for (let i = 0; i < 5; i++) {
const enemyBike = createBike(enemyColors[i]);
enemyBike.position.set((i - 2) * 5, 0.5, Math.random() * 50 + 20);
enemyBike.userData.lane = i - 2;
enemyBike.userData.baseSpeed =
CONFIG.enemySpeed + Math.random() * 20 - 10;
enemyBike.userData.aggressiveness = Math.random();
gameState.enemyBikes.push(enemyBike);
scene.add(enemyBike);
}
// Position camera
updateCamera();
}
// Update Camera
function updateCamera() {
const bikePosition = gameState.playerBike.position;
const bikeRotation = gameState.playerBike.rotation;
camera.position.x =
bikePosition.x - Math.sin(bikeRotation.y) * CONFIG.cameraDistance;
camera.position.y = bikePosition.y + CONFIG.cameraHeight;
camera.position.z =
bikePosition.z - Math.cos(bikeRotation.y) * CONFIG.cameraDistance;
camera.lookAt(bikePosition.x, bikePosition.y + 2, bikePosition.z);
}
// Handle player input
function handleInput(deltaTime) {
const player = gameState.playerBike;
// Acceleration/Braking
if (gameState.keys["w"] || gameState.keys["W"]) {
gameState.playerSpeed += CONFIG.playerAcceleration * deltaTime;
}
if (gameState.keys["s"] || gameState.keys["S"]) {
gameState.playerSpeed -= CONFIG.playerBraking * deltaTime;
}
// Clamp speed
gameState.playerSpeed = Math.max(
0,
Math.min(CONFIG.playerMaxSpeed, gameState.playerSpeed),
);
// Natural deceleration
if (!gameState.keys["w"] && !gameState.keys["W"]) {
gameState.playerSpeed *= 0.98;
}
// Steering with lean effect
if (
(gameState.keys["a"] || gameState.keys["A"]) &&
gameState.playerSpeed > 0
) {
player.rotation.y +=
CONFIG.playerSteering *
deltaTime *
(gameState.playerSpeed / CONFIG.playerMaxSpeed);
player.rotation.z = Math.min(0.3, player.rotation.z + deltaTime * 2);
} else if (
(gameState.keys["d"] || gameState.keys["D"]) &&
gameState.playerSpeed > 0
) {
player.rotation.y -=
CONFIG.playerSteering *
deltaTime *
(gameState.playerSpeed / CONFIG.playerMaxSpeed);
player.rotation.z = Math.max(-0.3, player.rotation.z - deltaTime * 2);
} else {
player.rotation.z *= 0.9;
}
// Apply movement
const forward = new THREE.Vector3(0, 0, 1);
forward.applyQuaternion(player.quaternion);
player.position.add(
forward.multiplyScalar(gameState.playerSpeed * deltaTime),
);
// Keep on track
player.position.x = Math.max(
-CONFIG.trackWidth / 2 + 2,
Math.min(CONFIG.trackWidth / 2 - 2, player.position.x),
);
player.position.z = Math.max(
0,
Math.min(CONFIG.trackLength - 10, player.position.z),
);
// Kicking
if (player.userData.kickCooldown > 0) {
player.userData.kickCooldown -= deltaTime;
}
if (
(gameState.keys["q"] || gameState.keys["Q"]) &&
player.userData.kickCooldown <= 0
) {
performKick(player, -1);
player.userData.kickCooldown = 0.5;
}
if (
(gameState.keys["e"] || gameState.keys["E"]) &&
player.userData.kickCooldown <= 0
) {
performKick(player, 1);
player.userData.kickCooldown = 0.5;
}
gameState.playerPosition = player.position.z;
}
// Perform kick
function performKick(attacker, direction) {
const attackerPos = attacker.position;
const kickRange = 5;
gameState.enemyBikes.forEach((enemy) => {
const distance = attackerPos.distanceTo(enemy.position);
if (distance < kickRange) {
// Apply kick force
enemy.userData.wobble = 1;
enemy.position.x += direction * CONFIG.kickForce;
// Add some rotation for effect
enemy.rotation.z += direction * 0.3;
// Temporary speed reduction
enemy.userData.baseSpeed *= 0.8;
setTimeout(() => {
enemy.userData.baseSpeed /= 0.8;
}, 2000);
}
});
}
// Update enemy AI
function updateEnemyAI(deltaTime) {
gameState.enemyBikes.forEach((enemy, index) => {
// Basic forward movement
enemy.position.z += enemy.userData.baseSpeed * deltaTime;
// Lane changing based on aggressiveness
if (Math.random() < enemy.userData.aggressiveness * 0.01) {
enemy.userData.lane += Math.random() > 0.5 ? 1 : -1;
enemy.userData.lane = Math.max(
-2,
Math.min(2, enemy.userData.lane),
);
}
// Smooth lane positioning
const targetX = enemy.userData.lane * 5;
enemy.position.x += (targetX - enemy.position.x) * 0.05;
// Wobble recovery
if (enemy.userData.wobble > 0) {
enemy.userData.wobble -= deltaTime * 2;
enemy.rotation.z *= 0.9;
}
// Keep on track
enemy.position.x = Math.max(
-CONFIG.trackWidth / 2 + 2,
Math.min(CONFIG.trackWidth / 2 - 2, enemy.position.x),
);
// Wrap around
if (enemy.position.z > CONFIG.trackLength - 10) {
enemy.position.z = 10;
}
if (enemy.position.z < 0) {
enemy.position.z = CONFIG.trackLength - 20;
}
// Avoid collisions with other enemies
gameState.enemyBikes.forEach((other, otherIndex) => {
if (index !== otherIndex) {
const dist = enemy.position.distanceTo(other.position);
if (dist < 5) {
const avoidDir = enemy.position
.clone()
.sub(other.position)
.normalize();
enemy.position.add(avoidDir.multiplyScalar(0.1));
}
}
});
// Try to catch up to player if behind
const playerZ = gameState.playerBike.position.z;
if (enemy.position.z < playerZ - 50) {
enemy.userData.baseSpeed = CONFIG.enemySpeed * 1.2;
} else if (enemy.position.z > playerZ + 50) {
enemy.userData.baseSpeed = CONFIG.enemySpeed * 0.9;
}
});
}
// Update environment
function updateEnvironment(deltaTime) {
// Animate clouds
gameState.clouds.forEach((cloud) => {
cloud.position.x += deltaTime * 5;
if (cloud.position.x > 150) {
cloud.position.x = -150;
}
cloud.rotation.y += deltaTime * 0.1;
});
// Animate train
if (gameState.train) {
gameState.train.position.z +=
deltaTime * 50 * gameState.trainDirection;
if (gameState.train.position.z > CONFIG.trackLength) {
gameState.trainDirection = -1;
} else if (gameState.train.position.z < -100) {
gameState.trainDirection = 1;
}
// Train sound effect (visual cue)
const playerZ = gameState.playerBike.position.z;
const trainZ = gameState.train.position.z;
if (Math.abs(playerZ - trainZ) < 100) {
// Make train more visible when near
gameState.train.scale.y = 1 + Math.sin(Date.now() * 0.01) * 0.05;
}
}
}
// Update HUD
function updateHUD() {
document.getElementById("speed").textContent = Math.round(
gameState.playerSpeed,
);
// Calculate position
const allBikes = [
{ bike: gameState.playerBike, z: gameState.playerBike.position.z },
...gameState.enemyBikes.map((bike) => ({ bike, z: bike.position.z })),
];
allBikes.sort((a, b) => b.z - a.z);
const position =
allBikes.findIndex((b) => b.bike === gameState.playerBike) + 1;
document.getElementById("position").textContent = position;
}
// Animation loop
let lastTime = 0;
function animate(currentTime) {
requestAnimationFrame(animate);
const deltaTime = (currentTime - lastTime) / 1000;
lastTime = currentTime;
if (deltaTime < 0.1) {
// Prevent huge jumps
handleInput(deltaTime);
updateEnemyAI(deltaTime);
updateEnvironment(deltaTime);
updateCamera();
updateHUD();
}
renderer.render(scene, camera);
}
// Event listeners
window.addEventListener("keydown", (e) => {
gameState.keys[e.key] = true;
});
window.addEventListener("keyup", (e) => {
gameState.keys[e.key] = false;
});
window.addEventListener("resize", () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
// Start game
init();
animate(0);
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment