Three.js 可操控3D魔方(键盘转动+打乱+复原)
直接复制整段HTML打开即可运行,WASD/方向键控层旋转,一键打乱、一键复原
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>Three.js 可玩3D魔方</title>
<style>
* {margin:0;padding:0;box-sizing:border-box;}
body {overflow:hidden;background:#121218;font-family:sans-serif;}
canvas {display:block;}
.btn-box{position:fixed;top:20px;left:20px;z-index:999;}
button{padding:8px 16px;margin:0 5px;border:none;border-radius:4px;cursor:pointer;background:#2563eb;color:#fff;}
</style>
</head>
<body>
<div class="btn-box">
<button id="randomBtn">打乱魔方</button>
<button id="resetBtn">一键复原</button>
</div>
<script src="https://cdn.jsdelivr.net/npm/three@0.158.0/build/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.158.0/examples/js/controls/OrbitControls.js"></script>
<script>
// 场景初始化
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x121218);
const camera = new THREE.PerspectiveCamera(60,innerWidth/innerHeight,0.1,200);
camera.position.set(9,9,15);
const renderer = new THREE.WebGLRenderer({antialias:true});
renderer.setSize(innerWidth,innerHeight);
document.body.appendChild(renderer.domElement);
// 视角控制器
const controls = new THREE.OrbitControls(camera,renderer.domElement);
controls.enableDamping = true;
// 灯光
scene.add(new THREE.AmbientLight(0xffffff,0.5));
const dirLight = new THREE.DirectionalLight(0xffffff,0.7);
dirLight.position.set(15,20,10);
scene.add(dirLight);
// 魔方六色:前、后、左、右、上、下
const COLORS = [
0xffffff, // 前白
0xffff00, // 后黄
0x0000ff, // 左蓝
0xff0000, // 右红
0x00ff00, // 上绿
0xffa500 // 下橙
];
const OFFSET = 1.06;
let cubies = [];
let rubikRoot = new THREE.Group();
// 创建单个魔方小块
function createCubie(x,y,z){
const geo = new THREE.BoxGeometry(1,1,1);
const mats = COLORS.map(c=>new THREE.MeshLambertMaterial({color:c}));
const cube = new THREE.Mesh(geo,mats);
cube.userData = {ox:x,oy:y,oz:z};
cube.position.set(x*OFFSET,y*OFFSET,z*OFFSET);
return cube;
}
// 生成完整3阶魔方
function initRubik(){
cubies = [];
rubikRoot.clear();
for(let x=-1;x<=1;x++){
for(let y=-1;y<=1;y++){
for(let z=-1;z<=1;z++){
let c = createCubie(x,y,z);
cubies.push(c);
rubikRoot.add(c);
}
}
}
scene.add(rubikRoot);
}
initRubik();
// 旋转指定层
function rotateLayer(axis, idx, dir=1){
let targetGroup = new THREE.Group();
let targetList = [];
cubies.forEach(c=>{
let pos = c.position;
let val;
if(axis === 'x') val = Math.round(pos.x/OFFSET);
if(axis === 'y') val = Math.round(pos.y/OFFSET);
if(axis === 'z') val = Math.round(pos.z/OFFSET);
if(val === idx){
targetList.push(c);
rubikRoot.remove(c);
targetGroup.add(c);
}
});
scene.add(targetGroup);
let angle = Math.PI / 2 * dir;
let rotObj = {v:0};
function rotateAnimate(){
if(rotObj.v >= angle){
targetGroup.rotation[axis] = angle;
targetList.forEach(item=>{
let wp = new THREE.Vector3();
item.getWorldPosition(wp);
let q = new THREE.Quaternion();
item.getWorldQuaternion(q);
rubikRoot.attach(item);
item.position.copy(wp);
item.quaternion.copy(q);
});
scene.remove(targetGroup);
return;
}
rotObj.v += 0.15;
targetGroup.rotation[axis] = rotObj.v;
requestAnimationFrame(rotateAnimate);
}
rotateAnimate();
}
// 键盘控制
window.addEventListener('keydown',e=>{
switch(e.key.toLowerCase()){
case 'w': rotateLayer('y',1,1);break; // 上层右转
case 's': rotateLayer('y',-1,-1);break; // 下层左转
case 'a': rotateLayer('x',-1,1);break; // 左层顺转
case 'd': rotateLayer('x',1,-1);break; // 右层逆转
case 'q': rotateLayer('z',1,1);break; // 前层顺转
case 'e': rotateLayer('z',-1,-1);break; // 后层逆转
}
});
// 打乱魔方
document.getElementById('randomBtn').onclick = ()=>{
let ops = [
()=>rotateLayer('y',1,1),
()=>rotateLayer('y',-1,-1),
()=>rotateLayer('x',1,-1),
()=>rotateLayer('x',-1,1),
()=>rotateLayer('z',1,1),
()=>rotateLayer('z',-1,-1)
];
let times = 20;
let i=0;
function randRun(){
if(i>=times) return;
let fn = ops[Math.floor(Math.random()*ops.length)];
fn();
i++;
setTimeout(randRun,220);
}
randRun();
};
// 一键复原
document.getElementById('resetBtn').onclick = ()=>{
initRubik();
};
// 窗口适配
window.onresize = ()=>{
camera.aspect = innerWidth/innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(innerWidth,innerHeight);
};
// 渲染循环
function animate(){
requestAnimationFrame(animate);
controls.update();
renderer.render(scene,camera);
}
animate();
</script>
</body>
</html>
操作说明
- 鼠标操作
- 左键拖拽:旋转视角
- 滚轮:缩放远近
- 右键拖拽:平移画面
- 键盘按键控魔方转动
- W:顶层向右旋转
- S:底层向左旋转
- A:左侧面顺时针转
- D:右侧面逆时针转
- Q:前面顺时针转
- E:后面逆时针转
- 按钮功能
- 打乱魔方:自动随机转动20步打乱
- 一键复原:瞬间重置回初始完整状态
后续可加功能
- 加入魔方还原算法自动求解
- 加数字贴纸、边框黑线更逼真
- 手机触屏滑动转层
- 记录转动步数、计时闯关