Spaces:
Running
Running
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>Epic 3D Rollercoaster</title> | |
<style> | |
body{margin:0;overflow:hidden;font-family:'Arial',sans-serif;background:#000;color:#fff} | |
canvas{display:block;width:100vw;height:100vh} | |
#ui{position:absolute;bottom:20px;left:50%;transform:translateX(-50%);text-align:center; | |
background:rgba(0,0,0,0.5);padding:15px 25px;border-radius:15px;font-size:18px; | |
pointer-events:none;backdrop-filter:blur(5px);border:1px solid rgba(255,255,255,0.1); | |
box-shadow:0 0 20px rgba(0,255,255,0.3);z-index:10} | |
#speed{font-size:28px;margin:10px 0;font-weight:bold;text-shadow:0 0 10px currentColor} | |
.speed-meter{width:250px;height:12px;background:rgba(255,255,255,0.1);border-radius:10px; | |
margin:15px auto;overflow:hidden;box-shadow:0 0 5px rgba(0,0,0,0.5) inset} | |
#speed-fill{height:100%;background:linear-gradient(90deg,#4CAF50,#FFEB3B,#F44336);transition:width 0.05s; | |
box-shadow:0 0 10px rgba(255,255,255,0.3)} | |
#controls{position:absolute;top:20px;left:20px;background:rgba(0,0,0,0.5);padding:15px; | |
border-radius:15px;font-size:16px;backdrop-filter:blur(5px);border:1px solid rgba(255,255,255,0.1); | |
max-width:220px;z-index:10} | |
#start-screen{position:absolute;top:0;left:0;width:100%;height:100%;background:radial-gradient(ellipse at center, | |
rgba(0,0,0,0.9) 0%,rgba(0,0,20,0.95) 100%);display:flex;flex-direction:column;justify-content:center; | |
align-items:center;z-index:100;text-align:center;opacity:1;transition:opacity 0.5s} | |
h1{font-size:4.5rem;margin:0 0 10px 0;background:linear-gradient(90deg,#ff8a00,#e52e71,#b145e9); | |
-webkit-background-clip:text;-webkit-text-fill-color:transparent;text-shadow:0 0 20px rgba(255,138,0,0.5)} | |
button{padding:15px 40px;font-size:1.3rem;margin-top:40px;background:linear-gradient(45deg,#00c6ff,#0072ff); | |
color:#fff;border:none;border-radius:50px;cursor:pointer;transition:all 0.3s;box-shadow:0 5px 25px rgba(0,198,255,0.5); | |
font-weight:bold;text-transform:uppercase} | |
button:hover{transform:scale(1.05);box-shadow:0 8px 35px rgba(0,198,255,0.7)} | |
#tutorial{background:rgba(255,255,255,0.1);padding:25px;border-radius:15px;margin:30px 0;max-width:600px; | |
border:1px solid rgba(255,255,255,0.1);backdrop-filter:blur(5px)} | |
#boost-container{margin-top:15px;font-size:14px} | |
#boost-meter{height:6px;background:rgba(255,255,255,0.1);border-radius:3px;margin-top:8px;overflow:hidden} | |
#boost-fill{height:100%;background:linear-gradient(90deg,#FF9800,#FF5722);box-shadow:0 0 10px rgba(255,152,0,0.5); | |
transition:width 0.1s} | |
#effects{position:absolute;top:0;left:0;width:100%;height:100%;pointer-events:none;opacity:0;transition:opacity 0.3s; | |
background:radial-gradient(ellipse at center,transparent 60%,rgba(255,255,255,0.3));z-index:5} | |
.stats{display:flex;justify-content:space-between;width:250px;margin:0 auto 10px auto} | |
.stat-value{font-weight:bold} | |
.blur-bg{position:absolute;top:0;left:0;width:100%;height:100%;background:radial-gradient(ellipse at center, | |
rgba(0,150,255,0.1) 0%,transparent 70%);opacity:0.5;z-index:-1} | |
</style> | |
</head> | |
<body> | |
<div id="effects"></div> | |
<div class="blur-bg"></div> | |
<div id="controls"><strong>CONTROLS:</strong><br>↑/↓: Speed<br>←/→: Lean<br>SPACE: Boost</div> | |
<div id="ui"> | |
<div class="stats"><div>Score: <span class="stat-value" id="score">0</span></div><div>Boost: <span class="stat-value" id="boost-text">Ready</span></div></div> | |
<div id="speed">0 km/h</div><div class="speed-meter"><div id="speed-fill" style="width:0%"></div></div> | |
<div id="boost-container"><div id="boost-meter"><div id="boost-fill" style="width:100%"></div></div></div> | |
</div> | |
<div id="start-screen"> | |
<h1>Epic Rollercoaster</h1> | |
<p style="font-size:1.3rem;max-width:700px;line-height:1.5">Experience breathtaking 3D visuals!</p> | |
<div id="tutorial"><strong>HOW TO PLAY:</strong><br><br>- ↑/↓ arrows for speed<br>- ←/→ arrows to lean<br>- SPACE for turbo boost</div> | |
<button id="start-btn">START RIDE</button> | |
</div> | |
<script src="https://cdn.jsdelivr.net/npm/three@0.132.2/build/three.min.js"></script> | |
<script> | |
// Scene setup | |
const scene = new THREE.Scene(); | |
scene.background = new THREE.Color(0x000000); | |
scene.fog = new THREE.FogExp2(0x87CEEB, 0.003); | |
const camera = new THREE.PerspectiveCamera(85, window.innerWidth/window.innerHeight, 0.1, 3000); | |
camera.position.set(0,1.5,0); | |
const renderer = new THREE.WebGLRenderer({antialias:true,powerPreference:"high-performance"}); | |
renderer.setPixelRatio(window.devicePixelRatio); | |
renderer.setSize(window.innerWidth, window.innerHeight); | |
renderer.shadowMap.enabled = true; | |
renderer.shadowMap.type = THREE.PCFSoftShadowMap; | |
renderer.toneMapping = THREE.ACESFilmicToneMapping; | |
renderer.toneMappingExposure = 1.2; | |
document.body.appendChild(renderer.domElement); | |
// Track creation | |
function createTrack() { | |
const points = []; | |
for(let i=0;i<=500;i++) { | |
const t = i/500*Math.PI*6; | |
points.push(new THREE.Vector3( | |
Math.sin(t*0.7)*150+Math.sin(t*0.3)*120*0.7, | |
Math.sin(t*0.5)*90+30+Math.abs(Math.sin(t))*40, | |
-t*30 | |
)); | |
} | |
const path = new THREE.CatmullRomCurve3(points,true); | |
const geom = new THREE.TubeGeometry(path,1000,1.8,24,false); | |
const mat = new THREE.MeshStandardMaterial({color:0x444444,metalness:0.8,roughness:0.3,side:THREE.DoubleSide}); | |
const track = new THREE.Mesh(geom,mat); | |
track.castShadow = track.receiveShadow = true; | |
track.position.y = -1; | |
const edges = new THREE.EdgesGeometry(geom); | |
track.add(new THREE.LineSegments(edges,new THREE.LineBasicMaterial({color:0x00ffff,transparent:true,opacity:0.8}))); | |
return {track,path}; | |
} | |
const {track,path} = createTrack(); | |
scene.add(track); | |
// Environment | |
function createEnv() { | |
const ground = new THREE.Mesh(new THREE.PlaneGeometry(5000,5000), | |
new THREE.MeshStandardMaterial({color:0x245d34,roughness:0.8})); | |
ground.rotation.x=-Math.PI/2; ground.position.y=-1.5; ground.receiveShadow=true; scene.add(ground); | |
const mtnMat = new THREE.MeshStandardMaterial({color:0x5a5a5a,roughness:0.9}); | |
for(let i=0;i<30;i++) { | |
const size=100+Math.random()*150; | |
const mtn=new THREE.Mesh(new THREE.ConeGeometry(size,size*1.5,6),mtnMat); | |
const a=Math.random()*Math.PI*2,d=800+Math.random()*1200; | |
mtn.position.set(Math.cos(a)*d,-size*0.3,Math.sin(a)*d); | |
mtn.rotation.y=Math.random()*Math.PI; mtn.castShadow=true; scene.add(mtn); | |
} | |
const treeGroup=new THREE.Group(); | |
for(let i=0;i<100;i++) { | |
const a=Math.random()*Math.PI*2,d=50+Math.random()*400,x=Math.cos(a)*d,z=Math.sin(a)*d; | |
if(Math.abs(x)<100&&Math.abs(z)<100) continue; | |
const th=3+Math.random()*7; | |
const trunk=new THREE.Mesh(new THREE.CylinderGeometry(0.3,0.5,th,8),new THREE.MeshStandardMaterial({color:0x5c4033})); | |
trunk.position.set(x,th/2,z); trunk.castShadow=true; | |
const leaves=new THREE.Mesh(new THREE.SphereGeometry(th*0.7,8),new THREE.MeshStandardMaterial({color:0x1a5e20})); | |
leaves.position.set(x,th+(th*0.4+Math.random()*3)*0.5,z); | |
leaves.scale.set(1+Math.random()*0.5,1,1+Math.random()*0.5); leaves.castShadow=true; | |
treeGroup.add(trunk); treeGroup.add(leaves); | |
} | |
scene.add(treeGroup); | |
scene.add(new THREE.Mesh(new THREE.SphereGeometry(2000,32),new THREE.MeshBasicMaterial({color:0x87CEEB,side:THREE.BackSide}))); | |
} | |
createEnv(); | |
// Lighting | |
function setupLights() { | |
const sun=new THREE.DirectionalLight(0xffffdd,1); | |
sun.position.set(10,100,10); | |
sun.castShadow=true; | |
sun.shadow.mapSize.width=sun.shadow.mapSize.height=2048; | |
scene.add(sun,new THREE.AmbientLight(0x404040,0.3), | |
new THREE.DirectionalLight(0x556688,0.3).position.set(-10,30,-30), | |
new THREE.DirectionalLight(0x445588,0.4).position.set(0,30,30)); | |
} | |
setupLights(); | |
// Game state | |
let speed=0,maxSpeed=80,pos=0,score=0,gameStarted=false,leanAngle=0,lastTime=0,boost=1,isBoosting=false; | |
const keys={ArrowUp:false,ArrowDown:false,ArrowLeft:false,ArrowRight:false,' ':false}; | |
window.addEventListener('keydown',e=>keys[e.key]=true); | |
window.addEventListener('keyup',e=>keys[e.key]=false); | |
document.getElementById('start-btn').addEventListener('click',()=>{ | |
document.getElementById('start-screen').style.opacity=0; | |
setTimeout(()=>{document.getElementById('start-screen').style.display='none';gameStarted=true},500); | |
}); | |
window.addEventListener('resize',()=>{ | |
camera.aspect=window.innerWidth/window.innerHeight; | |
camera.updateProjectionMatrix(); | |
renderer.setSize(window.innerWidth,window.innerHeight); | |
}); | |
// Animation loop | |
function animate(t) { | |
requestAnimationFrame(animate); | |
const dt=Math.min(0.1,(t-lastTime)/1000); lastTime=t; | |
if(gameStarted) { | |
if(keys.ArrowUp) speed=Math.min(maxSpeed,speed+dt*15); | |
if(keys.ArrowDown) speed=Math.max(0,speed-dt*15); | |
if(!keys.ArrowUp&&!keys.ArrowDown) speed=Math.max(0,speed*0.96); | |
isBoosting=false; | |
if(keys[' ']&&boost>0) { | |
isBoosting=true; | |
speed=Math.min(maxSpeed*1.5,speed+dt*30); | |
boost-=dt*0.5; | |
} else boost=Math.min(1,boost+dt*0.1); | |
if(keys.ArrowLeft) leanAngle=Math.max(-Math.PI/8,leanAngle-dt*2); | |
if(keys.ArrowRight) leanAngle=Math.min(Math.PI/8,leanAngle+dt*2); | |
if(!keys.ArrowLeft&&!keys.ArrowRight) leanAngle*=1-dt*5; | |
pos+=speed*dt*0.005; | |
updateCamera(dt); | |
score+=Math.floor(speed*dt*2); | |
updateUI(); | |
} | |
renderer.render(scene,camera); | |
} | |
animate(0); | |
function updateCamera(dt) { | |
const pt=path.getPointAt(pos%1),tan=path.getTangentAt(pos%1).normalize(); | |
const normal=new THREE.Vector3(0,1,0),binormal=new THREE.Vector3().crossVectors(tan,normal).normalize(); | |
const leanOffset=binormal.multiplyScalar(leanAngle*3); | |
camera.position.copy(pt).add(leanOffset).add(new THREE.Vector3(0,1.7,0)); | |
camera.lookAt(pt.clone().add(tan.multiplyScalar(10))); | |
camera.rotation.z=leanAngle; | |
if(speed>maxSpeed*0.9)camera.position.y+=Math.sin(t*0.03)*0.1; | |
} | |
function updateUI() { | |
document.getElementById('score').textContent=Math.floor(score); | |
document.getElementById('speed').textContent=Math.floor(speed)+' km/h'; | |
document.getElementById('speed-fill').style.width=(speed/maxSpeed)*100+'%'; | |
const speedColor=speed>maxSpeed*0.9?'#ff0000':speed>maxSpeed*0.7?'#ff9900':'#4CAF50'; | |
document.getElementById('speed').style.color=speedColor; | |
document.getElementById('boost-fill').style.width=boost*100+'%'; | |
document.getElementById('boost-text').textContent=boost<=0?"Recharging":isBoosting?"Active!":boost>=1?"Ready!":"Available"; | |
document.getElementById('effects').style.opacity=Math.min(1,speed/maxSpeed)*0.8; | |
} | |
</script> | |
</body> | |
</html> |