如何利用AnimationMixer检测怪物攻击动画的指定关键帧时机?
精准检测AnimationMixer动画关键帧,同步触发攻击逻辑
当然有办法实现!在Three.js里,结合AnimationMixer和AnimationAction这类核心动画类,完全可以精准定位到Blender导出动画里的特定关键帧时机,完美同步攻击动作和伤害、受击反应这类逻辑。下面给你一步步拆解可行的方案:
第一步:先理清Blender关键帧和Three.js动画时间的对应关系
Blender里的关键帧是基于帧率的(比如默认24fps),而Three.js的动画时间是以秒为单位的。所以要先把Blender里的目标关键帧转换成Three.js的时间戳:
const blenderFps = 24; // 你的Blender项目帧率,按需修改 const targetBlenderFrame = 12; // 你要触发逻辑的关键帧编号 const triggerTime = targetBlenderFrame / blenderFps; // 转换为Three.js的时间(秒)
方案一:在动画更新循环中实时检测时间
这是最直接的方式,在AnimationMixer的每帧更新里,检查攻击动画的当前播放时间是否到达触发点:
// 先定义几个全局/模块级变量 let hasTriggeredDamage = false; const clock = new THREE.Clock(); const mixer = new THREE.AnimationMixer(monsterMesh); const attackAction = mixer.clipAction(attackClip); function animate() { requestAnimationFrame(animate); const deltaTime = clock.getDelta(); mixer.update(deltaTime); // 只有当攻击动画在播放时才检测 if (attackAction.isRunning()) { // 处理循环动画:取当前时间在单轮动画内的相对值 const currentClipTime = attackAction.time % attackAction.getClip().duration; // 设置一个小阈值,避免因帧率问题错过触发点 const threshold = 0.01; if (currentClipTime >= triggerTime - threshold && currentClipTime <= triggerTime + threshold) { if (!hasTriggeredDamage) { // 这里触发你的伤害、受击反应逻辑 triggerPlayerDamage(); hasTriggeredDamage = true; // 标记已触发,防止同一帧重复执行 } } else if (currentClipTime < triggerTime - threshold) { // 当动画循环回到触发点之前,重置标记,准备下一次触发 hasTriggeredDamage = false; } } }
方案二:直接读取动画片段的关键帧轨道数据
如果想更精准地获取关键帧的时间点(比如不确定Blender帧率是否和导出一致),可以直接从AnimationClip的轨道中提取关键帧时间:
const attackClip = attackAction.getClip(); // 找到对应手臂动作的轨道(根据你Blender里的骨骼命名修改,比如"Arm_R.rotation") const targetTrack = attackClip.tracks.find(track => track.name.includes('Arm') && track.name.includes('rotation') ); if (targetTrack) { // targetTrack.times 是该轨道所有关键帧的时间数组(单位:秒) // 这里可以直接匹配你要的触发时间,或者遍历找到对应Blender关键帧的时间 const targetTime = targetBlenderFrame / blenderFps; const matchedTime = targetTrack.times.find(t => Math.abs(t - targetTime) < 0.001); if (matchedTime) { // 之后的检测逻辑和方案一一致,用matchedTime作为triggerTime即可 } }
几个需要注意的细节
- 循环动画处理:如果攻击动画是循环播放的,一定要用
attackAction.time % attackAction.getClip().duration来获取单轮动画内的相对时间,否则时间会一直累加,无法重复触发。 - 触发阈值:因为
mixer.update()是每帧执行,可能刚好跳过触发时间点,所以设置一个±0.01秒左右的阈值,确保不会错过触发时机。 - 防止重复触发:用一个布尔变量标记是否已经触发过,避免同一关键帧多次执行伤害逻辑。
这样就能精准在怪物手臂伸展的关键帧时机,同步触发你需要的所有游戏逻辑啦!
内容的提问来源于stack exchange,提问作者pera




