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

9 篇文章 0 订阅

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

在这里插入图片描述

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();
  • 14
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值