Skip to content

207. 综合实战:开放世界(八)

Published:

上节把车绘制出来了:

2025-12-14 19.26.27.gif

这节给他加上物理的刚体,以及实现开车。

首先导出 world:

image.png

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

image.png

这里我们用 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;

看下效果:

2025-12-20 16.11.00.gif

可以看到,物理世界中的刚体大小是对的。

走过去试试:

2025-12-20 16.11.51.gif

现在就不会穿过了。

可以去掉这个可视化的盒子了:

image.png

然后实现开车。

其实就和控制人行走一样,这里不过是改成了控制车来移动:

我们加一下车辆和人的视角切换:

image.png

按 X 切换视角。

具体的切换就是把相机驾到哪个模型下的问题。

用到的车的模型也要导出下:

image.png

人的模型也是:

image.png

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 }

试一下:

2025-12-20 18.04.06.gif

这样就实现了视角切换。

案例代码上传了小册仓库

总结

这节我们做了下车辆和人的视角切换。

其实就是把相机加到哪个模型的 group 下的问题。

当然,切换只是第一步,下一节来实现控制车辆驾驶的功能。

评论