This article mainly stitches together several cases of the official website, without advanced or complicated logic and code;
Camera tracking is very common in scenes such as games or pavilions. It can be first-person or third-person. This time we bring a third-person camera tracking method
The following will introduce one by one
The effect is as follows
From the gif picture, we can know that the effect is that the mouse moves to control the rotation of the character, press and hold the
W
key, the character switchesrun
to the running state, releaseW
the key to switch the character toidle
the idle state, the animation is edited by the glf model, and will be disassembled later.
So let's go step by step
Create initial scene
environment
The APIs used in the basic scene are scene
scene, PerspectiveCamera
perspective camera, DirectionalLight
parallel light, HemisphereLight
hemispherical light, and WebGLRenderer
renderer. The code will not be repeated here.
ground
The ground uses texture
textures. First, create a PlaneGeometry
plane. The material uses MeshLambertMaterial
a grid material. Its main function is to sense light and prepare for subsequent shadowing. This code does not include shadows. Interested students can learn by themselves;
The texture is provided by the official website grid.png
, and then used TextureLoader
to load and handed over to the material for rendering. Set the base plate size to 1000*1000, and the X axis of the created plane by default is Math.PI (vertical). Use ration to modify the X axis of the plane direction to (horizontal -0.5*Math.PI
) ,code show as below:
const geometry = new THREE.PlaneGeometry(PlaneSize, PlaneSize);
const material = new THREE.MeshLambertMaterial({ color: 0xffffff, side: THREE.DoubleSide });
const plane = new THREE.Mesh(geometry, material);
plane.receiveShadow = true;
textureLoader.load('../src/assets/textures/grid.png', function (texture) {
texture.wrapS = THREE.RepeatWrapping;
texture.wrapT = THREE.RepeatWrapping;
texture.repeat.set(1000, 1000);
plane.material.map = texture;
plane.material.needsUpdate = true;
});
plane.rotation.set(-0.5 * Math.PI, 0, 0);
Model
load model
Xbot.glb
Select the model file provided by the official website
Simply encapsulate a loadGltf
loader method
export function loadGltf(url: string) {
return new Promise<Object>((resolve, reject) => {
gltfLoader.load(url, function (gltf) {
resolve(gltf)
});
})
}
The loaded file structure contains scene
scenes and animations
animation collections. There are 7 default animations, and we will use idle
, run
, walk
three animations later,
Register the AnimationMixer animator
遍历animations
收集到所有动画的名称和动画内容,
收集动画
const animations = xbot.animations
playerMixer = new THREE.AnimationMixer(XBot);
for (let i = 0; i < animations.length; i++) {
const clip = animations[i];
const action = playerMixer.clipAction(clip);
action.clampWhenFinished = true;
actions[clip.name] = action
createHandleButton(clip.name)
}
clampWhenFinished
字段的作用是在执行一个单次动画后恢复成上一个或者指定的某个动画,目前用不到,如果在运动过程中执行跳跃或者打招呼的动画可以使用这个字段
目前我们收集了所有的动画到actions
变量内,actions需要设置成全局的变量,以供后续使用
修改动画
playerMixer = new THREE.AnimationMixer(XBot);
这行代码就是注册动画器,在render运行过程中用来更新动画的,
const dt = playerClock.getDelta();
if (playerMixer) playerMixer.update(dt);
收集到所有动画后,将模型动画默认动画设置为idle
playerActiveAction = actions['idle'];
playerActiveAction.play();
playerActiveAction
字段是全局变量,用来存储模型当前执行的动画,切换模型动画时,将即将更新的动画previousAction
和当前动画进行对比,如果属于不同的动画,退出当前动画,执行新的动画
/**
*
* @param name 下一个动画名称
* @param duration 过渡时间
*/
function fadeToAction(name: string, duration = 0.5) {
previousAction = playerActiveAction;
playerActiveAction = actions[name];
if (previousAction !== playerActiveAction) {
previousAction.fadeOut(duration);
}
playerActiveAction
.reset()
.setEffectiveTimeScale(1)
.setEffectiveWeight(1)
.fadeIn(duration)
.play();
}
setEffectiveTimeScale
为动画执行缩放,超过1为加速播放,低于1为缓慢播放,fadeIn
字段为执行过渡时间,与fadeOut
作用相同,不过含义不同,fadeOut
为取消动画时的过渡时间,下面看一下效果
动画成果展示
在播放动画时,添加一个角色的骨骼,这样更能直观的看到各个关节和部位的关系,对于three.js绘制骨骼动画也有帮助,three.js骨骼动画一般用于物理引擎下的两个物体链接,比如钟摆,竹蜻蜓等
const skeleton = new THREE.SkeletonHelper(XBot);
skeleton.visible = true;
helperGroup.add(skeleton);
指针控制器
指针锁定控制器 实现原理是采用js中的requestpointerlock
API,在实例化PointerLockControls
时,接受两个参数,源码中接受camera和HTMLElement(可选),在实验的过程中,发现第二个参数camera
继承了Object3D
,所以将mesh或者group作为第一个参数传入也没有关系,他们的基类都是Object3D(Camera extends Object3D
);
内部实现原理就是锁定指针,获取指针移动位置将指定的向量提供给moveRight
(向右)、moveForward
(向前)两个方法,从而修改第一个参数camera
的矩阵,实现摄像头的旋转和移动,当然,过程中也可以监听控制器的锁定状态,来做一些事情,其API在官网也可以查询到,这里不做赘述;
实现指针控制器
加载到模型后,将模型传入到控制器中,我们最终实现的效果是利用指针控制角色,并实现镜头跟踪,所以只修改模型的矩阵即可
controls = new PointerLockControls(XBot, document.body);
controls.maxPolarAngle = Math.PI * 0.5
controls.minPolarAngle = Math.PI * 0.5
document.body.addEventListener('click', function () {
controls.lock();
});
controls.lock();
锁定指针,使指针控制器生效,controls.minPolarAngle = Math.PI * 0.5
,设置控制器上仰和下俯的角度限制,这里设置的相当于禁止修改模型的上下转动,只左右转动
计算转动和移动的向量
在render函数中,计算一下每次更新的用时,使用performance.now()
毫秒级别,这样相对更精准,在render方法中调用updateControls
方法
function updateControls() {
const time = performance.now();
// 指针控制器锁定时计算方向和速度
if (controls.isLocked === true) {
const delta = (time - prevTime) / 1000;
// 计算每次更新时的速度
velocity.x -= velocity.x * 10.0 * delta;
velocity.z -= velocity.z * 10.0 * delta;
// 计算z轴方向数值 1向前,0站立,-1向后
direction.z = Number(moveForward) - Number(moveBackward);
// 将方向归一化
direction.normalize(); // this ensures consistent movements in all directions
// 如果按下向前或者向后,计算移动速度
if (moveForward || moveBackward) velocity.z -= direction.z * 40.0 * delta;
// 角色前后移动方向修改,z如果>0向前移动反之向后移动,如果为0站立
controls.moveForward(velocity.z * delta);
}
prevTime = time;
}
定义一个全局变量prevTime
(时间)、velocity
(速度)、direction
(方向),更新时根据moveForward
和moveBackward
是否为true判断当前角色是否需要向前或者向后移动,具体过程可以看代码和备注。
键盘监听
监听键盘w
键抬起或者按下修改moveForward
字段
监听键盘按下
const onKeyDown = function (event) {
switch (event.code) {
case 'ArrowUp':
case 'KeyW':
if (!moveForward&&controls.isLocked) { // 按下时移动角色,将角色动画状态改为run
fadeToAction('run')
}
moveForward = true; // 按下的标记
break;
}
}
监听键盘抬起
const onKeyUp = function (event) {
switch (event.code) {
case 'ArrowUp':
case 'KeyW':
if (moveForward&&controls.isLocked) { // 抬起时角色动画状态改为站立
fadeToAction('idle')
}
moveForward = false;
break;
}
};
镜头跟踪
计算摄像头位置
首先计算出动画模型的高度,使用box3中的getSize
,会获取到模型的具体尺寸,将得到一个三维向量,y字段就是模型的高度,将该字段存储起来给镜头使用。
const XBotSize = new THREE.Vector3();
const box = new THREE.Box3()
box.expandByObject(XBot)
box.getSize(XBotSize)
之前的代码moveForward
已经将角色的位置和方向进行了修改,所以在render
函数中实时获取角色的世界坐标,和世界方向,再通过相应的计算,获取到摄像头的位置,大概如下图所示:
1、首先计算出角色的世界方向并归一化normalize
获得方向;
2. Multiply the normalized coordinates by the corresponding distance multiplyScalar
;
3. Set the camera directly behind the character, and reverse the distance negate
;
4. Set the camera height XBotSize.y
;
As above, calculate the coordinates behind the character, and then add the previously normalized vector to the world coordinates of the character, so that the position of the camera will move according to the position of the character;
The following is the code for the above logic, which is called in the render method
if ( cone && XBot) {
let xbotV3 = new THREE.Vector3();
XBot.getWorldPosition(xbotV3);
const playerDirection = new THREE.Vector3()
XBot.getWorldDirection(playerDirection);
playerDirection.normalize();
playerDirection.multiplyScalar(5)
// 2为高度的增量,可以让摄像头在角色的后上方,以俯视的角度观察角色
camera.position.copy(playerDirection.negate().setY(XBotSize.y + 2).add(xbotV3));
camera.lookAt(xbotV3.clone().setY(XBotSize.y))
camera.updateProjectionMatrix()
updateControls()
}
position
Modify the camera position, lookAt
modify the camera direction, and the camera direction looks at the character's head. This is the whole logic of lens tracking. Welcome to the comment area to communicate.
You can do many things in the follow-up, such as making a third-person RPG game, you can add rays through ray for collision detection, you can also run it in the environment of the ammo physics engine, you can also adjust the position of the parameters, change it to the first person, and do some first person
multiplyScalar
. Things to do
historical article
# Javascript basics to write a fun click effect
#Javascript based mouse drag and drop
# three.js Create a small game scene (pick up weapons, receive tasks, spawn monsters)