如何在React中实现确定性3D骰子?整合dice-box-threejs或基于r3f重构
在React中实现确定性3D骰子投掷的两种方案
你不需要从零重构,可以直接在React项目中集成dice-box-threejs,也可以选择基于react-three-fiber(r3f)重构核心逻辑,以下是具体实现路径:
方案一:直接集成dice-box-threejs到React
dice-box-threejs是原生Three.js库,但可以通过React的生命周期钩子完成集成,步骤如下:
安装依赖
先安装库本身及依赖的Three.js:npm install dice-box-threejs three在React组件中初始化实例
使用useRef存储dice-box实例,通过useEffect在组件挂载时初始化,同时处理卸载时的清理:import React, { useRef, useEffect } from 'react'; import DiceBox from 'dice-box-threejs'; const DiceContainer = () => { const containerRef = useRef(null); const diceBoxRef = useRef(null); useEffect(() => { if (!containerRef.current) return; // 初始化dice-box实例 diceBoxRef.current = new DiceBox(containerRef.current, { scale: 10, gravity: -9.8, }); // 执行确定性投掷:指定骰子类型、目标点数、初始状态 const rollDice = async () => { const result = await diceBoxRef.current.roll({ dice: [ { type: 'd6', result: 5, // 预设朝上点数 position: { x: 0, y: 10, z: 0 }, rotation: { x: 0, y: 0, z: 0 }, velocity: { x: 2, y: 0, z: 3 }, }, ], }); console.log('投掷结果:', result); }; rollDice(); // 组件卸载时销毁实例,避免内存泄漏 return () => { if (diceBoxRef.current) { diceBoxRef.current.destroy(); } }; }, []); return <div ref={containerRef} style={{ width: '800px', height: '600px' }} />; }; export default DiceContainer;注意事项
- dice-box会自行管理Three.js的渲染循环,无需和r3f的渲染逻辑冲突,只需给它独立的DOM容器即可
- 如果需要和r3f场景整合,可以将dice-box的
scene对象合并到r3f的场景中,但需注意相机、灯光等资源的协调
方案二:基于react-three-fiber重构核心逻辑
如果需要深度整合React生态(比如用r3f的hooks、状态管理,或和其他3D组件联动),可以基于r3f重构,核心要点:
依赖准备
使用r3f的物理引擎绑定库@react-three/cannon处理物理模拟:npm install @react-three/fiber @react-three/drei @react-three/cannon three核心实现步骤
- 骰子模型:复用dice-box的3D模型(可从其源码中提取),或用
@react-three/drei的基础几何体构建 - 确定性物理模拟:
- 给
CannonProvider设置固定的seed值,确保物理模拟结果一致 - 为每个骰子刚体设置精确的初始位置、旋转角度、线性/角速度,反向推导这些参数以匹配目标点数(可参考dice-box的源码逻辑)
- 给
- 点数检测:在物理模拟停止后,通过骰子的旋转角度计算朝上的面,确保与预设值匹配
简化示例:
import React, { useRef, useEffect } from 'react'; import { Canvas } from '@react-three/fiber'; import { Physics, useBox } from '@react-three/cannon'; import { OrbitControls } from '@react-three/drei'; const D6Dice = ({ position, rotation, velocity, targetResult }) => { const meshRef = useRef(null); const [ref] = useBox(() => ({ mass: 1, position, rotation, velocity, args: [1, 1, 1], // 骰子碰撞盒尺寸 })); // 模拟停止后检测点数,确保匹配目标值 useEffect(() => { const checkResult = () => { if (!meshRef.current) return; // 此处参考dice-box的点数检测逻辑,根据骰子旋转计算朝上的面 // 若结果不符,可微调初始参数重新模拟 }; const interval = setInterval(checkResult, 100); return () => clearInterval(interval); }, [targetResult]); return ( <mesh ref={(node) => { ref(node); meshRef.current = node; }}> <boxGeometry args={[1, 1, 1]} /> <meshStandardMaterial color="#ffffff" /> {/* 添加点数纹理,此处省略 */} </mesh> ); }; const DiceScene = () => { return ( <Canvas camera={{ position: [0, 5, 10] }}> <ambientLight /> <pointLight position={[10, 10, 10]} /> <Physics seed={123}> {/* 固定种子确保确定性 */} {/* 地面 */} <mesh rotation={[-Math.PI / 2, 0, 0]} position={[0, -2, 0]}> <planeGeometry args={[20, 20]} /> <meshStandardMaterial color="#888888" /> <useBox args={[20, 0.1, 20]} mass={0} /> </mesh> {/* 预设点数为5的骰子 */} <D6Dice position={[0, 5, 0]} rotation={[0, 0, 0]} velocity={[2, 0, 3]} targetResult={5} /> </Physics> <OrbitControls /> </Canvas> ); }; export default DiceScene;- 骰子模型:复用dice-box的3D模型(可从其源码中提取),或用
关键细节
- 反向推导初始参数需要参考dice-box中“根据目标点数计算初始状态”的逻辑,确保物理模拟结束后骰子朝上的面符合预期
- 固定物理引擎的种子是实现确定性的核心,
@react-three/cannon的Physics组件支持通过seed参数设置
内容的提问来源于stack exchange,提问作者Kairei
相关产品推荐
相关产品推荐

