VUE2+THREE.JS 按照行动轨迹移动人物模型并相机视角跟随人物
按照行动轨迹移动人物模型并相机视角跟随人物
- 1. 初始化加载模型
- 2. 开始移动模型
- 3. 人物模型启动
- 4. 暂停模型移动
- 5. 重置模型位置
- 6. 切换区域动画
- 7. 摄像机追踪模型
- 8. 移动模型位置
- 9.动画执行
人物按照上一篇博客所设定的关键点位置,匀速移动
1. 初始化加载模型
// 加载巡航人物模型 callback 动作完成的回调函数
initWalkPerson(callback) {
fbxloader("walk").then((obj) => {
obj.scale.set(2.5, 2.5, 2.5);
obj.name = "person";
person = obj;
scene.add(obj);
//有回调函数 就执行回调函数
callback && callback();
});
},
2. 开始移动模型
// 开始移动模型
startAnimation() {
if (isAnimating) return this.elMessage("当前巡航已开始,请勿多次操作", "error");
isAnimating = true;
//说明模型已加载完成,无需重复加载,直接执行动画效果
if (person) {
this.personPositionStart();
} else {
//人物模型加载完毕后在执行
this.initWalkPerson(() => {
this.personPositionStart();
});
}
},
3. 人物模型启动
//人物动画启动
personPositionStart() {
personMixer = new THREE.AnimationMixer(person);
let AnimationAction = personMixer.clipAction(person.animations[0]);
AnimationAction.play();
person.position.set(pointArr[0]);
scene.getObjectByName("path").material.visible = false; //隐藏行动轨迹动画
scene.getObjectByName("person").visible = true;
tweenHandlers = [];
// 定义速度(单位:单位长度/秒)
const speed = 300; // 你可以根据需要调整这个速度值
let prevTween = null;
let startPos = new THREE.Vector3(...pointArr[0]);
for (let i = 1; i < pointArr.length; i++) {
// 每次循环设置下一个目标点
const endPos = new THREE.Vector3(...pointArr[i]);
const newTween = this.createTween(startPos.clone(), endPos, speed);
tweenHandlers.push(newTween);
if (prevTween) {
prevTween.chain(newTween);
} else {
// 如果是序列中的第一个tween,立即开始动画
newTween.start();
}
// 将此tween存储为下一个tween的'prevTween'
prevTween = newTween;
// 更新起始点为当前结束点,为下一个循环准备
startPos.copy(endPos);
}
// 开始第一个tween动画
if (tweenHandlers.length > 0) {
currentTween = tweenHandlers[0];
currentTween.start();
isAnimating = true;
}
// 在最后一个Tween结束后执行的动作
prevTween.onComplete(() => {
// 在动画被标记为完成时才重置位置
this.resetPosition();
});
},
4. 暂停模型移动
// 暂停模型移动
pauseAnimation() {
if (!isAnimating) {
this.elMessage("当前巡航未开始", "warning");
return;
}
if (this.isPaused) {
// 恢复摄像机状态
camera.position.copy(savedCameraPosition);
controls.target.copy(savedCameraTarget);
controls.update();
// 恢复动画
tweenHandlers.forEach((tween) => tween.resume());
personMixer.timeScale = 1;
this.isPaused = false; //设置this.isPaused为false
isAnimating = true;
this.elMessage("动画已恢复", "success");
this.updateCameraPosition(person, camera, new THREE.Vector3(0, 250, 200));
} else {
// 保存当前摄像机状态
savedCameraPosition = camera.position.clone();
savedCameraTarget = controls.target.clone();
// 暂停动画
tweenHandlers.forEach((tween) => tween.pause());
personMixer.timeScale = 0;
this.isPaused = true; //设置this.isPaused为true
this.elMessage("动画已暂停", "success");
}
},
5. 重置模型位置
// 重置模型位置
resetPosition() {
isAnimating = false;
this.pauseAnimation();
// 将模型从场景中移除
scene.getObjectByName("person").visible = false;
// 清理动画混合器
if (personMixer) {
personMixer.uncacheClip(personMixer._actions[0]._clip);
personMixer = null;
}
tweenHandlers.forEach((item) => item.stop());
tweenHandlers = [];
// 重置动画状态
this.isPaused = false;
this.tweenArea({ x: -5000, y: 7000, z: 16000 }, { x: 0, y: 0, z: 1 });
//显示行动轨迹
scene.getObjectByName("path").material.visible = true;
},
6. 切换区域动画
// 切换区域动画
tweenArea(Position, controlsTarget) {
// 传递任意目标位置,从当前位置运动到目标位置
const p1 = {
// 定义相机位置是目标位置到中心点距离的2.2倍
x: camera.position.x,
y: camera.position.y,
z: camera.position.z,
};
const p2 = {
x: Position.x,
y: Position.y,
z: Position.z,
};
changeAreaTween = new TWEEN.Tween(p1).to(p2, 1200); // 第一段动画
const update = function (object) {
camera.rotation.y = (90 * Math.PI) / 180;
camera.position.set(object.x, object.y, object.z);
controls.target = new THREE.Vector3(controlsTarget.x, controlsTarget.y, controlsTarget.z);
// camera.lookAt(lookAt); // 保证动画执行时,相机焦距在中心点
controls.enabled = false;
controls.update();
};
changeAreaTween.onUpdate(update);
// 动画完成后的执行函数
changeAreaTween.onComplete(() => {
controls.enabled = true; // 执行完成后开启控制
});
changeAreaTween.easing(TWEEN.Easing.Quadratic.InOut);
changeAreaTween.start();
},
7. 摄像机追踪模型
// 摄像机追踪模型
updateCameraPosition(model, camera, offset) {
if (!this.isPaused && isAnimating) {
// 添加条件判断
const desiredPosition = new THREE.Vector3().copy(model.position).add(offset);
camera.position.lerp(desiredPosition, 0.05);
camera.lookAt(model.position);
}
},
8. 移动模型位置
// 移动模型位置
createTween(startPosition, endPosition, speed) {
// 计算起点到终点的距离
const distance = startPosition.distanceTo(endPosition);
// 使用距离除以速度来计算持续时间
const duration = (distance / speed) * 1000; // 持续时间(以毫秒为单位)
// 创建并返回一个新的Tween动画
return new TWEEN.Tween(startPosition)
.to({ x: endPosition.x, y: endPosition.y, z: endPosition.z }, duration)
.easing(TWEEN.Easing.Quadratic.InOut)
.onUpdate(() => {
//相机的相对偏移量,z=-400 在人物模型的后面
const relativeCameraOffset = new THREE.Vector3(0, 100, -400);
const targetCameraPosition = relativeCameraOffset.applyMatrix4(person.matrixWorld);
camera.position.set(targetCameraPosition.x, targetCameraPosition.y, targetCameraPosition.z);
//更新控制器的目标为Person的位置
const walkerPosition = person.position.clone();
controls.target = new THREE.Vector3(walkerPosition.x, 100, walkerPosition.z);
// 确保控制器的变更生效
controls.update();
// 更新模型位置
person.position.copy(startPosition);
person.lookAt(endPosition);
})
.onComplete(() => {
// 动画完成时,确保模型位置与结束位置相匹配
person.position.copy(endPosition);
});
},
9.动画执行
全局定义的参数:
let personMixer = null; // 巡航混合器变量
let personClock = new THREE.Clock(); // 巡航计时工具
// 获取巡航时间差
const personDelta = personClock.getDelta();
if (personMixer && isAnimating) {
personMixer.update(personDelta);
}
TWEEN.update();