在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); }); } };
几个关键细节要注意
- 事件选择:如果只需要在动画开始时触发一次逻辑,用
onStart就够了;如果需要动画播放过程中持续执行,就用onLoop,记得加!action.paused && action.isRunning()的判断,避免暂停时误触发。 - 动画循环配置:
setLoop的参数可以按需调整,THREE.LoopOnce是播放一次就停,THREE.LoopRepeat是重复播放。 - 状态判断:用
action.isRunning()能准确判断动画是否处于运行状态,action.paused判断是否被暂停,结合起来就能避免不必要的函数触发。
这样修改后,你就可以单独控制每个动画了,比如调用controlAnimations('walk', 'play')播放走路动画,这时候只有走路动画的触发函数会执行,完全不会影响其他动画的状态。
内容来源于stack exchange
相关产品推荐
相关产品推荐

