我们在玩一些 3D 游戏的时候,可以通过键盘、鼠标控制人物移动:

并且视野也会跟着改变。
那如果我们也要实现这样的效果呢?
如何实现键盘、鼠标控制人物行走方向,如何实现镜头跟随呢?
这节开始我们就来实现一下。
创建项目:
npx create-vite player-roaming

进入项目,安装依赖:
pnpm install
pnpm install --save three
pnpm install --save-dev @types/three
先写下基础代码:
改下 src/main.js
import './style.css';
import * as THREE from 'three';
import {
OrbitControls
} from 'three/addons/controls/OrbitControls.js';
import mesh from './mesh.js';
const scene = new THREE.Scene();
scene.add(mesh);
const light = new THREE.DirectionalLight(0xffffff);
light.position.set(500, 300, 600);
scene.add(light);
const light2 = new THREE.AmbientLight();
scene.add(light2);
const axesHelper = new THREE.AxesHelper(1000);
scene.add(axesHelper);
const width = window.innerWidth;
const height = window.innerHeight;
const camera = new THREE.PerspectiveCamera(60, width / height, 1, 10000);
camera.position.set(0, 500, 500);
camera.lookAt(0, 0, 0);
const renderer = new THREE.WebGLRenderer({
antialias: true
});
renderer.setSize(width, height)
function render() {
renderer.render(scene, camera);
requestAnimationFrame(render);
}
render();
document.body.append(renderer.domElement);
const controls = new OrbitControls(camera, renderer.domElement);
创建 Scene、Light、Camera、Renderer
改下 style.css
body {
margin: 0;
}
然后创建 mesh.js
import * as THREE from 'three';
const geometry = new THREE.BoxGeometry(100, 100, 100);
const material = new THREE.MeshPhongMaterial({
color: 'orange'
})
const mesh = new THREE.Mesh(geometry, material);
export default mesh;
先画个立方体
跑一下:
npm run dev


然后我们找个人物行走的模型。
用 three.js 官方仓库里这个士兵的模型:
https://github.com/mrdoob/three.js/blob/dev/examples/models/gltf/Soldier.glb


代码里加载下:

import * as THREE from 'three';
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
const loader = new GLTFLoader();
const mesh = new THREE.Group();
loader.load("./Soldier.glb", function (gltf) {
console.log(gltf);
mesh.add(gltf.scene);
gltf.scene.scale.set(100, 100, 100);
})
export default mesh;

我们播放下它的行走动画:


const mixer = new THREE.AnimationMixer(mesh);
const clipAction = mixer.clipAction(gltf.animations[3]);
clipAction.play();
const clock = new THREE.Clock();
function render() {
const delta = clock.getDelta();
mixer.update(delta);
requestAnimationFrame(render);
}
render();

然后来实现人物行走。
玩游戏的时候是按下 W、A、S、D 控制人物行走。
这里我们先实现按 W 向前行走:

document.addEventListener('keydown', e => {
console.log(e.code + ' down');
});
document.addEventListener('keyup', e => {
console.log(e.code + ' up');
});
试一下:

我们用一个对象来记录每个键的按下状态:
let 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;
}
});
把渲染循环移到后面,这样我们可以在渲染循环里读取出来
如果按下 w 键,人物就往前走,否则不动:

const clock = new THREE.Clock();
const v = new THREE.Vector3(0, 0, -200);
function render() {
const deltaTime = clock.getDelta();
if(keyPressed.w) {
const movePos = v.clone().multiplyScalar(deltaTime);
mesh.position.add(movePos);
}
renderer.render(scene, camera);
requestAnimationFrame(render);
}
render();
v 是速度,这里方向是 z 轴负半轴方向走。
每次移动 v * t 速度 * 时间的距离。
试下效果:

这样,控制人物行走的基础功能就完成了。
案例代码上传了小册仓库
总结
这节我们开始做玩家漫游的效果,也就是射击游戏里那种第一人称感觉。
我们监听了 wasd 的按下状态,如果是按下,就在速度的方向上移动一段距离。
下节我们加上更多的加速、减速、后退等功能。