🎮ArcadeLab

猎豹跳跳 3D

by ElectricPenguin69
272 lines9.1 KB🛠️ Three.js (3D graphics)
▶ Play
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<title>猎豹跳跳 3D</title>
<style>
* { margin:0; padding:0; box-sizing:border-box; }
body { background:#0f1020; overflow:hidden; touch-action:none; font-family:monospace; }
#ui { position:fixed; top:12px; left:50%; transform:translateX(-50%); color:#fff; text-align:center; z-index:10; text-shadow:0 2px 4px #000; }
#ui small { opacity:.6; }
#charge { position:fixed; bottom:40px; left:50%; transform:translateX(-50%); width:220px; height:10px; background:rgba(0,0,0,.4); border-radius:6px; overflow:hidden; z-index:10; }
#charge > i { display:block; height:100%; width:0%; background:linear-gradient(90deg,#2ecc71,#f1c40f,#e74c3c); }
#over { position:fixed; inset:0; display:none; align-items:center; justify-content:center; flex-direction:column; color:#fff; background:rgba(0,0,0,.6); z-index:20; }
#over button { margin-top:16px; padding:10px 28px; font-size:18px; border:0; border-radius:8px; background:#e94560; color:#fff; cursor:pointer; }
</style>
</head>
<body>
<div id="ui">
  <div>分数 <span id="score">0</span> | Combo x<span id="combo">1</span></div>
  <small>按住空格/鼠标/触屏蓄力,松手跳</small>
</div>
<div id="charge"><i></i></div>
<div id="over">
  <div style="font-size:28px">游戏结束</div>
  <div id="final" style="margin:10px 0"></div>
  <button onclick="restart()">再来一次</button>
</div>

<script type="module">
import * as THREE from 'https://unpkg.com/three@0.160.0/build/three.module.js';

// ---------- 基础场景 ----------
const renderer = new THREE.WebGLRenderer({ antialias:true });
renderer.setSize(innerWidth, innerHeight);
renderer.shadowMap.enabled = true;
document.body.appendChild(renderer.domElement);

const scene = new THREE.Scene();
scene.background = new THREE.Color(0x0f1020);
scene.fog = new THREE.Fog(0x0f1020, 30, 80);

const camera = new THREE.PerspectiveCamera(45, innerWidth/innerHeight, 1, 200);
camera.position.set(0, 18, 22);
camera.lookAt(0, 0, 0);

// 光
const hemi = new THREE.HemisphereLight(0xffffff, 0x444466, 0.8);
scene.add(hemi);
const dir = new THREE.DirectionalLight(0xffffff, 0.9);
dir.position.set(10, 20, 10);
dir.castShadow = true;
dir.shadow.mapSize.set(1024,1024);
dir.shadow.camera.left=-30; dir.shadow.camera.right=30;
dir.shadow.camera.top=30; dir.shadow.camera.bottom=-30;
scene.add(dir);

// ---------- 参数(手感核心) ----------
const CFG = {
  chargeMax: 1.0,        // 满蓄力时间(秒)
  minDist: 2.2,
  maxDist: 7.5,
  gravity: 22,
  jumpUp: 9.5,           // 起跳垂直速度
  blockSize: 2.2,
  perfectR: 0.45,
  goodR: 1.1
};

// ---------- 猎豹(简化3D) ----------
const leopard = new THREE.Group();
const bodyGeo = new THREE.CapsuleGeometry(0.5, 0.8, 4, 8);
const skin = new THREE.MeshStandardMaterial({ color:0xe67e22, roughness:.6 });
const body = new THREE.Mesh(bodyGeo, skin);
body.castShadow = true;
leopard.add(body);
// 头
const head = new THREE.Mesh(new THREE.SphereGeometry(0.45, 16, 16), skin);
head.position.set(0, 0.7, 0.3); head.castShadow = true;
leopard.add(head);
// 耳朵
for (const sx of [-1,1]) {
  const ear = new THREE.Mesh(new THREE.ConeGeometry(0.18,0.4,8), skin);
  ear.position.set(sx*0.25, 1.0, 0.3); leopard.add(ear);
}
scene.add(leopard);

// 阴影圈
const shadow = new THREE.Mesh(
  new THREE.CircleGeometry(0.6, 24),
  new THREE.MeshBasicMaterial({ color:0x000000, transparent:true, opacity:0.3 })
);
shadow.rotation.x = -Math.PI/2;
scene.add(shadow);

// ---------- 方块 ----------
let blocks = [];
function makeBlock(x, z, color) {
  const g = new THREE.Group();
  const h = 1.2;
  const mesh = new THREE.Mesh(
    new THREE.BoxGeometry(CFG.blockSize, h, CFG.blockSize),
    new THREE.MeshStandardMaterial({ color, roughness:.7 })
  );
  mesh.position.y = -h/2;
  mesh.receiveShadow = true; mesh.castShadow = true;
  g.add(mesh);
  g.position.set(x, 0, z);
  scene.add(g);
  return { group:g, x, z, w:CFG.blockSize };
}
function randColor(){ return new THREE.Color().setHSL(Math.random(), .55, .6); }

// ---------- 状态 ----------
let state = 'idle'; // idle | charge | jump | dead
let charge = 0;
let vel = new THREE.Vector3();
let cur = null, next = null;
let score = 0, combo = 1, comboN = 0;
let t = 0;

function spawnFirst() {
  cur = makeBlock(0, 0, randColor());
  leopard.position.set(0, 0, 0);
  genNext();
}
function genNext() {
  const dist = CFG.minDist + Math.random()*(CFG.maxDist-CFG.minDist);
  const ang = Math.random()*Math.PI*2;
  const x = cur.x + Math.cos(ang)*dist;
  const z = cur.z + Math.sin(ang)*dist;
  next = makeBlock(x, z, randColor());
  blocks.push(next);
  // 清理远处
  if (blocks.length > 6) {
    const old = blocks.shift();
    scene.remove(old.group);
  }
}

// ---------- 输入 ----------
function down(e){ if(state==='idle'){ state='charge'; charge=0; } }
function up(e){
  if(state!=='charge') return;
  const power = charge / CFG.chargeMax;
  const dx = next.x - cur.x, dz = next.z - cur.z;
  const len = Math.hypot(dx,dz);
  const reach = CFG.minDist + power*(CFG.maxDist-CFG.minDist);
  vel.x = (dx/len)*reach*1.6;
  vel.z = (dz/len)*reach*1.6;
  vel.y = CFG.jumpUp;
  state = 'jump'; t = 0;
  leopard.userData.sx = leopard.scale.x;
}
addEventListener('keydown', e=>{ if(e.code==='Space') down(); });
addEventListener('keyup', e=>{ if(e.code==='Space') up(); });
renderer.domElement.addEventListener('pointerdown', down);
addEventListener('pointerup', up);

// ---------- 逻辑 ----------
function update(dt){
  if (state==='charge') {
    charge = Math.min(charge+dt, CFG.chargeMax);
    const s = 1 - charge*0.25;
    leopard.scale.set(1+(1-s), s, 1+(1-s));
  }
  if (state==='jump') {
    t += dt;
    vel.y -= CFG.gravity*dt;
    leopard.position.x += vel.x*dt;
    leopard.position.z += vel.z*dt;
    leopard.position.y += vel.y*dt;
    // 翻滚
    leopard.rotation.x -= dt*8;

    if (vel.y < 0) {
      // 接近目标高度
      const topY = 0;
      if (leopard.position.y <= topY) {
        leopard.position.y = topY;
        leopard.rotation.x = 0;
        const d = Math.hypot(leopard.position.x-next.x, leopard.position.z-next.z);
        if (d < CFG.goodR) {
          // 成功
          let gain = 1;
          if (d < CFG.perfectR) {
            comboN++; combo = Math.min(1+comboN*0.2, 2);
            gain = Math.round(10*combo);
            pop('PERFECT +'+gain, '#f1c40f');
          } else {
            comboN = 0; combo = 1; gain = 2;
            pop('GOOD +'+gain, '#95a5a6');
          }
          score += gain;
          cur = next;
          leopard.position.x = next.x; leopard.position.z = next.z;
          leopard.scale.set(1,1,1);
          state = 'idle';
          genNext();
          updateUI();
        } else {
          // 掉下去
          die();
        }
      }
    }
  }

  // 阴影跟随
  shadow.position.set(leopard.position.x, 0.01, leopard.position.z);
  const h = Math.max(0.2, 1 - leopard.position.y/8);
  shadow.scale.setScalar(h);

  // 相机跟随
  const tx = leopard.position.x*0.6, tz = leopard.position.z*0.6 + 6;
  camera.position.x += (tx - camera.position.x)*0.05;
  camera.position.z += (tz - camera.position.z)*0.05;
  camera.lookAt(leopard.position.x*0.5, 0, leopard.position.z*0.5);

  // 蓄力条
  document.querySelector('#charge > i').style.width = (charge/CFG.chargeMax*100)+'%';
}

function die(){
  state='dead';
  pop('MISS', '#e74c3c');
  document.getElementById('final').innerText = '得分 '+score;
  document.getElementById('over').style.display = 'flex';
}

function pop(text,color){
  const el = document.createElement('div');
  el.textContent = text;
  el.style.cssText = `position:fixed;left:50%;top:40%;transform:translateX(-50%);color:${color};font:bold 22px monospace;z-index:15;pointer-events:none;`;
  document.body.appendChild(el);
  let life=60;
  const tick=()=>{ life--; el.style.top=(40 - (60-life)/3)+'%'; el.style.opacity=life/60; if(life<=0) el.remove(); else requestAnimationFrame(tick); };
  tick();
}

function updateUI(){
  document.getElementById('score').innerText = score;
  document.getElementById('combo').innerText = combo.toFixed(1);
}

function restart(){
  blocks.forEach(b=>scene.remove(b.group));
  blocks=[]; score=0; combo=1; comboN=0; charge=0; state='idle';
  leopard.position.set(0,0,0); leopard.scale.set(1,1,1); leopard.rotation.set(0,0,0);
  document.getElementById('over').style.display='none';
  spawnFirst(); updateUI();
}

// ---------- 循环 ----------
let last = performance.now();
function loop(now){
  const dt = Math.min((now-last)/1000, 0.033);
  last = now;
  update(dt);
  renderer.render(scene, camera);
  requestAnimationFrame(loop);
}
spawnFirst(); updateUI();
requestAnimationFrame(loop);

addEventListener('resize', ()=>{
  camera.aspect = innerWidth/innerHeight; camera.updateProjectionMatrix();
  renderer.setSize(innerWidth, innerHeight);
});
</script>
</body>
</html>

Game Source: 猎豹跳跳 3D

Creator: ElectricPenguin69

Libraries: three

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