前言
本文将使用 three.js + cannon-es 构建一个 3D 的博饼 小游戏,在这里你能够学到如何通过 three.js 构建一个骰子🎲,如何通过 cannon-es 为骰子添加物理动作,下面就让我们开始介绍。
构造骰子
首先骰子是一个正方体,但是又有一点不同,不同在于他的边角都是圆的,并且每个面有不同个数的凹陷。
构建圆边角的正方体
首先让我们来构建圆边角的正方体,你可以通过 RoundedBoxGeometry类 来扩展 BoxGeometry 类,你可以在 examples/jsm/geometries/ 文件夹下找到这个类文件。
首先我们要清楚,要构建一个物体也就是网格模型Mesh,需要物体的形状(Geometry)以及物体的外观(Material),上面的 RoundedBoxGeometry 类帮我们解决了形状的问题,
在源码中可以发现,我们可以传入 5 个 参数,其中前三个参数对应就是物体的长宽高,第四个参数对应就是在三个方向的分的段数,重点在于第五个参数,决定了正方体的圆角
我们就可以通过 const geometry = new RoundedBoxGeometry(1, 1, 1, 3, 0.1); 来构建一个带有圆角的正方体形状
为每一个面上色
接着我们来创建我们需要的材质,你可以用最简单的单一颜色材质就可以看到之前创建的形状。
//创建一个材质对象Material
const material = new THREE.MeshBasicMaterial({
color: 0xff0000,//0xff0000设置材质颜色为红色
});
然后我们需要用到 THREE.MeshBasicMaterial 来为每个面创建材质,这个函数可以传入一个对象,对象的 map 属性为一个 canvas 对象,然后这个面就会展示这个 canvas 画的内容,然后我们就可以通过 canvas 来画出每一个面的点。
var materials = [
new THREE.MeshBasicMaterial({ map: new THREE.CanvasTexture(getCanvas(1)) }), // right
new THREE.MeshBasicMaterial({ map: new THREE.CanvasTexture(getCanvas(6)) }), // left
new THREE.MeshBasicMaterial({ map: new THREE.CanvasTexture(getCanvas(3)) }), // top
new THREE.MeshBasicMaterial({ map: new THREE.CanvasTexture(getCanvas(4)) }), // bottom
new THREE.MeshBasicMaterial({ map: new THREE.CanvasTexture(getCanvas(5)) }), // back
new THREE.MeshBasicMaterial({ map: new THREE.CanvasTexture(getCanvas(2)) }), // front
];
然后让我们将这个骰子添加到场景当中,并且我这里使用了 OrbitControls 方便观察每一个面的状况
为骰子添加动画
three.js 不过是帮我们创建好了 3D 模型,这其中不包括一些物理引擎或者其他的来帮助我们实现动画。
所以我们需要通过 cannon-es 来构建物理相关的动画,实现扔骰子的过程。
在这个过程当中,我们需要实现
- 骰子是一个刚体,它不应该发生形变
- 用一个平面来代表地面
- 碰撞检测,骰子与地面进行碰撞以及之后的弹跳逻辑
- 投掷骰子使得它们落在地面上
首先我们利用 new CANNON.World({}) 来创造一个物理世界,并且把我们的骰子放入这个物理世界当中,上面的新建骰子模型的方法我们可以抽出来,用于循环创建多个骰子。
const diceArray = [];
diceMesh = createDiceMesh();
for (let i = 0; i < params.numberOfDice; i++) {
diceArray.push(createDice());
}
function createDice() {
const mesh = diceMesh.clone();
scene.add(mesh);
const body = new CANNON.Body({
mass: 1,
shape: new CANNON.Box(new CANNON.Vec3(.5, .5, .5)),
});
physicsWorld.addBody(body);
return {mesh, body};
}
然后我们把创建好的骰子先放到物理世界中去
diceArray.forEach((d, dIdx) => {
d.body.position = new CANNON.Vec3(3, dIdx * 1.5, 0);
d.mesh.position.copy(d.body.position);
});
这时候我们就可以见到固定在天上的骰子,然后我们来为骰子创建一个地面,这样等下骰子才有地方可以碰撞。
function createFloor() {
// Three.js (visible) object
const floor = new THREE.Mesh(
new THREE.PlaneGeometry(1000, 1000),
new THREE.ShadowMaterial({
opacity: .1
})
)
floor.receiveShadow = true;
floor.position.y = -7;
floor.quaternion.setFromAxisAngle(new THREE.Vector3(-1, 0, 0), Math.PI * .5);
scene.add(floor);
// Cannon-es (physical) object
const floorBody = new CANNON.Body({
type: CANNON.Body.STATIC,
shape: new CANNON.Plane(),
});
floorBody.position.copy(floor.position);
floorBody.quaternion.copy(floor.quaternion);
physicsWorld.addBody(floorBody);
}
将创建好的地面丢入物理世界当中,并且确定好二者之间的初始距离,然后我们来为这个事件添加重力。
let physicsWorld = new CANNON.World({
allowSleep: true,
gravity: new CANNON.Vec3(0, -50, 0),
})
这时候骰子就会开始下落了,但是因为只有一个下落的力,所有就仅仅只是下落而已。
通过修改弹力,初始化的时候施加一个随即方向的力,以及在每次渲染的时候需要去更新骰子,这时候我们就可以得到一个比较灵动的扔骰子的动作了。
最后我们可以设定一个按钮,点击这个按钮之后,就可以重置骰子的状态,重新投掷
function throwDice () {
diceArray.forEach((d, dIdx) => {
d.body.position = new CANNON.Vec3(4, dIdx * 1.5 + 5, 0);
d.mesh.position.copy(d.body.position);
d.mesh.rotation.set(1 * Math.PI * Math.random(), 0, 2 * Math.PI * Math.random())
d.body.quaternion.copy(d.mesh.quaternion);
const force = 1 + 5 * Math.random();
d.body.applyImpulse(
new CANNON.Vec3(-force, force, 0)
);
});
}
综上,我们就能够得到一个简单的扔骰子小游戏了。