VUE2+THREE.JS 按照行动轨迹移动人物模型并相机视角跟随人物

按照行动轨迹移动人物模型并相机视角跟随人物

  • [1. 初始化加载模型](#1. 初始化加载模型)
  • [2. 开始移动模型](#2. 开始移动模型)
  • [3. 人物模型启动](#3. 人物模型启动)
  • [4. 暂停模型移动](#4. 暂停模型移动)
  • [5. 重置模型位置](#5. 重置模型位置)
  • [6. 切换区域动画](#6. 切换区域动画)
  • [7. 摄像机追踪模型](#7. 摄像机追踪模型)
  • [8. 移动模型位置](#8. 移动模型位置)
  • 9.动画执行

人物按照上一篇博客所设定的关键点位置,匀速移动

1. 初始化加载模型

javascript 复制代码
// 加载巡航人物模型 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. 开始移动模型

javascript 复制代码
// 开始移动模型
startAnimation() {
	if (isAnimating) return this.elMessage("当前巡航已开始,请勿多次操作", "error");
	isAnimating = true;
	//说明模型已加载完成,无需重复加载,直接执行动画效果
	if (person) {
		this.personPositionStart();
	} else {
		//人物模型加载完毕后在执行
		this.initWalkPerson(() => {
			this.personPositionStart();
		});
	}
},

3. 人物模型启动

javascript 复制代码
//人物动画启动
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. 暂停模型移动

javascript 复制代码
// 暂停模型移动
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. 重置模型位置

javascript 复制代码
// 重置模型位置
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. 切换区域动画

javascript 复制代码
// 切换区域动画
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. 摄像机追踪模型

javascript 复制代码
// 摄像机追踪模型
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. 移动模型位置

javascript 复制代码
// 移动模型位置
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.动画执行

全局定义的参数:

javascript 复制代码
let personMixer = null; // 巡航混合器变量
let personClock = new THREE.Clock(); // 巡航计时工具
javascript 复制代码
// 获取巡航时间差
const personDelta = personClock.getDelta();

if (personMixer && isAnimating) {
	personMixer.update(personDelta);
}
TWEEN.update();
相关推荐
清灵xmf13 分钟前
提前解锁 Vue 3.5 的新特性
前端·javascript·vue.js·vue3.5
simple_whu17 分钟前
相机畸变系数$b_1,b_2$与畸变系数aspect ratio和skew的互转
数码相机·计算机视觉·摄影测量
Jiaberrr19 分钟前
教你如何在微信小程序中轻松实现人脸识别功能
javascript·微信小程序·小程序·人脸识别·百度ai
小丁爱养花22 分钟前
记忆化搜索专题——算法简介&力扣实战应用
java·开发语言·算法·leetcode·深度优先
白云~️26 分钟前
监听html元素是否被删除,删除之后重新生成被删除的元素
前端·javascript·html
2401_8644769327 分钟前
无线领夹麦克风哪个降噪好?一文搞懂麦克风什么牌子的音质效果好
javascript·git·sql·github·mssql
爱上语文32 分钟前
Springboot三层架构
java·开发语言·spring boot·spring·架构
Crossoads35 分钟前
【数据结构】排序算法---快速排序
c语言·开发语言·数据结构·算法·排序算法
6230_37 分钟前
git使用“保姆级”教程2——初始化及工作机制解释
开发语言·前端·笔记·git·html·学习方法·改行学it
挽月00144 分钟前
C++单例模式
开发语言·c++·单例模式