上节把车绘制出来了:

这节给他加上物理的刚体,以及实现开车。
首先导出 world:

创建一个 Box 立方体来作为车辆在物理世界的形状:

这里我们用 BoxGeometry 可视化了一下:
import * as THREE from 'three';
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
import * as CANNON from 'cannon-es';
import { world } from './mesh.js';
const loader = new GLTFLoader();
const group = new THREE.Group();
// 车的位置和尺寸
const carSize = { width: 2, height: 1.31, depth: 5 }; // 车的碰撞盒尺寸
const carPosition = { x: 0, y: carSize.height / 2, z: 10 };
// 创建车的物理碰撞体
const carBody = new CANNON.Body({
mass: 1500, // 车的质量(千克),可以移动
position: new CANNON.Vec3(carPosition.x, carPosition.y, carPosition.z),
linearDamping: 0.3, // 线性阻尼,模拟空气阻力
angularDamping: 0.3 // 角度阻尼,防止车身旋转过度
});
carBody.addShape(new CANNON.Box(new CANNON.Vec3(
carSize.width / 2,
carSize.height / 2,
carSize.depth / 2
)));
world.addBody(carBody);
export { carBody };
// 可视化碰撞盒(调试用)
const carBoxGeo = new THREE.BoxGeometry(carSize.width, carSize.height, carSize.depth);
const carBoxMat = new THREE.MeshPhongMaterial({
color: 0xff0000,
transparent: true,
opacity: 0.3,
wireframe: true
});
const carBoxMesh = new THREE.Mesh(carBoxGeo, carBoxMat);
carBoxMesh.position.set(carPosition.x, carPosition.y, carPosition.z);
group.add(carBoxMesh);
export const loadPromise = loader.loadAsync("./car.glb");
let carModel = null;
loadPromise.then(gltf => {
gltf.scene.traverse((child) => {
if (child.isMesh) {
child.castShadow = true;
child.receiveShadow = true;
}
});
carModel = gltf.scene;
carModel.position.set(0, 0, 10);
group.add(carModel);
console.log(gltf);
})
export default group;
看下效果:

可以看到,物理世界中的刚体大小是对的。
走过去试试:

现在就不会穿过了。
可以去掉这个可视化的盒子了:

然后实现开车。
其实就和控制人行走一样,这里不过是改成了控制车来移动:
我们加一下车辆和人的视角切换:

按 X 切换视角。
具体的切换就是把相机驾到哪个模型下的问题。
用到的车的模型也要导出下:

人的模型也是:

import './style.css';
import * as THREE from 'three';
import {
OrbitControls
} from 'three/addons/controls/OrbitControls.js';
import mesh, { characterModel } from './mesh.js';
import car, { carModel, carBody } from './car.js';
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x87ceeb);
scene.add(mesh);
scene.add(car);
scene.add(new THREE.AmbientLight(0xffffff, 0.6));
const sun = new THREE.DirectionalLight(0xffffff, 0.8);
sun.position.set(20, 30, 10);
sun.castShadow = true;
sun.shadow.camera.left = -30;
sun.shadow.camera.right = 30;
sun.shadow.camera.top = 30;
sun.shadow.camera.bottom = -30;
sun.shadow.mapSize.width = 2048;
sun.shadow.mapSize.height = 2048;
scene.add(sun);
const width = window.innerWidth;
const height = window.innerHeight;
const camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 200);
camera.position.set(0, 1.6, 5);
camera.lookAt(0, 0, 0);
const renderer = new THREE.WebGLRenderer({
antialias: true
});
renderer.setSize(width, height)
renderer.shadowMap.enabled = true;
function render() {
renderer.render(scene, camera);
requestAnimationFrame(render);
}
render();
document.body.append(renderer.domElement);
// const controls = new OrbitControls(camera, renderer.domElement);
// 车辆视角切换
export let isCarView = false;
// 监听键盘事件
window.addEventListener('keydown', (event) => {
if (event.key === 'x' || event.key === 'X') {
isCarView = !isCarView;
if (isCarView) {
// 切换到车辆视角
// 将相机添加到车辆模型下,位置在车后上方,能看到车辆
if (carModel && characterModel) {
characterModel.remove(camera);
carModel.add(camera);
// 相机在车后面,看向车辆前进方向
camera.position.set(0, 3, -6);
camera.rotation.set(-0.1, Math.PI, 0); // 稍微向下看约6度,主要朝向前方
}
} else {
// 切换回人物视角
if (carModel && characterModel) {
carModel.remove(camera);
characterModel.add(camera);
camera.position.set(0, 1.5, 2.5);
camera.lookAt(0, 1, 0);
camera.rotation.x = 0;
}
}
}
});
export { camera }
试一下:

这样就实现了视角切换。
案例代码上传了小册仓库
总结
这节我们做了下车辆和人的视角切换。
其实就是把相机加到哪个模型的 group 下的问题。
当然,切换只是第一步,下一节来实现控制车辆驾驶的功能。