🎮ArcadeLab

猎豹跳跳 2.5D

by ElectricPenguin69
219 lines7.5 KB
▶ Play
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no">
<title>猎豹跳跳 2.5D</title>
<style>
*{margin:0;padding:0;box-sizing:border-box}
html,body{height:100%;background:#1b1d2e;overflow:hidden;touch-action:none;font-family:-apple-system,system-ui,sans-serif}
canvas{display:block}
#hud{position:fixed;top:10px;left:0;right:0;text-align:center;color:#fff;font-size:14px;text-shadow:0 1px 2px #000;z-index:5}
#hud b{font-size:18px}
#bar{position:fixed;left:50%;bottom:36px;transform:translateX(-50%);width:60%;max-width:260px;height:8px;background:rgba(0,0,0,.4);border-radius:6px;overflow:hidden;z-index:5}
#bar>i{display:block;height:100%;width:0;background:linear-gradient(90deg,#2ecc71,#f1c40f,#e74c3c)}
#tip{position:fixed;left:0;right:0;bottom:14px;text-align:center;color:#fff8;font-size:12px;z-index:5}
#over{position:fixed;inset:0;display:none;align-items:center;justify-content:center;flex-direction:column;background:rgba(0,0,0,.55);color:#fff;z-index:9}
#over button{margin-top:14px;padding:10px 26px;font-size:16px;border:0;border-radius:8px;background:#e94560;color:#fff}
</style>
</head>
<body>
<div id="hud">分数 <b id="s">0</b> | Combo x<b id="c">1</b></div>
<div id="bar"><i id="pow"></i></div>
<div id="tip">按住屏幕蓄力,松手跳</div>
<div id="over">
  <div style="font-size:24px">游戏结束</div>
  <div id="fs" style="margin:8px 0"></div>
  <button onclick="reset()">再来一次</button>
</div>
<canvas id="cv"></canvas>

<script>
const cv=document.getElementById('cv'),x=cv.getContext('2d');
let W,H,DPR;
function resize(){
  DPR=Math.min(devicePixelRatio||1,2);
  W=cv.width=innerWidth*DPR; H=cv.height=innerHeight*DPR;
  cv.style.width=innerWidth+'px'; cv.style.height=innerHeight+'px';
}
resize(); addEventListener('resize',resize);

// 伪3D:把世界坐标(x,z)投到屏幕
const cam={x:0,z:0};
function proj(wx,wz,wy){
  const sx=(wx-cam.x)*1;
  const sz=(wz-cam.z)*0.5; // 纵深压缩
  const px=W/2 + sx*26*DPR;
  const py=H*0.62 - (wz-cam.z)*10*DPR - wy*26*DPR + sz*0;
  return [px,py];
}
function blockDraw(b){
  const s=26*DPR;
  const [px,py]=proj(b.x,b.z,0);
  // 顶面
  x.fillStyle=b.color;
  x.beginPath();
  x.moveTo(px,py-s*0.5);
  x.lineTo(px+s*0.6,py-s*0.2);
  x.lineTo(px,py+s*0.1);
  x.lineTo(px-s*0.6,py-s*0.2);
  x.closePath(); x.fill();
  // 左面
  x.fillStyle=shade(b.color,-20);
  x.beginPath();
  x.moveTo(px-s*0.6,py-s*0.2);
  x.lineTo(px,py+s*0.1);
  x.lineTo(px,py+s*0.1+10*DPR);
  x.lineTo(px-s*0.6,py-s*0.2+10*DPR);
  x.closePath(); x.fill();
  // 右面
  x.fillStyle=shade(b.color,-35);
  x.beginPath();
  x.moveTo(px+s*0.6,py-s*0.2);
  x.lineTo(px,py+s*0.1);
  x.lineTo(px,py+s*0.1+10*DPR);
  x.lineTo(px+s*0.6,py-s*0.2+10*DPR);
  x.closePath(); x.fill();
}
function shade(hex,d){
  const n=parseInt(hex.slice(1),16);
  let r=(n>>16)+d,g=((n>>8)&255)+d,b=(n&255)+d;
  r=Math.max(0,Math.min(255,r));g=Math.max(0,Math.min(255,g));b=Math.max(0,Math.min(255,b));
  return 'rgb('+r+','+g+','+b+')';
}

// 游戏状态
let blocks=[],cur,next;
let p={x:0,z:0,y:0,vx:0,vz:0,vy:0,st:'idle',charge:0,rot:0};
let score=0,combo=1,cn=0;

function rndBlock(x,z){
  const hue=Math.floor(Math.random()*360);
  return {x,z,color:'hsl('+hue+',60%,62%)'};
}
function reset(){
  blocks=[];score=0;combo=1;cn=0;
  cur=rndBlock(0,0); blocks.push(cur);
  p.x=0;p.z=0;p.y=0;p.st='idle';p.charge=0;
  genNext(); document.getElementById('over').style.display='none';
  updateHUD();
}
function genNext(){
  const minD=2.2,maxD=4.2;
  const d=minD+Math.random()*(maxD-minD);
  const a=Math.random()*Math.PI*2;
  next=rndBlock(cur.x+Math.cos(a)*d, cur.z+Math.sin(a)*d);
  blocks.push(next);
  if(blocks.length>7) blocks.shift();
}

function updateHUD(){
  document.getElementById('s').textContent=score;
  document.getElementById('c').textContent=combo.toFixed(1);
}

// 输入
let pressing=false;
function down(e){ if(p.st==='idle'){pressing=true;p.st='charge';p.charge=0;} }
function up(e){
  if(p.st!=='charge')return;
  const pw=Math.min(p.charge/1.0,1);
  const dx=next.x-cur.x, dz=next.z-cur.z;
  const len=Math.hypot(dx,dz)||1;
  const reach=2.2+pw*(4.2-2.2);
  p.vx=dx/len*reach; p.vz=dz/len*reach;
  p.vy=7.5+pw*2.5;
  p.st='jump'; pressing=false;
}
cv.addEventListener('touchstart',e=>{e.preventDefault();down();},{passive:false});
cv.addEventListener('touchend',e=>{e.preventDefault();up();},{passive:false});
cv.addEventListener('mousedown',down);
addEventListener('mouseup',up);
addEventListener('keydown',e=>{if(e.code==='Space'&&!pressing)down();});
addEventListener('keyup',e=>{if(e.code==='Space')up();});

// 循环
let last=performance.now();
function loop(now){
  const dt=Math.min((now-last)/1000,0.033); last=now;
  // 蓄力
  if(p.st==='charge'){
    p.charge=Math.min(p.charge+dt,1.0);
    document.getElementById('pow').style.width=(p.charge*100)+'%';
  }
  // 跳跃物理
  if(p.st==='jump'){
    p.vy-=22*dt;
    p.x+=p.vx*dt; p.z+=p.vz*dt; p.y+=p.vy*dt;
    p.rot+=dt*6;
    if(p.vy<0 && p.y<=0){
      p.y=0; p.rot=0;
      const d=Math.hypot(p.x-next.x,p.z-next.z);
      if(d<1.1){
        let gain;
        if(d<0.45){ cn++;combo=Math.min(1+cn*0.2,2);gain=Math.round(10*combo);float('PERFECT +'+gain,'#f1c40f'); }
        else { cn=0;combo=1;gain=2;float('GOOD +2','#aaa'); }
        score+=gain;
        cur=next; p.x=next.x; p.z=next.z;
        p.st='idle'; genNext(); updateHUD();
      }else{
        p.st='dead';
        document.getElementById('fs').textContent='得分 '+score;
        document.getElementById('over').style.display='flex';
      }
    }
  }
  // 相机跟随
  cam.x += (p.x-cam.x)*0.08;
  cam.z += (p.z-cam.z)*0.08;

  // 画
  x.clearRect(0,0,W,H);
  const g=x.createLinearGradient(0,0,0,H);
  g.addColorStop(0,'#0f1020'); g.addColorStop(1,'#232544');
  x.fillStyle=g; x.fillRect(0,0,W,H);

  // 方块按z排序
  const order=blocks.slice().sort((a,b)=>a.z-b.z);
  for(const b of order) blockDraw(b);

  // 猎豹(简化:椭圆身体+头+尾巴)
  drawLeopard();

  requestAnimationFrame(loop);
}
function drawLeopard(){
  const [px,py]=proj(p.x,p.z,p.y);
  x.save();
  x.translate(px,py-10*DPR);
  // 影子
  x.fillStyle='rgba(0,0,0,.25)';
  x.beginPath();x.ellipse(0,8*DPR,14*DPR,5*DPR,0,0,7);x.fill();
  // 身体
  x.fillStyle='#e67e22';
  x.beginPath();x.ellipse(0,-6*DPR,12*DPR,8*DPR,0,0,7);x.fill();
  // 头
  x.beginPath();x.arc(8*DPR,-12*DPR,7*DPR,0,7);x.fill();
  // 耳朵
  x.beginPath();x.moveTo(4*DPR,-18*DPR);x.lineTo(7*DPR,-22*DPR);x.lineTo(10*DPR,-17*DPR);x.fill();
  // 尾巴
  x.strokeStyle='#e67e22';x.lineWidth=3*DPR;x.lineCap='round';
  x.beginPath();x.moveTo(-10*DPR,-6*DPR);
  x.quadraticCurveTo(-18*DPR,-10*DPR+Math.sin(performance.now()/200)*4,-16*DPR,-16*DPR);x.stroke();
  // 眼睛
  x.fillStyle='#fff';x.beginPath();x.arc(10*DPR,-13*DPR,2*DPR,0,7);x.fill();
  x.fillStyle='#222';x.beginPath();x.arc(10.5*DPR,-13*DPR,1*DPR,0,7);x.fill();
  x.restore();
}
function float(t,c){
  const el=document.createElement('div');
  el.textContent=t;el.style.cssText='position:fixed;left:50%;top:42%;transform:translateX(-50%);color:'+c+';font:bold 20px sans-serif;z-index:8;pointer-events:none';
  document.body.appendChild(el);
  let n=50;(function f(){n--;el.style.top=(42-(50-n)/4)+'%';el.style.opacity=n/50;if(n>0)requestAnimationFrame(f);else el.remove();})();
}

reset();
requestAnimationFrame(loop);
</script>
</body>
</html>

Game Source: 猎豹跳跳 2.5D

Creator: ElectricPenguin69

Libraries: none

Complexity: complex (219 lines, 7.5 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: 2-5d-electricpenguin69" to link back to the original. Then publish at arcadelab.ai/publish.