《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. 记录转动步数、计时闯关
相关推荐
kdxiaojie1 小时前
Linux 驱动研究 —— V4L2 (3)
linux·运维·笔记·学习
方向研究2 小时前
金融游戏的训练价值
人工智能·游戏·金融
facaixxx20242 小时前
雨云游戏服务器MCSM面板指南:低延迟BGP线路(一键开服)
运维·服务器·游戏
qq_529599382 小时前
react js循环滚动
前端·javascript·react.js
十月的皮皮2 小时前
C语言学习笔记20260717-预处理机制
c语言·笔记·学习
come112342 小时前
Vue 3 与现代 JavaScript 常用语法指南
前端·javascript·vue.js
dsyyyyy11012 小时前
用JavaScript实现排序算法
开发语言·javascript·排序算法
槑有老呆2 小时前
从零搭建 LLM 流式对话前端:一文读懂 SSE、二进制流与 Vue 3 响应式
前端·javascript
Zzj_tju2 小时前
Alignment Science 与可控生成:偏好学习、宪法式训练和可验证对齐
人工智能·学习·语言模型