|
|
<!DOCTYPE html> |
|
|
<html lang="es"> |
|
|
<head> |
|
|
<meta charset="UTF-8"> |
|
|
<title>OutRun con Rivales visibles</title> |
|
|
<style> |
|
|
body{margin:0;overflow:hidden;background:#6dd5fa;font-family:sans-serif;touch-action:none;} |
|
|
canvas{display:block;width:100vw;height:80vh;background:skyblue;} |
|
|
#controls{position:fixed;bottom:0;width:100%;display:flex;justify-content:space-around;padding:10px;background:rgba(0,0,0,0.3);} |
|
|
.btn{width:70px;height:70px;border-radius:50%;border:none;background:rgba(255,255,255,0.7);font-size:24px;font-weight:bold;user-select:none;} |
|
|
#hud{position:fixed;top:5px;left:0;right:0;display:flex;justify-content:space-around;font-size:20px;font-weight:bold;color:white;text-shadow:2px 2px 3px black;} |
|
|
</style> |
|
|
</head> |
|
|
<body> |
|
|
<canvas id="game"></canvas> |
|
|
<div id="hud"> |
|
|
<div id="score">SCORE: 0</div> |
|
|
<div id="time">TIME: 60</div> |
|
|
<div id="speed">SPEED: 0 km/h</div> |
|
|
</div> |
|
|
<div id="controls"> |
|
|
<button class="btn" id="left">⟵</button> |
|
|
<button class="btn" id="right">⟶</button> |
|
|
<button class="btn" id="up">⏫</button> |
|
|
<button class="btn" id="down">⏬</button> |
|
|
</div> |
|
|
|
|
|
<script> |
|
|
|
|
|
const canvas=document.getElementById("game"),ctx=canvas.getContext("2d"); |
|
|
function resize(){canvas.width=window.innerWidth;canvas.height=window.innerHeight*0.8;} |
|
|
resize();window.addEventListener("resize",resize); |
|
|
|
|
|
let roadWidth=2000,segL=200,camD=0.84; |
|
|
let playerX=0,pos=0,speed=200; |
|
|
let score=0,timeLeft=60; |
|
|
let keys={left:false,right:false,up:false,down:false}; |
|
|
let segments=[]; |
|
|
|
|
|
|
|
|
for(let i=0;i<500;i++){segments.push({i,curve:Math.sin(i/30)*2,y:i*segL});} |
|
|
|
|
|
|
|
|
let palmImg=new Image(); palmImg.src="https://i.ibb.co/6Hw6yHr/palm.png"; |
|
|
let carImg=new Image(); carImg.src="https://i.ibb.co/VVvTqYm/car.png"; |
|
|
|
|
|
|
|
|
let rivals=[]; |
|
|
function spawnRival(){ |
|
|
let lane=(Math.random()*2-1)*0.8; |
|
|
let z=pos+2000+Math.random()*3000; |
|
|
let spd=400+Math.random()*400; |
|
|
rivals.push({x:lane,z,spd}); |
|
|
} |
|
|
setInterval(spawnRival,3000); |
|
|
|
|
|
|
|
|
window.addEventListener("keydown",e=>{ |
|
|
if(e.key==="ArrowLeft")keys.left=true; |
|
|
if(e.key==="ArrowRight")keys.right=true; |
|
|
if(e.key==="ArrowUp")keys.up=true; |
|
|
if(e.key==="ArrowDown")keys.down=true; |
|
|
}); |
|
|
window.addEventListener("keyup",e=>{ |
|
|
if(e.key==="ArrowLeft")keys.left=false; |
|
|
if(e.key==="ArrowRight")keys.right=false; |
|
|
if(e.key==="ArrowUp")keys.up=false; |
|
|
if(e.key==="ArrowDown")keys.down=false; |
|
|
}); |
|
|
function bindTouch(id,key){ |
|
|
let el=document.getElementById(id); |
|
|
el.addEventListener("touchstart",()=>keys[key]=true); |
|
|
el.addEventListener("touchend",()=>keys[key]=false); |
|
|
} |
|
|
bindTouch("left","left"); bindTouch("right","right"); |
|
|
bindTouch("up","up"); bindTouch("down","down"); |
|
|
|
|
|
|
|
|
function project(p,camX,camY,camZ){ |
|
|
let dz=p.z-camZ; if(dz<=0.1)dz=0.1; |
|
|
let dx=p.x-camX,dy=p.y-camY; |
|
|
let scale=camD/dz; |
|
|
return {x:(1+scale*dx)*canvas.width/2,y:(1-scale*dy)*canvas.height/2,w:scale*roadWidth/2}; |
|
|
} |
|
|
function drawSegment(p1,p2,color){ |
|
|
ctx.fillStyle=color.grass; ctx.fillRect(0,p2.y,canvas.width,p1.y-p2.y); |
|
|
ctx.fillStyle=color.road; |
|
|
ctx.beginPath(); |
|
|
ctx.moveTo(p1.x-p1.w,p1.y); ctx.lineTo(p1.x+p1.w,p1.y); |
|
|
ctx.lineTo(p2.x+p2.w,p2.y); ctx.lineTo(p2.x-p2.w,p2.y); |
|
|
ctx.fill(); |
|
|
} |
|
|
function drawSprite(img,scale,x,y,w,h){ctx.drawImage(img,x-w*scale/2,y-h*scale,w*scale,h*scale);} |
|
|
|
|
|
|
|
|
function update(dt){ |
|
|
pos+=speed*dt; if(pos>=segments.length*segL)pos-=segments.length*segL; |
|
|
if(keys.left)playerX-=0.02; |
|
|
if(keys.right)playerX+=0.02; |
|
|
if(keys.up)speed+=5; |
|
|
if(keys.down)speed-=5; |
|
|
if(speed<0)speed=0;if(speed>2000)speed=2000; |
|
|
for(let r of rivals){r.z-= (speed-r.spd)*dt;} |
|
|
rivals=rivals.filter(r=>r.z>pos&&r.z<pos+8000); |
|
|
timeLeft-=dt;if(timeLeft<=0){timeLeft=0;speed=0;} |
|
|
score+=Math.floor(speed*dt*0.1); |
|
|
document.getElementById("score").textContent="SCORE: "+score; |
|
|
document.getElementById("time").textContent="TIME: "+Math.floor(timeLeft); |
|
|
document.getElementById("speed").textContent="SPEED: "+Math.floor(speed/10)+" km/h"; |
|
|
} |
|
|
|
|
|
|
|
|
function render(){ |
|
|
ctx.clearRect(0,0,canvas.width,canvas.height); |
|
|
let base=Math.floor(pos/segL),camY=1000,camZ=pos+500; |
|
|
let x=0,dx=0; |
|
|
for(let n=0;n<300;n++){ |
|
|
let seg=segments[(base+n)%segments.length]; |
|
|
seg.z=seg.y-pos; x+=dx; dx+=seg.curve*0.001; |
|
|
let p1=project({x:x,y:0,z:seg.z},playerX*roadWidth,camY,camZ); |
|
|
let p2=project({x:x+dx,y:0,z:seg.z+segL},playerX*roadWidth,camY,camZ); |
|
|
let color={road:n%2?"#707070":"#696969",grass:n%2?"#10aa10":"#009900",rumble:"#fff"}; |
|
|
if(p1.y>=p2.y&&p2.y<canvas.height)drawSegment(p1,p2,color); |
|
|
if(n%20===0&&palmImg.complete){ |
|
|
let s=p1.w/250; |
|
|
drawSprite(palmImg,s,p1.x-p1.w*1.5,p1.y,200,200); |
|
|
drawSprite(palmImg,s,p1.x+p1.w*1.5,p1.y,200,200); |
|
|
} |
|
|
} |
|
|
|
|
|
for(let r of rivals){ |
|
|
let dz=r.z-pos; if(dz<=0)continue; |
|
|
let proj=project({x:r.x*roadWidth,y:0,z:dz},playerX*roadWidth,1000,pos+500); |
|
|
if(carImg.complete){ |
|
|
let s=proj.w/350; |
|
|
drawSprite(carImg,s,proj.x,proj.y,120,60); |
|
|
} |
|
|
} |
|
|
|
|
|
ctx.fillStyle="red"; |
|
|
ctx.fillRect(canvas.width/2-20+playerX*200,canvas.height-80,40,60); |
|
|
} |
|
|
|
|
|
|
|
|
let last=0; |
|
|
function loop(ts){let dt=(ts-last)/1000;if(dt>0.05)dt=0.05;last=ts;update(dt);render();requestAnimationFrame(loop);} |
|
|
requestAnimationFrame(loop); |
|
|
</script> |
|
|
</body> |
|
|
</html> |
|
|
|