猎豹跳跳 3D
by ElectricPenguin69272 lines9.1 KB🛠️ Three.js (3D graphics)
<!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.