I have two animations: robot_idle.fbx and robot_shoot.fbx. I’m displaying the robot_idle animation by default when the page loads. However, I need to display the robot_shoot animation whenever the user clicks on the page, and then return to the idle animation. Could I please have some assistance with this? I’m relatively new to Three.js.
"use client";
import { useRef, useEffect, useState } from "react";
import * as THREE from "three";
import { Canvas, useLoader, useFrame } from "@react-three/fiber";
import { useAnimations } from "@react-three/drei";
import { FBXLoader } from "three/examples/jsm/Addons.js";
const Model = () => {
const [error, setError] = useState<string | null>(null);
const [loaded, setLoaded] = useState(false);
const fbx = useLoader(FBXLoader, "/models/robot.fbx", undefined, (error) => {
setError(`Failed to load FBX: ${error}`);
});
const idleAnimation = useLoader(FBXLoader, "/models/animations/robot_idle.fbx", undefined, (error) => {
setError(`Failed to load idle animation: ${error}`);
});
const { mixer } = useAnimations(idleAnimation.animations, fbx);
const mouseX = useRef(0);
const [
baseTexture1,
normalTexture1,
roughnessTexture1,
metallicTexture1,
emissiveTexture1,
aoTexture1,
baseTexture2,
normalTexture2,
roughnessTexture2,
metallicTexture2,
emissiveTexture2,
aoTexture2,
] = useLoader(THREE.TextureLoader, [
"/models/textures/torso_base_color.png",
"/models/textures/torso_normal.png",
"/models/textures/torso_roughness.png",
"/models/textures/torso_metallic.png",
"/models/textures/torso_emissive.png",
"/models/textures/torso_ao.png",
"/models/textures/limbs_base_color.png",
"/models/textures/limbs_normal.png",
"/models/textures/limbs_roughness.png",
"/models/textures/limbs_metallic.png",
"/models/textures/limbs_emissive.png",
"/models/textures/limbs_ao.png",
]);
const groupRef = useRef<THREE.Group>(null);
useEffect(() => {
if (fbx && idleAnimation) {
const torsoMaterial = new THREE.MeshStandardMaterial({
map: baseTexture1,
normalMap: normalTexture1,
roughnessMap: roughnessTexture1,
metalnessMap: metallicTexture1,
emissiveMap: emissiveTexture1,
aoMap: aoTexture1,
emissive: new THREE.Color(0xffffff),
emissiveIntensity: 1,
roughness: 0.5,
metalness: 0.8,
});
const limbsMaterial = new THREE.MeshStandardMaterial({
map: baseTexture2,
normalMap: normalTexture2,
roughnessMap: roughnessTexture2,
metalnessMap: metallicTexture2,
emissiveMap: emissiveTexture2,
aoMap: aoTexture2,
emissive: new THREE.Color(0xffffff),
emissiveIntensity: 1,
roughness: 0.5,
metalness: 0.85,
});
fbx.traverse((child) => {
if (child instanceof THREE.Mesh) {
if (child.name.toLowerCase().includes("torso")) {
child.material = torsoMaterial;
} else {
child.material = limbsMaterial;
}
child.castShadow = true;
child.receiveShadow = true;
}
});
const idleAction = mixer.clipAction(idleAnimation.animations[0]);
idleAction.play();
setLoaded(true);
}
const handleMouseMove = (event: MouseEvent) => {
mouseX.current = (event.clientX / window.innerWidth) * 2 - 1;
};
window.addEventListener("mousemove", handleMouseMove);
return () => {
window.removeEventListener("mousemove", handleMouseMove);
};
}, [
fbx,
idleAnimation,
mixer,
baseTexture1,
normalTexture1,
roughnessTexture1,
metallicTexture1,
emissiveTexture1,
aoTexture1,
baseTexture2,
normalTexture2,
roughnessTexture2,
metallicTexture2,
emissiveTexture2,
aoTexture2,
]);
useFrame(() => {
if (groupRef.current) {
groupRef.current.rotation.y = THREE.MathUtils.lerp(
groupRef.current.rotation.y,
mouseX.current * 0.5,
0.1,
);
}
});
if (error) {
console.error(error);
return null;
}
if (!loaded) {
return null;
}
return (
<group ref={groupRef}>
<primitive
object={fbx}
scale={0.013}
position={[0, -1.2, 0]}
rotation={[0, Math.PI * 0.1, 0]}
/>
</group>
);
};
const Scene = () => {
return (
<>
<ambientLight intensity={0.5} />
<directionalLight
color="#dec3c4"
position={[5, 5, 5]}
intensity={1}
castShadow
/>
<Model />
</>
);
};
const AnimatedRobot = () => {
return (
<div className="h-full w-full">
<Canvas shadows camera={{ position: [0, 0, 3], fov: 60 }}>
<Scene />
</Canvas>
</div>
);
};
export default AnimatedRobot;
I tried implementing same useLoader for the shoot animation to display on click, but it didn’t really work as expected.