You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在React Three Fiber中,如何逐个加载GLB模型动画并在对应动画运行时触发关联函数

在React Three Fiber中,如何逐个加载GLB模型动画并在对应动画运行时触发关联函数

嘿,我完全懂你现在的困扰——用useGLTF和useAnimations拿到动画后,要么所有动画一起触发,要么绑定的函数一开始就跑起来,根本没法对应到单个动画的真实播放状态。我来帮你调整代码,实现每个GLB动画独立可控,并且只有当它真正在运行时才触发对应的关联函数。

首先咱们理清核心需求:一是能单独控制每个动画的播放/暂停/重置,二是给每个动画绑定专属的触发逻辑,只有动画处于播放状态时才执行函数。

第一步:先把动画和触发函数做映射

先把每个动画要触发的函数整理成一个对象,后续维护起来也方便,比如:

// 替换成你实际的动画名和业务逻辑
const animationTriggers = {
  "walk": () => {
    console.log("走路动画正在播放");
    // 这里写你要执行的逻辑,比如更新页面状态、触发音效等
  },
  "jump": () => {
    console.log("跳跃动画正在播放");
  }
};

第二步:优化动画加载与事件绑定

你原来的代码是把所有动画一股脑存起来,现在咱们在加载动画时,给每个动画绑定专属的触发事件——Three.js的AnimationAction提供了onStart、onLoop、onEnd这些原生事件,刚好能满足咱们的需求。修改你的useEffect部分:

import * as THREE from 'three';
// 其他导入保持不变

const { scene, animations } = useGLTF(url);
const { actions, names } = useAnimations(animations, scene);
const [animationData, setAnimationData] = useState<Record<string, Record<string, THREE.AnimationAction>>>({});

useEffect(() => {
  if (names.length === 0) return;

  // 逐个处理每个动画,绑定触发逻辑
  const newAnimations = names.reduce((acc, animationName) => {
    const action = actions[animationName];
    if (!action) return acc;

    // 配置动画的基础行为(按需调整)
    action.clampWhenFinished = true; // 动画结束后停在最后一帧
    action.setLoop(THREE.LoopRepeat, Infinity); // 设置无限循环,也可改成LoopOnce(仅播放一次)

    // 给动画绑定触发函数
    if (animationTriggers[animationName]) {
      // 动画开始播放时触发一次
      action.onStart = () => {
        console.log(`动画 ${animationName} 开始播放`);
        animationTriggers[animationName]();
      };

      // 动画每播放一帧就触发(如果需要持续执行逻辑就打开)
      action.onLoop = () => {
        // 加判断:只有动画未暂停且正在运行时才触发
        if (!action.paused && action.isRunning()) {
          animationTriggers[animationName]();
        }
      };

      // 动画结束时触发(可选)
      action.onEnd = () => {
        console.log(`动画 ${animationName} 播放结束`);
      };
    }

    acc[animationName] = action;
    return acc;
  }, {} as Record<string, THREE.AnimationAction>);

  // 把处理好的动画存入状态
  setAnimationData(prev => ({
    ...prev,
    [url]: newAnimations
  }));
}, [actions, names, url]);

第三步:修改动画控制逻辑,支持单个动画操作

你原来的controlAnimations是批量操作所有动画,现在咱们改成可以单独控制某个动画,同时保留批量操作的选项:

// 单个动画的处理逻辑,抽成独立函数复用
const handleSingleAnimation = (action: THREE.AnimationAction, operation: 'play' | 'pause' | 'reset') => {
  switch (operation) {
    case 'play':
      if (!action.isRunning() || action.paused) {
        action.paused = false;
        action.play();
      }
      break;
    case 'pause':
      action.paused = true;
      break;
    case 'reset':
      action.stop();
      action.paused = true;
      action.reset(); // 重置动画到初始状态
      break;
  }
};

// 动画控制主函数:可指定单个动画,也可批量操作所有
const controlAnimations = (targetAnimation?: string, operation: 'play' | 'pause' | 'reset' = 'play') => {
  const modelAnimations = animationData[url];
  if (!modelAnimations) return;

  if (targetAnimation) {
    // 只操作指定的单个动画
    const targetAction = modelAnimations[targetAnimation];
    if (targetAction) handleSingleAnimation(targetAction, operation);
  } else {
    // 批量操作当前模型的所有动画(保留原需求的话)
    Object.values(modelAnimations).forEach(action => {
      handleSingleAnimation(action, operation);
    });
  }
};

几个关键细节要注意

  1. 事件选择:如果只需要在动画开始时触发一次逻辑,用onStart就够了;如果需要动画播放过程中持续执行,就用onLoop,记得加!action.paused && action.isRunning()的判断,避免暂停时误触发。
  2. 动画循环配置:setLoop的参数可以按需调整,THREE.LoopOnce是播放一次就停,THREE.LoopRepeat是重复播放。
  3. 状态判断:用action.isRunning()能准确判断动画是否处于运行状态,action.paused判断是否被暂停,结合起来就能避免不必要的函数触发。

这样修改后,你就可以单独控制每个动画了,比如调用controlAnimations('walk', 'play')播放走路动画,这时候只有走路动画的触发函数会执行,完全不会影响其他动画的状态。

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.08 14:29:34