前言
很多刚开始接触 3D 可能都研究过怎么实现一个人物的第三人称控制,从刚开始的觉得可能会很难,然后一步一步去摸索最后实现了,或者有人在摸索过程就结束了。
等你有一定理解后你会发现其实没那么难,特别是使用 Babylon.js 你会发现就是那么简单的,这边先看下效果,这个是根据目前大部分操作习惯来实现的,只允许8个方向旋转,同时动作切换和方向旋转都增加了缓动,建议到 PG 去体验。
一、前期准备
首先你现在需要做一个3D场景,接着找个角色模型,接着给角色模型加上骨骼,然后我们就可以给模型上骨骼动画了,为了方便控制不同状态切换你需要至少增加两个动画。
推荐使用 mixamo 躯干动画,动画编辑可以用 Blender
二、实现步骤
1.导入模型并设置动画权重
BABYLON.SceneLoader.ImportMesh("", '', '', scene, (newMeshes, particleSystems, skeletons, animationGroups) => {
const idleAnim = scene.animationGroups.find(a => a.name === 'female_01_idle');
const idleParam = { name: "Idle", anim: idleAnim, weight: 1 };
idleAnim!.play(true);
idleAnim!.setWeightForAllAnimatables(1);
const walkAnim = scene.animationGroups.find(a => a.name === 'female_01_walk');
const walkParam = { name: "Walk", anim: walkAnim, weight: 0 };
walkAnim!.play(true);
walkAnim!.setWeightForAllAnimatables(0);
})
2.增加 WSAD 控制切换
将键值码和键值状态收集起来,然后通过调用状态切换来实现缓和的动作切换
let keyMap = new Map()
scene.onKeyboardObservable.add((info) => {
keyMap.set(info.event.keyCode, info.event.type == "keydown")
if (info.event.type == "keyup") {
toIdle()
}
})
不同动画切换的实现
function toIdle() {
if (currentParam === idleParam) {
return;
}
if (currentParam) {
idleParam.anim.syncAllAnimationsWith(null);
currentParam.anim.syncAllAnimationsWith(idleParam.anim.animatables[0]);
}
scene.onBeforeAnimationsObservable.removeCallback(onBeforeAnimation);
currentParam = idleParam;
scene.onBeforeAnimationsObservable.add(onBeforeAnimation);
}
function toRun() {
newMeshes[0].movePOV(0, 0, 0.1);
if (currentParam === runParam) {
return;
}
if (currentParam) {
runParam.anim.syncAllAnimationsWith(null);
currentParam.anim.syncAllAnimationsWith(runParam.anim.animatables[0]);
}
scene.onBeforeAnimationsObservable.removeCallback(onBeforeAnimation);
currentParam = runParam;
scene.onBeforeAnimationsObservable.add(onBeforeAnimation);
}
3.方向切换使用四元数差值旋转
为了增加操作体验敢特别做了方向对齐,就是人物前进和后退的方向跟镜头的方向是一致的,这边用了点计算
一开始我们使用旋转时候大部分都是直接就转过去,体验很差,很容易就给操作晕掉了,为了增加操作感我特别增加了旋转的差值,在旋转的时候做旋转缓动。
let rot
newMeshes[0].rotationQuaternion = newMeshes[0].rotationQuaternion || BABYLON.Quaternion.Identity()
const angle = BABYLON.Vector3.GetAngleBetweenVectorsOnPlane(scene.activeCamera.getForwardRay().direction, BABYLON.Vector3.Forward(), BABYLON.Vector3.Up());
BABYLON.Quaternion.SlerpToRef(newMeshes[0].rotationQuaternion, rot, 0.1, newMeshes[0].rotationQuaternion)
4.增加碰撞检测
这个只需要将场景开启 checkCollisions 同时将人物开启 collisions 就可以实现碰撞检测了