Ecctrl uses a lightweight animation state resolver. The controller writes the current state into useEcctrlAnimationStore, which you can consume to drive your model's animations.
Default States:
"IDLE" | "WALK" | "RUN" | "JUMP_START" | "JUMP_IDLE" | "JUMP_FALL" | "JUMP_LAND"
Basic Setup:
- Use
EcctrlAnimationStateController to link the Ecctrl handle to the animation store. - Consume
useEcctrlAnimationStore in your model component to react to state changes.
Custom State Mapping:
Pass a resolver to EcctrlAnimationStateController to implement custom logic (e.g., switching to RUN based on moveSpeed). Use resolveEcctrlAnimationState(ctx) within your resolver to fall back to default behavior.
import { useEffect, useRef } from "react";
import { Ecctrl, type EcctrlHandle } from "ecctrl";
import { EcctrlAnimationStateController, useEcctrlAnimationStore } from "ecctrl/animation";
import { useAnimations, useGLTF } from "@react-three/drei";
const ANIMATION_MAP = {
IDLE: "Idle",
WALK: "Walk",
RUN: "Run",
JUMP_START: "Jump_Start",
JUMP_IDLE: "Jump_Idle",
JUMP_FALL: "Jump_Fall",
JUMP_LAND: "Jump_Land",
} as const;
function AnimatedCharacterModel() {
const group = useRef(null);
const { scene, animations } = useGLTF("/character.glb");
const { actions } = useAnimations(animations, group);
const animationState = useEcctrlAnimationStore((state) => state.animationState);
useEffect(() => {
const action = actions[ANIMATION_MAP[animationState]];
if (!action) return;
action.reset().fadeIn(0.15).play();
return () => action.fadeOut(0.15);
}, [actions, animationState]);
return (
<group ref={group}>
<primitive object={scene} />
</group>
);
}
function CharacterWithAnimation() {
const ecctrl = useRef<EcctrlHandle>(null);
return (
<>
<EcctrlAnimationStateController ecctrl={ecctrl} />
<Ecctrl ref={ecctrl}>
<AnimatedCharacterModel />
</Ecctrl>
</>
);
}