CHOIX
November 22, 2023, 1:36am
1
I have here a model.glb, and two different fbx animations without skin. I want to switch between the animations smoothly; here is a working prototype with abrupt animation switching:
function Model(props) {
const { nodes, scene } = useGLTF(props.url);
const wavingAction = useFBX("Waving.fbx");
const wavingAnimation = wavingAction.animations;
const talkingAction = useFBX("Talking.fbx");
const talkingAnimation = talkingAction.animations;
const mixer = useRef(new AnimationMixer());
useFrame((state, delta) => mixer.current.update(delta));
useEffect(() => {
mixer.current.clipAction(talkingAnimation[0], scene).play();
}, [])
useEffect(() => {
const unsubscribe = subscribe(proxy, () => {
if (proxy.isTalking) {
mixer.current.clipAction(talkingAnimation[0], scene).play();
mixer.current.clipAction(wavingAnimation[0], scene).stop();
} else {
mixer.current.clipAction(wavingAnimation[0], scene).play();
mixer.current.clipAction(talkingAnimation[0], scene).stop();
}
});
return () => {
unsubscribe();
};
}, [proxy]); // Make sure to pass the correct dependencies
return <primitive {...props} object={scene} />;
}
So I replaced the state conditions like this:
if (proxy.isTalking) {
mixer.current.clipAction(talkingAnimation[0], scene).fadeIn(0.5).play();
mixer.current.clipAction(wavingAnimation[0], scene).fadeOut(0.5).stop();
} else {
mixer.current.clipAction(wavingAnimation[0], scene).fadeIn(0.5).play();
mixer.current.clipAction(talkingAnimation[0], scene).fadeOut(0.5).stop();
}
But now it is animating once and then bouncing back to the reset (T-pose) form.
there is also the possiblity of crossFadeTo(action); but I think I need to rearange my code structure and use the mixer differently.
three fiber, drei is welcome; but I also appriciate basic three.js.
Whether I’m using r3f or vanilla, the general sequence I use is,
fadeOut
the old action,
reset
, fadeIn
and play
the new action.
E.g., going from idle to walking
actions['idle'].fadeOut(0.1)
actions['walk'].reset().fadeIn(0.1).play()
R3F example : Obstacle Course
Vanilla/TypeScript : Kick Boxing
In react you could also use this basic pattern (untested, use it as a guide)
const [action, setAction] = useState()
useEffect(() => {
action && action.reset().fadeIn(0.1).play()
return () => {
action && action.fadeOut(0.1)
}
}, [action])
then some where else in your code call
setAction(some-preloaded-animationAction)
Also. if you open your FBX animations in blender, you can then export them as glB and the file sizes will be much smaller.
1 Like
CHOIX
November 22, 2023, 3:25pm
3
Don’t you need an animationmixer to handle the transition between the animations? I updated the component similar to your code; what I noticed is it is always going to the T-pose first and then from there transition to the next animation - shouldn’t it just fade into the next animation where ever the bones are at the moment when I say fadeout? - or does fadeout means: go to reset pose?
if (proxy.isTalking) {
// wavingClipAction.crossFadeTo(talkingClipAction, 1, true); not worked
wavingClipAction.fadeOut(0.4)
talkingClipAction.reset().fadeIn(0.1).play()
} else {
// talkingClipAction.crossFadeTo(wavingClipAction, 1, true); not worked
talkingClipAction.fadeOut(0.4)
wavingClipAction.reset().fadeIn(0.1).play()
}
This here gives a better “transition” - at least it does not try to go to the T-pose:
if (proxy.isTalking) {
wavingClipAction.fadeOut(1)
talkingClipAction.fadeIn(1).play()
talkingClipAction.reset();
} else {
talkingClipAction.fadeOut(1)
wavingClipAction.fadeIn(1).play()
wavingClipAction.reset();
}
what does reset() mean here? go back to the pose without animation applied? (T-pose in my case)
Hey, I have since tested what I suggested at the beginning, and it works.
Animation Controller : https://sbcode.net/react-three-fiber/animation-controller/
CHOIX
December 3, 2023, 10:17pm
5
haha the fancy pose got me.
thx for the example! <3
hi, i’ve the same problem, with the same code as you
my code here : useAnimations (@react-fiber/drei) + NextJS14
But my prob is i’m with NextJS, then i don’t understand why it not works well
Thanks @seanwasere I was having this T-pose issue and your referance code helped me to resolve it. Here is my custom hook for reference.
import { useEffect, useMemo, useState } from "react";
import { useFBX } from "@react-three/drei";
import { AnimationMixer } from "three";
import { useFrame } from "@react-three/fiber";
const useCustomAnimation = (group, initialAnimationName = "Breathing") => {
const angryAnimation = useFBX("animations/angry_point.fbx").animations;
const laughingAnimation = useFBX("animations/laughing.fbx").animations;
const breathingAnimation = useFBX("animations/breathing_Idle.fbx").animations;
const greetingsAnimation = useFBX("animations/greetings.fbx").animations;
const dancingAnimation = useFBX("animations/silly_dancing.fbx").animations;
const surprisedAnimation = useFBX("animations/surprised.fbx").animations;
const talking1Animation = useFBX("animations/talking_1.fbx").animations;
const talking2Animation = useFBX("animations/talking_2.fbx").animations;
const talking3Animation = useFBX("animations/talking_3.fbx").animations;
const tauntAnimation = useFBX("animations/taunt.fbx").animations;
const actions = useMemo(() => [], []);
const mixer = useMemo(() => new AnimationMixer(), []);
const [currentAnimation, setCurrentAnimation] = useState(
actions[initialAnimationName]
);
useEffect(() => {
actions["Angry"] = mixer.clipAction(angryAnimation[0], group.current);
actions["Laughing"] = mixer.clipAction(laughingAnimation[0], group.current);
actions["Breathing"] = mixer.clipAction(breathingAnimation[0] ,group.current);
actions["Greetings"] = mixer.clipAction(greetingsAnimation[0], group.current);
actions["Dancing"] = mixer.clipAction(dancingAnimation[0], group.current);
actions["Surprised"] = mixer.clipAction(surprisedAnimation[0], group.current);
actions["Talking_1"] = mixer.clipAction(talking1Animation[0], group.current);
actions["Talking_2"] = mixer.clipAction(talking2Animation[0], group.current);
actions["Talking_3"] = mixer.clipAction(talking3Animation[0], group.current);
actions["Taunt"] = mixer.clipAction(tauntAnimation[0], group.current);
const initialAction = actions[initialAnimationName] || actions["Greetings"]
setCurrentAnimation(initialAction)
initialAction.play()
}, []);
useFrame((_, delta) => {
// Update the mixer with the time delt
mixer.update(delta);
});
useEffect(() => {
currentAnimation?.reset().fadeIn(0.5).play();
return () => {
currentAnimation?.fadeOut(0.5);
};
}, [currentAnimation]);
const changeAnimation = (animationName) => {
console.log("Changing animation to", animationName);
setCurrentAnimation(actions[animationName]);
};
return { actions, changeAnimation };
};
export default useCustomAnimation;
1 Like