这节加一下阴影,还有人物行走的控制。
首先加一下阴影,增加真实感:
开一下渲染器的阴影:

renderer.shadowMap.enabled = true;
阳光的阴影以及投影矩阵:

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;
然后给地面加上接收阴影:

groundMesh.receiveShadow = true;
方块和人加上投射阴影:

mesh.castShadow = true;
mesh.receiveShadow = true;

characterModel.traverse((child) => {
if (child.isMesh) {
child.castShadow = true;
child.receiveShadow = true;
}
});
看下效果:

有阴影后真实多了。
然后加上人物行走的控制:
把 OrbitControls 禁用掉,导出 camera:

export { camera }
把相机加到人物的 group 里,跟随人一起移动:

let currentAction = null;
currentAction = idleAction;
characterModel.add(camera);
// 设置相机相对人物的位置(局部坐标)
camera.position.set(0, 1.5, 2.5); // 在人物后上方
camera.lookAt(0, 1, 0);
idleAction.play();
记录下当前的动画,后面切换的时候用。
把相机加到人物的 group 里,放到人后面,这样镜头就可以跟随人一起移动了。
试下效果:

然后加一下人物移动和方向改变的逻辑:

下面的渲染循环做了 mixer.update,上面的就可以去掉了。
代码比较多,我们一部分一部分看:
const keyPressed = {
w: false,
a: false,
s: false,
d: false
};
document.addEventListener('keydown', e => {
switch(e.code) {
case 'KeyA':
keyPressed.a = true;
break;
case 'KeyW':
keyPressed.w = true;
break;
case 'KeyS':
keyPressed.s = true;
break;
case 'KeyD':
keyPressed.d = true;
break;
}
});
document.addEventListener('keyup', e => {
switch(e.code) {
case 'KeyA':
keyPressed.a = false;
break;
case 'KeyW':
keyPressed.w = false;
break;
case 'KeyS':
keyPressed.s = false;
break;
case 'KeyD':
keyPressed.d = false;
break;
}
});
const moveSpeed = 3;
function updatePlayerMovement(deltaTime) {
if (!characterModel) return;
const forward = new THREE.Vector3();
camera.getWorldDirection(forward);
forward.y = 0;
forward.normalize();
const right = new THREE.Vector3();
right.crossVectors(new THREE.Vector3(0, 1, 0), forward).normalize();
let isMoving = false;
const moveDirection = new THREE.Vector3(0, 0, 0);
if (keyPressed.w) {
moveDirection.add(forward);
isMoving = true;
}
if (keyPressed.s) {
moveDirection.add(forward.clone().negate());
isMoving = true;
}
if (keyPressed.a) {
const leftDir = right;
moveDirection.add(leftDir);
isMoving = true;
}
if (keyPressed.d) {
const rightDir = right.clone().negate();
moveDirection.add(rightDir);
isMoving = true;
}
if (isMoving) {
moveDirection.normalize();
const moveOffset = moveDirection.multiplyScalar(moveSpeed * deltaTime);
characterModel.position.add(moveOffset);
}
if (mixer) {
if (isMoving && currentAction !== walkAction) {
if (currentAction) currentAction.stop();
walkAction.play();
currentAction = walkAction;
} else if (!isMoving && currentAction !== idleAction) {
if (currentAction) currentAction.stop();
idleAction.play();
currentAction = idleAction;
}
}
}
const clock = new THREE.Clock();
function animate() {
requestAnimationFrame(animate);
const dt = Math.min(clock.getDelta(), 0.1);
updatePlayerMovement(dt);
if (mixer) {
mixer.update(dt);
}
}
animate();
首先,记录 WASD 每个键的按下状态:

然后基于按下的键来调整方向:

camera.getWorldDirection 拿到相机方向,也就拿到了前后方向。
用叉乘计算出向右的方向,也就拿到了左右方向。
根据按下的 WSAD 来设置方向,速度 * 时间,在这个方向上运动。
此外,还要切换动画:

根据是否在运动,切换走路和静止动画。

渲染循环里调用,传入每一帧的间隔时间,用于计算移动距离。
试下效果:

这样,前后左右移动就实现了。
案例代码上传了小册仓库
总结
这节我们实现了相机跟随人物移动,以及键盘控制前后左右运动。
相机跟随就是把 camera 加到玩家的 group,这样只要改变人物位置、方向就可以了,相机会跟着动。
键盘控制前后左右运动,就是拿到相机方向,通过叉乘计算左右方向,然后每一帧计算移动距离改变位置就好了。
下节我们继续添加其他功能。