You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何利用AnimationMixer检测怪物攻击动画的指定关键帧时机?

精准检测AnimationMixer动画关键帧,同步触发攻击逻辑

当然有办法实现!在Three.js里,结合AnimationMixerAnimationAction这类核心动画类,完全可以精准定位到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

火山引擎 最新活动