《Three.js 可操控3D魔方(键盘转动+打乱+复原)》

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>

操作说明

  1. 鼠标操作
  • 左键拖拽:旋转视角
  • 滚轮:缩放远近
  • 右键拖拽:平移画面
  1. 键盘按键控魔方转动
  • W:顶层向右旋转
  • S:底层向左旋转
  • A:左侧面顺时针转
  • D:右侧面逆时针转
  • Q:前面顺时针转
  • E:后面逆时针转
  1. 按钮功能
  • 打乱魔方:自动随机转动20步打乱
  • 一键复原:瞬间重置回初始完整状态

后续可加功能

  1. 加入魔方还原算法自动求解
  2. 加数字贴纸、边框黑线更逼真
  3. 手机触屏滑动转层
  4. 记录转动步数、计时闯关
相关推荐
智恒百亿5 小时前
RTX 5090 全场景技术应用解析:从游戏算力到专业生产力落地
大数据·服务器·游戏
cd_949217216 小时前
AI纹理和Substance Painter手绘纹理哪个更适合游戏资产制作?
人工智能·游戏·substance painter
单线程_016 小时前
从案例分析 Vue3 Tokenizer 源码一
前端·javascript·vue.js
德迅云安全-上官6 小时前
游戏攻防反套路:德迅云安全游戏盾如何破解黑产的“低成本精准打击”
网络·安全·游戏
浔溺6 小时前
al+大数据每日学习笔记26
笔记·学习
xx~t7 小时前
嵌入式——进程与线程1
linux·c语言·学习·嵌入式·进程与线程
大模型码小白7 小时前
AI 对话流性能调优:万级消息的虚拟滚动落地
java·大数据·前端·javascript·人工智能·算法·机器学习
m0_547486667 小时前
《JavaScript核心原理 》全套PPT课件2026
开发语言·javascript·ecmascript
老当益壮梁奶奶7 小时前
Linux软件编程学习笔记(七):线程分离与线程间通信详解
linux·c语言·c++·笔记·学习
xm_xm_xm_18 小时前
react17版本以前类组件常用操作
前端·javascript·react.js