Skip to content

The Animation Controller (Geckolib4)

Tslat edited this page Sep 27, 2025 · 16 revisions

The AnimationController is the class that handles animations for GeckoLib animatables.

Its purpose is to give users an entrypoint to managing animations for an animatable instance, primarily doing so through the use of its AnimationStateHandler.

Each AnimationController handles ONE animation at a time, and each animatable may have one or more controllers, allowing for multiple simultaneous animations to be played concurrently.

Table of Contents

How it works

When you create an Animatable, you will be prompted to add registerControllers to your class. This is your space to add as many controllers as you need to add for your animating purposes.

public void registerControllers(AnimatableManager.ControllerRegistrar controllers) {
    // Register your controllers here
}

Note

If your animatable does not have any animations, you do not need to register any controllers.

You can then register a controller to the registrar, setting it up however you want to.

E.G.

public void registerControllers(AnimatableManager.ControllerRegistrar controllers) {
    controllers.add(new AnimationController<>(this, "Walking", 0, state -> {
        return state.isMoving() ? state.setAndContinue(DefaultAnimations.WALK) : PlayState.STOP;
    }));
}

Multiple Controllers

When registering multiple controllers to a single animatable, the order of the registration can be important. GeckoLib runs all controllers in the order they are registered in, which means that animations that are handled by later-registered controllers override earlier-registered ones.

For this reason, it is recommended to register 'broad' animations first (such as walking, idling, etc), then registering more specific animations later.

As an example, if we register a walk/idle controller first, then an attack controller second, then the attack controller will be able to swing the entity's arm while it is still walking, blending the two animations together.

Controller Settings

You may notice when constructing your AnimationController, there are a few parameters to pass in. There are also some additional settings you may set on your controller(s) at any time.

Constructor Parameters

These are the parameters the various constructors of AnimationController takes when creating one.

  • animatable - This is the animatable instance this controller is for (E.G. Your entity)
  • name - This is the name of the AnimationController. Each controller in an animatable should have a unique name, ideally related to what the controller's animations are (E.G. "Walking" for a walking-animation controller)
  • transitionTickTime - This is the number of game ticks that the controller should use to interpolate between two animations (or when starting an animation). This can act to smooth out movement, but may also cause your animation's timing to look off. It's recommended to leave this as default, but feel free to experiment.
  • animationHandler - This is the predicate that you as a user create to determine what animation(s) the controller should be playing, and when. See the AnimationStateHandler section for more information.

Factory Methods

These are additional methods you can add to your controller when registering it to apply various properties or settings.

Triggered Animations

AnimationControllers can have zero or more "triggered" animations registered to it. For more information on how to implement and use triggered animations, see the relevant wiki page

When a triggered animation is triggered on a controller, the controller will skip the AnimationState check entirely while the triggered animation plays. Once the triggered animation finishes, it will clear itself and return to checking the AnimationState as normal. If for some reason you want to manage the AnimationState even when a triggered animation is playing, you may do so by marking your controller with .receiveTriggeredAnimations().

Note

Enabling receiveTriggeredAnimations will cause your controller to be able to interrupt triggered animations, and so your predicate must account for the controller potentially playing a triggered animation. AnimationController#isPlayingTriggeredAnimation can be used to check this easily.

The AnimationStateHandler

The AnimationStateHandler is the user's access point into how a controller handles its animations. This is what actually determines what animation a controller is playing, and when.

An AnimationStateHandler is a predicate that takes in an instance of an AnimationState, and returns a PlayState to tell the controller what to do.

Note

The AnimationStateHandler is called every single render frame, allowing you to handle animations in real-time, as fast as physically possible. You should consider it the current 'state' of your controller, and not a place to tell animations to start/stop.

Examples

Example 1 - Basic walking

Let's look at this example:

public void registerControllers(AnimatableManager.ControllerRegistrar controllers) {
    controllers.add(new AnimationController<>(this, "Walking", state -> {
        return state.isMoving() ? state.setAndContinue(DefaultAnimations.WALK) : PlayState.STOP;
    }));
}

The above example shows an AnimationController with an AnimationStateHandler that checks if the state of the animatable is that it is currently moving, and if so, tells the controller to ensure its current animation is DefaultAnimations.WALK (a walking animation), or else that the controller should STOP if it is not moving.

This is a basic walking animation controller, defining that a walking animation should be played when the animatable is moving, or that no animation should be played if it is not.

Example 2 - Walking + Running + Idling

Let's look at this example:

public void registerControllers(AnimatableManager.ControllerRegistrar controllers) {
    controllers.add(new AnimationController<>(this, "Walk/Run/Idle", state -> {
        if (state.isMoving())
            return state.setAndContinue(MyEntity.this.isSprinting() ? DefaultAnimations.RUN : DefaultAnimations.WALK);

        return state.setAndContinue(DefaultAnimations.IDLE);
    }));
}

This example shows a more advanced walking controller. In this one, we first check if the animatable is moving. If so, we then check if our entity is sprinting (which uses the vanilla sprinting flag, but other conditions can be used). If it is sprinting, we tell it to ensure it's animating with DefaultAnimations.RUN (a running animation), or otherwise just WALK. If it is not moving at all, we tell the controller to ensure it is just doing an idle animation (DefaultAnimations.IDLE).

Notice that we do not return STOP anywhere in this controller. This is because we always want this controller to be playing an animation. We just use the predicate to determine what animation it should be.

Example 3 - Spawning

Let's look at this example:

public void registerControllers(AnimatableManager.ControllerRegistrar controllers) {
    controllers.add(new AnimationController<>(this, "Spawning", state -> {
        if (MyEntity.this.tickCount < 100)
            return state.setAndContinue(MY_SPAWN_ANIMATION);

        return PlayState.STOP;
    }));
}

This example shows a less common animation situation. In this example, we see that this entity has a spawning animation, that plays for about 100 ticks (5 seconds) when first joining the world. To achieve this, we tell the controller that if the entity has only existed for less than 100 ticks, that it should play our custom MY_SPAWN_ANIMATION, or otherwise it should just STOP and play nothing.

Example 4 - Attack animation

Let's look at this example:

public void registerControllers(AnimatableManager.ControllerRegistrar controllers) {
    controllers.add(new AnimationController<>(this, "Attack", state -> {
        if (MyEntity.this.swinging)
            return state.setAndContinue(DefaultAnimations.ATTACK_SWING);

        state.resetCurrentAnimation();

        return PlayState.STOP;
    }));
}

This example makes use of the vanilla entity swinging boolean that AI goals typically use to determine when an entity should be swinging its arm. You can use whatever condition you want to use however in order to achieve this check.

In this example, we check if the entity is currently considered to be swinging. If so, the controller should ensure it is playing the DefaultAnimations.ATTACK_SWING (an attack animation) animation.

If it is not currently swinging, we tell the controller to reset the current animation, so that in the event that the entity stopped swinging before the animation finished, the controller knows to restart again from the start, rather than continuing from where it stopped. Then, we tell it to STOP.

DefaultAnimations

GeckoLib includes a class called DefaultAnimations with it that contains a suite of built-in basic controllers and generic RawAnimations.

It is strongly recommended that you utilise this class as much as practical in order to keep your animation handling consistent and clean.

Using the animations or controllers in DefaultAnimations assumes that you are using the expected standard for animation names in your animation json.

For example, using DefaultAnimations#genericWalkIdleController assumes that you have a walk animation in your animation json called "move.walk", and an idle animation called "misc.idle".

Clone this wiki locally